Lashtw commited on
Commit
c465c27
·
verified ·
1 Parent(s): c0b3084

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +85 -13
index.html CHANGED
@@ -10,7 +10,7 @@
10
  <script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.2/dist/confetti.browser.min.js"></script>
11
  <!-- 載入 QR Code 產生器 -->
12
  <script src="https://cdn.jsdelivr.net/npm/qrcode-generator/qrcode.js"></script>
13
- <!-- 【已新增】載入 Pako 壓縮函式庫 -->
14
  <script src="https://cdnjs.cloudflare.com/ajax/libs/pako/2.1.0/pako.min.js"></script>
15
  <style>
16
  /* 定義閃卡的翻轉效果 */
@@ -444,6 +444,7 @@
444
 
445
  <audio id="correct-sound" src="correct.mp3" preload="auto"></audio>
446
  <audio id="wrong-sound" src="wrong.mp3" preload="auto"></audio>
 
447
 
448
  <footer class="fixed bottom-4 right-4 text-xs text-gray-500 text-right z-50">
449
  <p>遊戲設計者:新竹縣精華國中藍星宇</p>
@@ -562,6 +563,7 @@
562
  // 音效元素
563
  const correctSound = document.getElementById('correct-sound');
564
  const wrongSound = document.getElementById('wrong-sound');
 
565
 
566
 
567
  // 應用程式狀態
@@ -770,7 +772,7 @@
770
  frontText = '請聽發音';
771
  backText = card.english;
772
  [speakBtn, speakSlowBtn].forEach(btn => btn.classList.remove('hidden'));
773
- speakWord(card.english, 0.75);
774
  answerInput.placeholder = "請輸入英文答案...";
775
  break;
776
  }
@@ -797,7 +799,7 @@
797
  case 'listen':
798
  frontText = '請聽發音'; backText = card.english;
799
  [speakBtn, speakSlowBtn].forEach(btn => btn.classList.remove('hidden'));
800
- speakWord(card.english, 0.75);
801
  answerInput.placeholder = "請輸入英文答案...";
802
  break;
803
  }
@@ -1096,9 +1098,9 @@
1096
  confetti({ particleCount: 150, spread: 70, origin: { y: 0.6 } });
1097
  };
1098
 
1099
- const speakWord = (word, rate) => {
1100
- if ('speechSynthesis' in window) {
1101
- if (window.speechSynthesis.speaking) { return; }
1102
  const wordToSpeak = word.split('(')[0].trim();
1103
  const utterance = new SpeechSynthesisUtterance(wordToSpeak);
1104
  utterance.lang = 'en-US';
@@ -1106,6 +1108,70 @@
1106
  window.speechSynthesis.speak(utterance);
1107
  }
1108
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1109
 
1110
  const getCurrentWordToSpeak = () => {
1111
  if (currentMode === 'speed' && currentSpeedCard) return currentSpeedCard.english;
@@ -1234,8 +1300,18 @@
1234
  prevBtn.addEventListener('click', () => { if (currentCardIndex > 0) { currentCardIndex--; displayCard(); }});
1235
  nextBtn.addEventListener('click', () => { if (currentCardIndex < wordsForCurrentMode.length - 1) { currentCardIndex++; displayCard(); }});
1236
  quizForm.addEventListener('submit', (e) => { e.preventDefault(); checkAnswer(); });
1237
- speakBtn.addEventListener('click', (e) => { e.stopPropagation(); const word = getCurrentWordToSpeak(); if(word) speakWord(word, 0.75); });
1238
- speakSlowBtn.addEventListener('click', (e) => { e.stopPropagation(); const word = getCurrentWordToSpeak(); if(word) speakWord(word, 0.2); });
 
 
 
 
 
 
 
 
 
 
1239
  hintBtn.addEventListener('click', () => {
1240
  const card = currentMode === 'speed' ? currentSpeedCard : quizQueue[0];
1241
  if (!card) return;
@@ -1310,7 +1386,6 @@
1310
  generateLinkBtn.disabled = true;
1311
 
1312
  try {
1313
- // 【已修改】使用 pako 壓縮資料
1314
  const wordsString = JSON.stringify(words);
1315
  const compressedData = pako.deflate(wordsString);
1316
  const base64Words = btoa(String.fromCharCode.apply(null, compressedData));
@@ -1396,21 +1471,19 @@
1396
 
1397
  confirmDeleteBtn.addEventListener('click', () => {
1398
  if (indexToDelete > -1) {
1399
- // 【已修復】建立一個局部常數來保存索引,避免在 setTimeout 執行前被重置
1400
  const indexToRemove = indexToDelete;
1401
 
1402
  const itemElement = wordListContainer.querySelector(`.list-item button[data-index="${indexToRemove}"]`)?.closest('.list-item');
1403
  if (itemElement) itemElement.classList.add('removing');
1404
 
1405
  setTimeout(() => {
1406
- // 使用局部常數來執行刪除操作
1407
  words.splice(indexToRemove, 1);
1408
  saveWordsToStorage();
1409
  renderWordList();
1410
  }, 300);
1411
  }
1412
  confirmDeleteModal.classList.add('hidden');
1413
- indexToDelete = -1; // 為下一次操作重置全域變數
1414
  });
1415
 
1416
  randomQuestionsCheckbox.addEventListener('change', () => {
@@ -1442,7 +1515,6 @@
1442
 
1443
  if (sharedData) {
1444
  try {
1445
- // 【已修改】使用 pako 解壓縮資料
1446
  const binaryString = atob(sharedData);
1447
  const compressedData = new Uint8Array(binaryString.length).map((_, i) => binaryString.charCodeAt(i));
1448
  const decodedWordsString = pako.inflate(compressedData, { to: 'string' });
 
10
  <script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.2/dist/confetti.browser.min.js"></script>
11
  <!-- 載入 QR Code 產生器 -->
12
  <script src="https://cdn.jsdelivr.net/npm/qrcode-generator/qrcode.js"></script>
13
+ <!-- 載入 Pako 壓縮函式庫 -->
14
  <script src="https://cdnjs.cloudflare.com/ajax/libs/pako/2.1.0/pako.min.js"></script>
15
  <style>
16
  /* 定義閃卡的翻轉效果 */
 
444
 
445
  <audio id="correct-sound" src="correct.mp3" preload="auto"></audio>
446
  <audio id="wrong-sound" src="wrong.mp3" preload="auto"></audio>
447
+ <audio id="api-audio-player" preload="auto"></audio>
448
 
449
  <footer class="fixed bottom-4 right-4 text-xs text-gray-500 text-right z-50">
450
  <p>遊戲設計者:新竹縣精華國中藍星宇</p>
 
563
  // 音效元素
564
  const correctSound = document.getElementById('correct-sound');
565
  const wrongSound = document.getElementById('wrong-sound');
566
+ const apiAudioPlayer = document.getElementById('api-audio-player');
567
 
568
 
569
  // 應用程式狀態
 
772
  frontText = '請聽發音';
773
  backText = card.english;
774
  [speakBtn, speakSlowBtn].forEach(btn => btn.classList.remove('hidden'));
775
+ speakWord(card.english);
776
  answerInput.placeholder = "請輸入英文答案...";
777
  break;
778
  }
 
799
  case 'listen':
800
  frontText = '請聽發音'; backText = card.english;
801
  [speakBtn, speakSlowBtn].forEach(btn => btn.classList.remove('hidden'));
802
+ speakWord(card.english);
803
  answerInput.placeholder = "請輸入英文答案...";
804
  break;
805
  }
 
1098
  confetti({ particleCount: 150, spread: 70, origin: { y: 0.6 } });
1099
  };
1100
 
1101
+ const speakWithBrowserTTS = (word, rate = 1) => {
1102
+ if ('speechSynthesis' in window) {
1103
+ window.speechSynthesis.cancel();
1104
  const wordToSpeak = word.split('(')[0].trim();
1105
  const utterance = new SpeechSynthesisUtterance(wordToSpeak);
1106
  utterance.lang = 'en-US';
 
1108
  window.speechSynthesis.speak(utterance);
1109
  }
1110
  };
1111
+
1112
+ const speakWord = async (word) => {
1113
+ speakBtn.disabled = true;
1114
+ speakSlowBtn.disabled = true;
1115
+
1116
+ const wordToSpeak = word.split('(')[0].trim();
1117
+ if (!wordToSpeak) {
1118
+ speakBtn.disabled = false;
1119
+ speakSlowBtn.disabled = false;
1120
+ return;
1121
+ }
1122
+
1123
+ const cleanedWord = wordToSpeak.replace(/[^a-zA-Z\s-]/g, '');
1124
+
1125
+ // 【已修改】智慧判斷:如果是片語,直接用瀏覽器語音;如果是單字,才用 API
1126
+ if (cleanedWord.includes(' ')) {
1127
+ speakWithBrowserTTS(cleanedWord, 0.75);
1128
+ speakBtn.disabled = false;
1129
+ speakSlowBtn.disabled = false;
1130
+ return;
1131
+ }
1132
+
1133
+ try {
1134
+ const response = await fetch(`https://api.dictionaryapi.dev/api/v2/entries/en/${cleanedWord}`);
1135
+ if (!response.ok) throw new Error('API request failed');
1136
+ const data = await response.json();
1137
+
1138
+ let audioUrl = '';
1139
+ if (data && data.length > 0) {
1140
+ for (const entry of data) {
1141
+ for (const phonetic of entry.phonetics || []) {
1142
+ if (phonetic.audio) {
1143
+ if (phonetic.audio.includes('-us.mp3')) {
1144
+ audioUrl = phonetic.audio;
1145
+ break;
1146
+ }
1147
+ if (!audioUrl) audioUrl = phonetic.audio;
1148
+ }
1149
+ }
1150
+ if (audioUrl) break;
1151
+ }
1152
+ }
1153
+
1154
+ if (audioUrl) {
1155
+ apiAudioPlayer.src = audioUrl;
1156
+ apiAudioPlayer.play().catch(error => {
1157
+ console.error("Audio playback error:", error);
1158
+ speakWithBrowserTTS(cleanedWord, 0.75);
1159
+ }).finally(() => {
1160
+ speakBtn.disabled = false;
1161
+ speakSlowBtn.disabled = false;
1162
+ });
1163
+ } else {
1164
+ speakWithBrowserTTS(cleanedWord, 0.75);
1165
+ speakBtn.disabled = false;
1166
+ speakSlowBtn.disabled = false;
1167
+ }
1168
+ } catch (error) {
1169
+ console.error("Dictionary API error:", error);
1170
+ speakWithBrowserTTS(cleanedWord, 0.75);
1171
+ speakBtn.disabled = false;
1172
+ speakSlowBtn.disabled = false;
1173
+ }
1174
+ };
1175
 
1176
  const getCurrentWordToSpeak = () => {
1177
  if (currentMode === 'speed' && currentSpeedCard) return currentSpeedCard.english;
 
1300
  prevBtn.addEventListener('click', () => { if (currentCardIndex > 0) { currentCardIndex--; displayCard(); }});
1301
  nextBtn.addEventListener('click', () => { if (currentCardIndex < wordsForCurrentMode.length - 1) { currentCardIndex++; displayCard(); }});
1302
  quizForm.addEventListener('submit', (e) => { e.preventDefault(); checkAnswer(); });
1303
+
1304
+ speakBtn.addEventListener('click', (e) => {
1305
+ e.stopPropagation();
1306
+ const word = getCurrentWordToSpeak();
1307
+ if(word) speakWord(word);
1308
+ });
1309
+ speakSlowBtn.addEventListener('click', (e) => {
1310
+ e.stopPropagation();
1311
+ const word = getCurrentWordToSpeak();
1312
+ if(word) speakWithBrowserTTS(word, 0.2);
1313
+ });
1314
+
1315
  hintBtn.addEventListener('click', () => {
1316
  const card = currentMode === 'speed' ? currentSpeedCard : quizQueue[0];
1317
  if (!card) return;
 
1386
  generateLinkBtn.disabled = true;
1387
 
1388
  try {
 
1389
  const wordsString = JSON.stringify(words);
1390
  const compressedData = pako.deflate(wordsString);
1391
  const base64Words = btoa(String.fromCharCode.apply(null, compressedData));
 
1471
 
1472
  confirmDeleteBtn.addEventListener('click', () => {
1473
  if (indexToDelete > -1) {
 
1474
  const indexToRemove = indexToDelete;
1475
 
1476
  const itemElement = wordListContainer.querySelector(`.list-item button[data-index="${indexToRemove}"]`)?.closest('.list-item');
1477
  if (itemElement) itemElement.classList.add('removing');
1478
 
1479
  setTimeout(() => {
 
1480
  words.splice(indexToRemove, 1);
1481
  saveWordsToStorage();
1482
  renderWordList();
1483
  }, 300);
1484
  }
1485
  confirmDeleteModal.classList.add('hidden');
1486
+ indexToDelete = -1;
1487
  });
1488
 
1489
  randomQuestionsCheckbox.addEventListener('change', () => {
 
1515
 
1516
  if (sharedData) {
1517
  try {
 
1518
  const binaryString = atob(sharedData);
1519
  const compressedData = new Uint8Array(binaryString.length).map((_, i) => binaryString.charCodeAt(i));
1520
  const decodedWordsString = pako.inflate(compressedData, { to: 'string' });