Spaces:
Running
Running
Update index.html
Browse files- index.html +86 -163
index.html
CHANGED
|
@@ -96,20 +96,12 @@
|
|
| 96 |
<p id="highscore-display" class="text-lg font-bold text-amber-500">0</p>
|
| 97 |
</div>
|
| 98 |
</div>
|
| 99 |
-
<!--
|
| 100 |
-
<div
|
| 101 |
-
<
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
<input type="number" id="end-range" min="1" class="w-20 p-2 border border-gray-300 rounded-lg text-center" placeholder="到">
|
| 106 |
-
</div>
|
| 107 |
-
<div class="flex items-center justify-center gap-4">
|
| 108 |
-
<input type="checkbox" id="random-questions-checkbox" class="h-5 w-5 rounded text-indigo-600 focus:ring-indigo-500 border-gray-300">
|
| 109 |
-
<label for="random-questions-checkbox" class="font-semibold text-gray-700">隨機出題:</label>
|
| 110 |
-
<input type="number" id="random-questions-count" min="1" class="w-20 p-2 border border-gray-300 rounded-lg text-center disabled:bg-gray-200 disabled:cursor-not-allowed" placeholder="題數" disabled>
|
| 111 |
-
<span class="font-semibold text-gray-700">題</span>
|
| 112 |
-
</div>
|
| 113 |
</div>
|
| 114 |
|
| 115 |
<!-- 主要功能 -->
|
|
@@ -142,7 +134,7 @@
|
|
| 142 |
</div>
|
| 143 |
|
| 144 |
<!-- 教師工具 -->
|
| 145 |
-
<div
|
| 146 |
<button id="share-game-btn" class="text-white font-semibold py-2 px-5 rounded-full transition-transform hover:scale-105 bg-violet-500 hover:bg-violet-600 text-sm">🔗 分享</button>
|
| 147 |
<button id="manage-words-btn" class="text-white font-semibold py-2 px-5 rounded-full transition-transform hover:scale-105 bg-gray-500 hover:bg-gray-600 text-sm">⚙️ 管理</button>
|
| 148 |
</div>
|
|
@@ -335,34 +327,18 @@
|
|
| 335 |
<div id="share-modal" class="hidden fixed inset-0 bg-gray-900 bg-opacity-75 flex items-center justify-center z-50 p-4">
|
| 336 |
<div class="bg-white p-8 rounded-2xl shadow-xl w-full max-w-lg">
|
| 337 |
<h3 class="text-2xl font-bold mb-4 text-gray-800">分享遊戲連結</h3>
|
| 338 |
-
<p class="text-gray-600 mb-6"
|
| 339 |
-
<div class="
|
| 340 |
-
<
|
| 341 |
-
|
| 342 |
-
<span class="ml-3 text-gray-700">禁止學生變更設定 (鎖定範圍與題數)</span>
|
| 343 |
-
</label>
|
| 344 |
-
</div>
|
| 345 |
-
|
| 346 |
-
<button id="generate-link-btn" class="w-full mb-4 bg-indigo-600 text-white font-bold py-3 rounded-lg hover:bg-indigo-700 transition-colors">
|
| 347 |
-
產生分享連結
|
| 348 |
-
</button>
|
| 349 |
-
|
| 350 |
-
<div id="share-result-container" class="hidden">
|
| 351 |
-
<p class="text-sm text-gray-500 mb-2">連結已產生:</p>
|
| 352 |
-
<div class="flex items-center gap-2">
|
| 353 |
-
<input id="share-link-input" type="text" readonly class="w-full p-3 border border-gray-300 rounded-lg bg-gray-100">
|
| 354 |
-
<button id="copy-link-btn" class="px-6 py-3 bg-violet-600 text-white rounded-lg hover:bg-violet-700 shrink-0">複製</button>
|
| 355 |
-
</div>
|
| 356 |
-
<p id="copy-feedback" class="text-green-600 text-sm h-5 mt-2 text-center font-semibold"></p>
|
| 357 |
</div>
|
| 358 |
-
|
| 359 |
<div class="flex justify-end mt-4">
|
| 360 |
<button type="button" id="close-share-modal-btn" class="px-6 py-2 bg-gray-200 text-gray-800 rounded-lg hover:bg-gray-300 transition-colors">關閉</button>
|
| 361 |
</div>
|
| 362 |
</div>
|
| 363 |
</div>
|
| 364 |
|
| 365 |
-
|
| 366 |
<!-- 清除確認 Modal -->
|
| 367 |
<div id="confirm-clear-modal" class="hidden fixed inset-0 bg-gray-900 bg-opacity-75 flex items-center justify-center z-50 p-4">
|
| 368 |
<div class="bg-white p-8 rounded-2xl shadow-xl w-full max-w-sm">
|
|
@@ -387,9 +363,6 @@
|
|
| 387 |
</div>
|
| 388 |
</div>
|
| 389 |
|
| 390 |
-
<audio id="correct-sound" src="correct.mp3" preload="auto"></audio>
|
| 391 |
-
<audio id="wrong-sound" src="wrong.mp3" preload="auto"></audio>
|
| 392 |
-
|
| 393 |
<footer class="fixed bottom-4 right-4 text-xs text-gray-500 text-right z-50">
|
| 394 |
<p>遊戲設計者:新竹縣精華國中藍星宇</p>
|
| 395 |
<p>FB教育社群:<a href="https://www.facebook.com/groups/1554372228718393" target="_blank" rel="noopener noreferrer" class="text-blue-500 hover:underline">萬物皆數</a></p>
|
|
@@ -403,14 +376,10 @@
|
|
| 403 |
const learningMode = document.getElementById('learning-mode');
|
| 404 |
const modeSelectionSection = document.getElementById('mode-selection-section');
|
| 405 |
const highscoreDisplay = document.getElementById('highscore-display');
|
| 406 |
-
const settingsContainer = document.getElementById('settings-container');
|
| 407 |
const startRangeInput = document.getElementById('start-range');
|
| 408 |
const endRangeInput = document.getElementById('end-range');
|
| 409 |
-
const teacherTools = document.getElementById('teacher-tools');
|
| 410 |
const manageWordsBtn = document.getElementById('manage-words-btn');
|
| 411 |
const shareGameBtn = document.getElementById('share-game-btn');
|
| 412 |
-
const randomQuestionsCheckbox = document.getElementById('random-questions-checkbox');
|
| 413 |
-
const randomQuestionsCountInput = document.getElementById('random-questions-count');
|
| 414 |
|
| 415 |
// 單字管理介面
|
| 416 |
const wordManagementView = document.getElementById('word-management-view');
|
|
@@ -480,9 +449,6 @@
|
|
| 480 |
const saveEditBtn = document.getElementById('save-edit-btn');
|
| 481 |
const cancelEditBtn = document.getElementById('cancel-edit-btn');
|
| 482 |
const shareModal = document.getElementById('share-modal');
|
| 483 |
-
const lockSettingsCheckbox = document.getElementById('lock-settings-checkbox');
|
| 484 |
-
const generateLinkBtn = document.getElementById('generate-link-btn');
|
| 485 |
-
const shareResultContainer = document.getElementById('share-result-container');
|
| 486 |
const shareLinkInput = document.getElementById('share-link-input');
|
| 487 |
const copyLinkBtn = document.getElementById('copy-link-btn');
|
| 488 |
const copyFeedback = document.getElementById('copy-feedback');
|
|
@@ -495,10 +461,6 @@
|
|
| 495 |
const cancelDeleteBtn = document.getElementById('cancel-delete-btn');
|
| 496 |
const confirmDeleteBtn = document.getElementById('confirm-delete-btn');
|
| 497 |
|
| 498 |
-
// 音效元素
|
| 499 |
-
const correctSound = document.getElementById('correct-sound');
|
| 500 |
-
const wrongSound = document.getElementById('wrong-sound');
|
| 501 |
-
|
| 502 |
|
| 503 |
// 應用程式狀態
|
| 504 |
let words = [];
|
|
@@ -744,32 +706,15 @@
|
|
| 744 |
const start = parseInt(startRangeInput.value, 10);
|
| 745 |
const end = parseInt(endRangeInput.value, 10);
|
| 746 |
|
| 747 |
-
|
| 748 |
-
// 1. First, determine the pool of words based on the range.
|
| 749 |
if (!isNaN(start) && !isNaN(end) && start > 0 && end >= start && end <= words.length) {
|
| 750 |
-
|
| 751 |
} else {
|
| 752 |
-
|
| 753 |
}
|
| 754 |
|
| 755 |
-
// 2. Then, check if random selection is enabled.
|
| 756 |
-
if (randomQuestionsCheckbox.checked) {
|
| 757 |
-
const randomCount = parseInt(randomQuestionsCountInput.value, 10);
|
| 758 |
-
if (!isNaN(randomCount) && randomCount > 0 && randomCount <= wordPool.length) {
|
| 759 |
-
// Shuffle the pool and take the specified number of words.
|
| 760 |
-
wordsForCurrentMode = shuffleArray([...wordPool]).slice(0, randomCount);
|
| 761 |
-
} else {
|
| 762 |
-
alert('請輸入有效的隨機題數。題數不能為零或超過所選範圍的單字總數。');
|
| 763 |
-
return;
|
| 764 |
-
}
|
| 765 |
-
} else {
|
| 766 |
-
// If not random, use the entire pool.
|
| 767 |
-
wordsForCurrentMode = wordPool;
|
| 768 |
-
}
|
| 769 |
-
|
| 770 |
-
|
| 771 |
if (wordsForCurrentMode.length === 0) {
|
| 772 |
-
alert("
|
| 773 |
return;
|
| 774 |
}
|
| 775 |
|
|
@@ -808,32 +753,75 @@
|
|
| 808 |
|
| 809 |
const wordIndex = words.findIndex(w => w.english === card.english && w.chinese === card.chinese);
|
| 810 |
|
| 811 |
-
|
| 812 |
-
let answerLang;
|
| 813 |
-
|
| 814 |
if (isReview) {
|
| 815 |
const isFlipped = flashcardContainer.classList.contains('flipped');
|
| 816 |
-
|
|
|
|
|
|
|
| 817 |
correctAnswer = card.english.split('(')[0].trim();
|
| 818 |
answerLang = 'en';
|
| 819 |
-
} else { // English side showing, user should type Chinese
|
| 820 |
correctAnswer = card.chinese.split('(')[0].trim();
|
| 821 |
answerLang = 'zh';
|
| 822 |
}
|
| 823 |
-
|
| 824 |
-
let
|
| 825 |
-
if (
|
| 826 |
-
|
| 827 |
-
|
| 828 |
-
|
| 829 |
-
|
| 830 |
-
|
| 831 |
-
|
| 832 |
-
|
| 833 |
-
|
| 834 |
-
|
| 835 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 836 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 837 |
}
|
| 838 |
|
| 839 |
let isCorrect;
|
|
@@ -846,17 +834,13 @@
|
|
| 846 |
}
|
| 847 |
|
| 848 |
if (isCorrect) {
|
| 849 |
-
correctSound.play();
|
| 850 |
answerInput.disabled = true;
|
| 851 |
submitBtn.disabled = true;
|
| 852 |
feedbackDisplay.textContent = '答對了!';
|
| 853 |
feedbackDisplay.classList.remove('text-red-500');
|
| 854 |
feedbackDisplay.classList.add('text-green-600');
|
| 855 |
triggerConfetti();
|
| 856 |
-
|
| 857 |
-
if (!flashcardContainer.classList.contains('flipped')) {
|
| 858 |
-
flashcardContainer.classList.add('flipped');
|
| 859 |
-
}
|
| 860 |
|
| 861 |
if (wordIndex !== -1) words[wordIndex].proficiency = (words[wordIndex].proficiency || 0) + 1;
|
| 862 |
|
|
@@ -864,7 +848,7 @@
|
|
| 864 |
currentScore++;
|
| 865 |
scoreDisplay.textContent = `得分: ${currentScore}`;
|
| 866 |
setTimeout(startSpeedCard, 500);
|
| 867 |
-
} else
|
| 868 |
quizQueue.shift();
|
| 869 |
|
| 870 |
setTimeout(() => {
|
|
@@ -882,13 +866,7 @@
|
|
| 882 |
}
|
| 883 |
}, 1500);
|
| 884 |
}
|
| 885 |
-
|
| 886 |
-
if (isReview) {
|
| 887 |
-
answerInput.disabled = false;
|
| 888 |
-
submitBtn.disabled = false;
|
| 889 |
-
}
|
| 890 |
-
} else { // Incorrect answer
|
| 891 |
-
wrongSound.play();
|
| 892 |
quizIncorrectCount++;
|
| 893 |
updateHintButtonVisibility();
|
| 894 |
answerInput.classList.add('shake');
|
|
@@ -902,13 +880,6 @@
|
|
| 902 |
feedbackDisplay.textContent = '答錯了,再試一次!';
|
| 903 |
feedbackDisplay.classList.add('text-red-500');
|
| 904 |
setTimeout(() => { answerInput.classList.remove('shake'); feedbackDisplay.textContent = ''; }, 1000);
|
| 905 |
-
} else if (isReview) {
|
| 906 |
-
feedbackDisplay.textContent = '答錯了!';
|
| 907 |
-
feedbackDisplay.classList.add('text-red-500');
|
| 908 |
-
if (!flashcardContainer.classList.contains('flipped')) {
|
| 909 |
-
flashcardContainer.classList.add('flipped');
|
| 910 |
-
}
|
| 911 |
-
setTimeout(() => { answerInput.classList.remove('shake'); }, 820);
|
| 912 |
} else { // Other quiz modes
|
| 913 |
answerInput.disabled = true;
|
| 914 |
submitBtn.disabled = true;
|
|
@@ -1041,7 +1012,7 @@
|
|
| 1041 |
nextBtn.addEventListener('click', () => { if (currentCardIndex < wordsForCurrentMode.length - 1) { currentCardIndex++; displayCard(); }});
|
| 1042 |
quizForm.addEventListener('submit', (e) => { e.preventDefault(); checkAnswer(); });
|
| 1043 |
speakBtn.addEventListener('click', (e) => { e.stopPropagation(); const word = getCurrentWordToSpeak(); if(word) speakWord(word, 0.75); });
|
| 1044 |
-
speakSlowBtn.addEventListener('click', (e) => { e.stopPropagation(); const word = getCurrentWordToSpeak(); if(word) speakWord(word, 0.
|
| 1045 |
hintBtn.addEventListener('click', () => {
|
| 1046 |
const card = currentMode === 'speed' ? currentSpeedCard : quizQueue[0];
|
| 1047 |
if (!card) return;
|
|
@@ -1094,21 +1065,14 @@
|
|
| 1094 |
alert('冊次和課次不能為空!');
|
| 1095 |
}
|
| 1096 |
});
|
| 1097 |
-
|
| 1098 |
-
shareGameBtn.addEventListener('click', () => {
|
| 1099 |
-
shareResultContainer.classList.add('hidden');
|
| 1100 |
-
shareLinkInput.value = '';
|
| 1101 |
-
copyFeedback.textContent = '';
|
| 1102 |
-
shareModal.classList.remove('hidden');
|
| 1103 |
-
});
|
| 1104 |
|
| 1105 |
-
|
| 1106 |
-
|
| 1107 |
const wordsString = JSON.stringify(words);
|
|
|
|
| 1108 |
const base64Words = btoa(unescape(encodeURIComponent(wordsString)));
|
| 1109 |
const start = startRangeInput.value || '';
|
| 1110 |
const end = endRangeInput.value || '';
|
| 1111 |
-
const isLocked = lockSettingsCheckbox.checked;
|
| 1112 |
|
| 1113 |
const baseUrl = window.location.href.split('?')[0];
|
| 1114 |
const url = new URL(baseUrl);
|
|
@@ -1116,16 +1080,10 @@
|
|
| 1116 |
url.searchParams.set('data', base64Words);
|
| 1117 |
if (start) url.searchParams.set('start', start);
|
| 1118 |
if (end) url.searchParams.set('end', end);
|
| 1119 |
-
if (isLocked) {
|
| 1120 |
-
url.searchParams.set('lock', 'true');
|
| 1121 |
-
if(randomQuestionsCheckbox.checked) {
|
| 1122 |
-
const randomCount = randomQuestionsCountInput.value;
|
| 1123 |
-
if(randomCount) url.searchParams.set('random', randomCount);
|
| 1124 |
-
}
|
| 1125 |
-
}
|
| 1126 |
|
| 1127 |
shareLinkInput.value = url.toString();
|
| 1128 |
-
|
|
|
|
| 1129 |
|
| 1130 |
} catch (error) {
|
| 1131 |
console.error("產生分享連結時發生錯誤:", error);
|
|
@@ -1133,7 +1091,6 @@
|
|
| 1133 |
}
|
| 1134 |
});
|
| 1135 |
|
| 1136 |
-
|
| 1137 |
copyLinkBtn.addEventListener('click', () => {
|
| 1138 |
shareLinkInput.select();
|
| 1139 |
shareLinkInput.setSelectionRange(0, 99999); // 兼容移動設備
|
|
@@ -1195,26 +1152,6 @@
|
|
| 1195 |
confirmDeleteModal.classList.add('hidden');
|
| 1196 |
indexToDelete = -1;
|
| 1197 |
});
|
| 1198 |
-
|
| 1199 |
-
randomQuestionsCheckbox.addEventListener('change', () => {
|
| 1200 |
-
randomQuestionsCountInput.disabled = !randomQuestionsCheckbox.checked;
|
| 1201 |
-
if (!randomQuestionsCheckbox.checked) {
|
| 1202 |
-
randomQuestionsCountInput.value = '';
|
| 1203 |
-
} else {
|
| 1204 |
-
randomQuestionsCountInput.focus();
|
| 1205 |
-
}
|
| 1206 |
-
});
|
| 1207 |
-
|
| 1208 |
-
const updateRandomCountMax = () => {
|
| 1209 |
-
const start = parseInt(startRangeInput.value, 10) || 1;
|
| 1210 |
-
const end = parseInt(endRangeInput.value, 10) || words.length;
|
| 1211 |
-
if (end >= start) {
|
| 1212 |
-
const rangeSize = end - start + 1;
|
| 1213 |
-
randomQuestionsCountInput.max = rangeSize;
|
| 1214 |
-
}
|
| 1215 |
-
};
|
| 1216 |
-
startRangeInput.addEventListener('input', updateRandomCountMax);
|
| 1217 |
-
endRangeInput.addEventListener('input', updateRandomCountMax);
|
| 1218 |
|
| 1219 |
|
| 1220 |
// --- 應用程式初始化 ---
|
|
@@ -1266,21 +1203,8 @@
|
|
| 1266 |
|
| 1267 |
const sharedStart = urlParams.get('start');
|
| 1268 |
const sharedEnd = urlParams.get('end');
|
| 1269 |
-
const sharedRandom = urlParams.get('random');
|
| 1270 |
-
const isLocked = urlParams.get('lock') === 'true';
|
| 1271 |
-
|
| 1272 |
if (sharedStart) startRangeInput.value = sharedStart;
|
| 1273 |
if (sharedEnd) endRangeInput.value = sharedEnd;
|
| 1274 |
-
if (sharedRandom) {
|
| 1275 |
-
randomQuestionsCheckbox.checked = true;
|
| 1276 |
-
randomQuestionsCountInput.value = sharedRandom;
|
| 1277 |
-
randomQuestionsCountInput.disabled = false;
|
| 1278 |
-
}
|
| 1279 |
-
|
| 1280 |
-
if (isLocked) {
|
| 1281 |
-
settingsContainer.classList.add('opacity-50', 'pointer-events-none');
|
| 1282 |
-
teacherTools.classList.add('hidden');
|
| 1283 |
-
}
|
| 1284 |
|
| 1285 |
if (urlParams.has('data')) {
|
| 1286 |
history.replaceState(null, '', window.location.pathname);
|
|
@@ -1289,7 +1213,6 @@
|
|
| 1289 |
startRangeInput.max = words.length;
|
| 1290 |
endRangeInput.max = words.length;
|
| 1291 |
endRangeInput.placeholder = `到 ${words.length}`;
|
| 1292 |
-
updateRandomCountMax();
|
| 1293 |
showView('menu');
|
| 1294 |
};
|
| 1295 |
initializeApp();
|
|
|
|
| 96 |
<p id="highscore-display" class="text-lg font-bold text-amber-500">0</p>
|
| 97 |
</div>
|
| 98 |
</div>
|
| 99 |
+
<!-- 單字範圍選擇 -->
|
| 100 |
+
<div class="my-4 p-4 bg-gray-100 rounded-2xl flex items-center justify-center gap-4">
|
| 101 |
+
<label for="start-range" class="font-semibold text-gray-700">單字範圍:</label>
|
| 102 |
+
<input type="number" id="start-range" min="1" class="w-20 p-2 border border-gray-300 rounded-lg text-center" placeholder="從">
|
| 103 |
+
<span class="font-semibold text-gray-700">-</span>
|
| 104 |
+
<input type="number" id="end-range" min="1" class="w-20 p-2 border border-gray-300 rounded-lg text-center" placeholder="到">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
</div>
|
| 106 |
|
| 107 |
<!-- 主要功能 -->
|
|
|
|
| 134 |
</div>
|
| 135 |
|
| 136 |
<!-- 教師工具 -->
|
| 137 |
+
<div class="mt-8 pt-4 border-t flex justify-end items-center gap-3">
|
| 138 |
<button id="share-game-btn" class="text-white font-semibold py-2 px-5 rounded-full transition-transform hover:scale-105 bg-violet-500 hover:bg-violet-600 text-sm">🔗 分享</button>
|
| 139 |
<button id="manage-words-btn" class="text-white font-semibold py-2 px-5 rounded-full transition-transform hover:scale-105 bg-gray-500 hover:bg-gray-600 text-sm">⚙️ 管理</button>
|
| 140 |
</div>
|
|
|
|
| 327 |
<div id="share-modal" class="hidden fixed inset-0 bg-gray-900 bg-opacity-75 flex items-center justify-center z-50 p-4">
|
| 328 |
<div class="bg-white p-8 rounded-2xl shadow-xl w-full max-w-lg">
|
| 329 |
<h3 class="text-2xl font-bold mb-4 text-gray-800">分享遊戲連結</h3>
|
| 330 |
+
<p class="text-gray-600 mb-6">將此連結傳送給學生,他們將使用您目前的單字列表和範圍設定。</p>
|
| 331 |
+
<div class="flex items-center gap-2">
|
| 332 |
+
<input id="share-link-input" type="text" readonly class="w-full p-3 border border-gray-300 rounded-lg bg-gray-100">
|
| 333 |
+
<button id="copy-link-btn" class="px-6 py-3 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 shrink-0">複製</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 334 |
</div>
|
| 335 |
+
<p id="copy-feedback" class="text-green-600 text-sm h-5 mt-2 text-center font-semibold"></p>
|
| 336 |
<div class="flex justify-end mt-4">
|
| 337 |
<button type="button" id="close-share-modal-btn" class="px-6 py-2 bg-gray-200 text-gray-800 rounded-lg hover:bg-gray-300 transition-colors">關閉</button>
|
| 338 |
</div>
|
| 339 |
</div>
|
| 340 |
</div>
|
| 341 |
|
|
|
|
| 342 |
<!-- 清除確認 Modal -->
|
| 343 |
<div id="confirm-clear-modal" class="hidden fixed inset-0 bg-gray-900 bg-opacity-75 flex items-center justify-center z-50 p-4">
|
| 344 |
<div class="bg-white p-8 rounded-2xl shadow-xl w-full max-w-sm">
|
|
|
|
| 363 |
</div>
|
| 364 |
</div>
|
| 365 |
|
|
|
|
|
|
|
|
|
|
| 366 |
<footer class="fixed bottom-4 right-4 text-xs text-gray-500 text-right z-50">
|
| 367 |
<p>遊戲設計者:新竹縣精華國中藍星宇</p>
|
| 368 |
<p>FB教育社群:<a href="https://www.facebook.com/groups/1554372228718393" target="_blank" rel="noopener noreferrer" class="text-blue-500 hover:underline">萬物皆數</a></p>
|
|
|
|
| 376 |
const learningMode = document.getElementById('learning-mode');
|
| 377 |
const modeSelectionSection = document.getElementById('mode-selection-section');
|
| 378 |
const highscoreDisplay = document.getElementById('highscore-display');
|
|
|
|
| 379 |
const startRangeInput = document.getElementById('start-range');
|
| 380 |
const endRangeInput = document.getElementById('end-range');
|
|
|
|
| 381 |
const manageWordsBtn = document.getElementById('manage-words-btn');
|
| 382 |
const shareGameBtn = document.getElementById('share-game-btn');
|
|
|
|
|
|
|
| 383 |
|
| 384 |
// 單字管理介面
|
| 385 |
const wordManagementView = document.getElementById('word-management-view');
|
|
|
|
| 449 |
const saveEditBtn = document.getElementById('save-edit-btn');
|
| 450 |
const cancelEditBtn = document.getElementById('cancel-edit-btn');
|
| 451 |
const shareModal = document.getElementById('share-modal');
|
|
|
|
|
|
|
|
|
|
| 452 |
const shareLinkInput = document.getElementById('share-link-input');
|
| 453 |
const copyLinkBtn = document.getElementById('copy-link-btn');
|
| 454 |
const copyFeedback = document.getElementById('copy-feedback');
|
|
|
|
| 461 |
const cancelDeleteBtn = document.getElementById('cancel-delete-btn');
|
| 462 |
const confirmDeleteBtn = document.getElementById('confirm-delete-btn');
|
| 463 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 464 |
|
| 465 |
// 應用程式狀態
|
| 466 |
let words = [];
|
|
|
|
| 706 |
const start = parseInt(startRangeInput.value, 10);
|
| 707 |
const end = parseInt(endRangeInput.value, 10);
|
| 708 |
|
| 709 |
+
// 學習時,使用新增順序
|
|
|
|
| 710 |
if (!isNaN(start) && !isNaN(end) && start > 0 && end >= start && end <= words.length) {
|
| 711 |
+
wordsForCurrentMode = words.slice(start - 1, end);
|
| 712 |
} else {
|
| 713 |
+
wordsForCurrentMode = [...words];
|
| 714 |
}
|
| 715 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 716 |
if (wordsForCurrentMode.length === 0) {
|
| 717 |
+
alert("選定的範圍內沒有單字,請重新���擇。");
|
| 718 |
return;
|
| 719 |
}
|
| 720 |
|
|
|
|
| 753 |
|
| 754 |
const wordIndex = words.findIndex(w => w.english === card.english && w.chinese === card.chinese);
|
| 755 |
|
| 756 |
+
// --- Start: New, separated logic for Review Mode ---
|
|
|
|
|
|
|
| 757 |
if (isReview) {
|
| 758 |
const isFlipped = flashcardContainer.classList.contains('flipped');
|
| 759 |
+
let correctAnswer, answerLang;
|
| 760 |
+
|
| 761 |
+
if(isFlipped) { // Chinese side is showing, user should type English
|
| 762 |
correctAnswer = card.english.split('(')[0].trim();
|
| 763 |
answerLang = 'en';
|
| 764 |
+
} else { // English side is showing, user should type Chinese
|
| 765 |
correctAnswer = card.chinese.split('(')[0].trim();
|
| 766 |
answerLang = 'zh';
|
| 767 |
}
|
| 768 |
+
|
| 769 |
+
let isCorrect;
|
| 770 |
+
if (answerLang === 'en') {
|
| 771 |
+
const normalize = (str) => str.toLowerCase().replace(/[.,/#!$%^&*;:{}=\-_`~()]/g, "").trim();
|
| 772 |
+
isCorrect = normalize(userAnswer) === normalize(correctAnswer);
|
| 773 |
+
} else if (answerLang === 'zh') {
|
| 774 |
+
const possibleAnswers = correctAnswer.split(/[;;]/).map(a => a.trim());
|
| 775 |
+
isCorrect = possibleAnswers.some(ans => userAnswer.trim().includes(ans) && ans !== '');
|
| 776 |
+
}
|
| 777 |
+
|
| 778 |
+
if (isCorrect) {
|
| 779 |
+
feedbackDisplay.textContent = '答對了!';
|
| 780 |
+
feedbackDisplay.classList.remove('text-red-500');
|
| 781 |
+
feedbackDisplay.classList.add('text-green-600');
|
| 782 |
+
triggerConfetti();
|
| 783 |
+
} else {
|
| 784 |
+
feedbackDisplay.textContent = '答錯了!';
|
| 785 |
+
feedbackDisplay.classList.add('text-red-500');
|
| 786 |
+
answerInput.classList.add('shake');
|
| 787 |
+
setTimeout(() => { answerInput.classList.remove('shake'); }, 820);
|
| 788 |
}
|
| 789 |
+
|
| 790 |
+
// Flip the card to show the answer, regardless of correctness
|
| 791 |
+
if (isFlipped) {
|
| 792 |
+
flashcardContainer.classList.remove('flipped');
|
| 793 |
+
} else {
|
| 794 |
+
flashcardContainer.classList.add('flipped');
|
| 795 |
+
}
|
| 796 |
+
|
| 797 |
+
// Update placeholder for the new card face and clear input
|
| 798 |
+
setTimeout(() => {
|
| 799 |
+
const currentlyFlipped = flashcardContainer.classList.contains('flipped');
|
| 800 |
+
answerInput.placeholder = currentlyFlipped ? "請輸入英文答案..." : "請輸入中文答案...";
|
| 801 |
+
answerInput.value = '';
|
| 802 |
+
answerInput.focus();
|
| 803 |
+
}, 600); // Wait for flip to finish
|
| 804 |
+
|
| 805 |
+
return; // End execution for review mode
|
| 806 |
+
}
|
| 807 |
+
// --- End: Logic for Review Mode ---
|
| 808 |
+
|
| 809 |
+
// --- Logic for all other quiz modes ---
|
| 810 |
+
let correctAnswer;
|
| 811 |
+
let answerLang;
|
| 812 |
+
let effectiveMode = currentMode;
|
| 813 |
+
if (isSpeed) effectiveMode = currentSpeedQuestionType;
|
| 814 |
+
if (currentMode === 'hard') effectiveMode = 'zh-en';
|
| 815 |
+
|
| 816 |
+
switch (effectiveMode) {
|
| 817 |
+
case 'zh-en': case 'listen':
|
| 818 |
+
correctAnswer = card.english.split('(')[0].trim();
|
| 819 |
+
answerLang = 'en';
|
| 820 |
+
break;
|
| 821 |
+
case 'en-zh':
|
| 822 |
+
correctAnswer = card.chinese.split('(')[0].trim();
|
| 823 |
+
answerLang = 'zh';
|
| 824 |
+
break;
|
| 825 |
}
|
| 826 |
|
| 827 |
let isCorrect;
|
|
|
|
| 834 |
}
|
| 835 |
|
| 836 |
if (isCorrect) {
|
|
|
|
| 837 |
answerInput.disabled = true;
|
| 838 |
submitBtn.disabled = true;
|
| 839 |
feedbackDisplay.textContent = '答對了!';
|
| 840 |
feedbackDisplay.classList.remove('text-red-500');
|
| 841 |
feedbackDisplay.classList.add('text-green-600');
|
| 842 |
triggerConfetti();
|
| 843 |
+
flashcardContainer.classList.add('flipped');
|
|
|
|
|
|
|
|
|
|
| 844 |
|
| 845 |
if (wordIndex !== -1) words[wordIndex].proficiency = (words[wordIndex].proficiency || 0) + 1;
|
| 846 |
|
|
|
|
| 848 |
currentScore++;
|
| 849 |
scoreDisplay.textContent = `得分: ${currentScore}`;
|
| 850 |
setTimeout(startSpeedCard, 500);
|
| 851 |
+
} else {
|
| 852 |
quizQueue.shift();
|
| 853 |
|
| 854 |
setTimeout(() => {
|
|
|
|
| 866 |
}
|
| 867 |
}, 1500);
|
| 868 |
}
|
| 869 |
+
} else { // Incorrect answer for quiz modes
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 870 |
quizIncorrectCount++;
|
| 871 |
updateHintButtonVisibility();
|
| 872 |
answerInput.classList.add('shake');
|
|
|
|
| 880 |
feedbackDisplay.textContent = '答錯了,再試一次!';
|
| 881 |
feedbackDisplay.classList.add('text-red-500');
|
| 882 |
setTimeout(() => { answerInput.classList.remove('shake'); feedbackDisplay.textContent = ''; }, 1000);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 883 |
} else { // Other quiz modes
|
| 884 |
answerInput.disabled = true;
|
| 885 |
submitBtn.disabled = true;
|
|
|
|
| 1012 |
nextBtn.addEventListener('click', () => { if (currentCardIndex < wordsForCurrentMode.length - 1) { currentCardIndex++; displayCard(); }});
|
| 1013 |
quizForm.addEventListener('submit', (e) => { e.preventDefault(); checkAnswer(); });
|
| 1014 |
speakBtn.addEventListener('click', (e) => { e.stopPropagation(); const word = getCurrentWordToSpeak(); if(word) speakWord(word, 0.75); });
|
| 1015 |
+
speakSlowBtn.addEventListener('click', (e) => { e.stopPropagation(); const word = getCurrentWordToSpeak(); if(word) speakWord(word, 0.2); });
|
| 1016 |
hintBtn.addEventListener('click', () => {
|
| 1017 |
const card = currentMode === 'speed' ? currentSpeedCard : quizQueue[0];
|
| 1018 |
if (!card) return;
|
|
|
|
| 1065 |
alert('冊次和課次不能為空!');
|
| 1066 |
}
|
| 1067 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1068 |
|
| 1069 |
+
shareGameBtn.addEventListener('click', () => {
|
| 1070 |
+
try {
|
| 1071 |
const wordsString = JSON.stringify(words);
|
| 1072 |
+
// 處理 UTF-8 字元以避免 btoa 錯誤
|
| 1073 |
const base64Words = btoa(unescape(encodeURIComponent(wordsString)));
|
| 1074 |
const start = startRangeInput.value || '';
|
| 1075 |
const end = endRangeInput.value || '';
|
|
|
|
| 1076 |
|
| 1077 |
const baseUrl = window.location.href.split('?')[0];
|
| 1078 |
const url = new URL(baseUrl);
|
|
|
|
| 1080 |
url.searchParams.set('data', base64Words);
|
| 1081 |
if (start) url.searchParams.set('start', start);
|
| 1082 |
if (end) url.searchParams.set('end', end);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1083 |
|
| 1084 |
shareLinkInput.value = url.toString();
|
| 1085 |
+
copyFeedback.textContent = '';
|
| 1086 |
+
shareModal.classList.remove('hidden');
|
| 1087 |
|
| 1088 |
} catch (error) {
|
| 1089 |
console.error("產生分享連結時發生錯誤:", error);
|
|
|
|
| 1091 |
}
|
| 1092 |
});
|
| 1093 |
|
|
|
|
| 1094 |
copyLinkBtn.addEventListener('click', () => {
|
| 1095 |
shareLinkInput.select();
|
| 1096 |
shareLinkInput.setSelectionRange(0, 99999); // 兼容移動設備
|
|
|
|
| 1152 |
confirmDeleteModal.classList.add('hidden');
|
| 1153 |
indexToDelete = -1;
|
| 1154 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1155 |
|
| 1156 |
|
| 1157 |
// --- 應用程式初始化 ---
|
|
|
|
| 1203 |
|
| 1204 |
const sharedStart = urlParams.get('start');
|
| 1205 |
const sharedEnd = urlParams.get('end');
|
|
|
|
|
|
|
|
|
|
| 1206 |
if (sharedStart) startRangeInput.value = sharedStart;
|
| 1207 |
if (sharedEnd) endRangeInput.value = sharedEnd;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1208 |
|
| 1209 |
if (urlParams.has('data')) {
|
| 1210 |
history.replaceState(null, '', window.location.pathname);
|
|
|
|
| 1213 |
startRangeInput.max = words.length;
|
| 1214 |
endRangeInput.max = words.length;
|
| 1215 |
endRangeInput.placeholder = `到 ${words.length}`;
|
|
|
|
| 1216 |
showView('menu');
|
| 1217 |
};
|
| 1218 |
initializeApp();
|