Spaces:
Running
Running
Update index.html
Browse files- index.html +162 -85
index.html
CHANGED
|
@@ -96,12 +96,20 @@
|
|
| 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
|
| 101 |
-
<
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
</div>
|
| 106 |
|
| 107 |
<!-- 主要功能 -->
|
|
@@ -134,7 +142,7 @@
|
|
| 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,18 +335,34 @@
|
|
| 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"
|
| 331 |
-
<div class="
|
| 332 |
-
<
|
| 333 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 334 |
</div>
|
| 335 |
-
|
| 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,6 +387,9 @@
|
|
| 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,10 +403,14 @@
|
|
| 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,6 +480,9 @@
|
|
| 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,6 +495,10 @@
|
|
| 461 |
const cancelDeleteBtn = document.getElementById('cancel-delete-btn');
|
| 462 |
const confirmDeleteBtn = document.getElementById('confirm-delete-btn');
|
| 463 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 464 |
|
| 465 |
// 應用程式狀態
|
| 466 |
let words = [];
|
|
@@ -706,15 +744,32 @@
|
|
| 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 |
-
|
| 712 |
} else {
|
| 713 |
-
|
| 714 |
}
|
| 715 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 716 |
if (wordsForCurrentMode.length === 0) {
|
| 717 |
-
alert("
|
| 718 |
return;
|
| 719 |
}
|
| 720 |
|
|
@@ -753,75 +808,32 @@
|
|
| 753 |
|
| 754 |
const wordIndex = words.findIndex(w => w.english === card.english && w.chinese === card.chinese);
|
| 755 |
|
| 756 |
-
|
|
|
|
|
|
|
| 757 |
if (isReview) {
|
| 758 |
const isFlipped = flashcardContainer.classList.contains('flipped');
|
| 759 |
-
|
| 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
|
| 765 |
correctAnswer = card.chinese.split('(')[0].trim();
|
| 766 |
answerLang = 'zh';
|
| 767 |
}
|
| 768 |
-
|
| 769 |
-
let
|
| 770 |
-
if (
|
| 771 |
-
|
| 772 |
-
|
| 773 |
-
|
| 774 |
-
|
| 775 |
-
|
| 776 |
-
|
| 777 |
-
|
| 778 |
-
|
| 779 |
-
|
| 780 |
-
|
| 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,13 +846,17 @@
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
| 844 |
|
| 845 |
if (wordIndex !== -1) words[wordIndex].proficiency = (words[wordIndex].proficiency || 0) + 1;
|
| 846 |
|
|
@@ -848,7 +864,7 @@
|
|
| 848 |
currentScore++;
|
| 849 |
scoreDisplay.textContent = `得分: ${currentScore}`;
|
| 850 |
setTimeout(startSpeedCard, 500);
|
| 851 |
-
} else {
|
| 852 |
quizQueue.shift();
|
| 853 |
|
| 854 |
setTimeout(() => {
|
|
@@ -866,7 +882,13 @@
|
|
| 866 |
}
|
| 867 |
}, 1500);
|
| 868 |
}
|
| 869 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 870 |
quizIncorrectCount++;
|
| 871 |
updateHintButtonVisibility();
|
| 872 |
answerInput.classList.add('shake');
|
|
@@ -880,6 +902,13 @@
|
|
| 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;
|
|
@@ -1065,14 +1094,21 @@
|
|
| 1065 |
alert('冊次和課次不能為空!');
|
| 1066 |
}
|
| 1067 |
});
|
| 1068 |
-
|
| 1069 |
shareGameBtn.addEventListener('click', () => {
|
| 1070 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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,10 +1116,16 @@
|
|
| 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 |
-
|
| 1086 |
-
shareModal.classList.remove('hidden');
|
| 1087 |
|
| 1088 |
} catch (error) {
|
| 1089 |
console.error("產生分享連結時發生錯誤:", error);
|
|
@@ -1091,6 +1133,7 @@
|
|
| 1091 |
}
|
| 1092 |
});
|
| 1093 |
|
|
|
|
| 1094 |
copyLinkBtn.addEventListener('click', () => {
|
| 1095 |
shareLinkInput.select();
|
| 1096 |
shareLinkInput.setSelectionRange(0, 99999); // 兼容移動設備
|
|
@@ -1152,6 +1195,26 @@
|
|
| 1152 |
confirmDeleteModal.classList.add('hidden');
|
| 1153 |
indexToDelete = -1;
|
| 1154 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1155 |
|
| 1156 |
|
| 1157 |
// --- 應用程式初始化 ---
|
|
@@ -1203,8 +1266,21 @@
|
|
| 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,6 +1289,7 @@
|
|
| 1213 |
startRangeInput.max = words.length;
|
| 1214 |
endRangeInput.max = words.length;
|
| 1215 |
endRangeInput.placeholder = `到 ${words.length}`;
|
|
|
|
| 1216 |
showView('menu');
|
| 1217 |
};
|
| 1218 |
initializeApp();
|
|
|
|
| 96 |
<p id="highscore-display" class="text-lg font-bold text-amber-500">0</p>
|
| 97 |
</div>
|
| 98 |
</div>
|
| 99 |
+
<!-- 單字範圍與隨機出題選擇 -->
|
| 100 |
+
<div id="settings-container" class="my-4 p-4 bg-gray-100 rounded-2xl space-y-3">
|
| 101 |
+
<div class="flex items-center justify-center gap-4">
|
| 102 |
+
<label for="start-range" class="font-semibold text-gray-700">單字範圍:</label>
|
| 103 |
+
<input type="number" id="start-range" min="1" class="w-20 p-2 border border-gray-300 rounded-lg text-center" placeholder="從">
|
| 104 |
+
<span class="font-semibold text-gray-700">-</span>
|
| 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 |
</div>
|
| 143 |
|
| 144 |
<!-- 教師工具 -->
|
| 145 |
+
<div id="teacher-tools" class="mt-8 pt-4 border-t flex justify-end items-center gap-3">
|
| 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 |
<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">請選擇分享選項,然後產生連結。</p>
|
| 339 |
+
<div class="my-4">
|
| 340 |
+
<label class="flex items-center text-lg">
|
| 341 |
+
<input type="checkbox" id="lock-settings-checkbox" class="h-5 w-5 rounded text-indigo-600 focus:ring-indigo-500 border-gray-300">
|
| 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 |
</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 |
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 |
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 |
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 |
const start = parseInt(startRangeInput.value, 10);
|
| 745 |
const end = parseInt(endRangeInput.value, 10);
|
| 746 |
|
| 747 |
+
let wordPool;
|
| 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 |
+
wordPool = words.slice(start - 1, end);
|
| 751 |
} else {
|
| 752 |
+
wordPool = [...words];
|
| 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 |
|
| 809 |
const wordIndex = words.findIndex(w => w.english === card.english && w.chinese === card.chinese);
|
| 810 |
|
| 811 |
+
let correctAnswer;
|
| 812 |
+
let answerLang;
|
| 813 |
+
|
| 814 |
if (isReview) {
|
| 815 |
const isFlipped = flashcardContainer.classList.contains('flipped');
|
| 816 |
+
if(isFlipped) { // Chinese side showing, user should type English
|
|
|
|
|
|
|
| 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 |
+
} else {
|
| 824 |
+
let effectiveMode = currentMode;
|
| 825 |
+
if (isSpeed) effectiveMode = currentSpeedQuestionType;
|
| 826 |
+
if (currentMode === 'hard') effectiveMode = 'zh-en';
|
| 827 |
+
switch (effectiveMode) {
|
| 828 |
+
case 'zh-en': case 'listen':
|
| 829 |
+
correctAnswer = card.english.split('(')[0].trim();
|
| 830 |
+
answerLang = 'en';
|
| 831 |
+
break;
|
| 832 |
+
case 'en-zh':
|
| 833 |
+
correctAnswer = card.chinese.split('(')[0].trim();
|
| 834 |
+
answerLang = 'zh';
|
| 835 |
+
break;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 836 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 837 |
}
|
| 838 |
|
| 839 |
let isCorrect;
|
|
|
|
| 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 |
currentScore++;
|
| 865 |
scoreDisplay.textContent = `得分: ${currentScore}`;
|
| 866 |
setTimeout(startSpeedCard, 500);
|
| 867 |
+
} else if (!isReview) {
|
| 868 |
quizQueue.shift();
|
| 869 |
|
| 870 |
setTimeout(() => {
|
|
|
|
| 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 |
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;
|
|
|
|
| 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 |
+
generateLinkBtn.addEventListener('click', () => {
|
| 1106 |
+
try {
|
| 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 |
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 |
+
shareResultContainer.classList.remove('hidden');
|
|
|
|
| 1129 |
|
| 1130 |
} catch (error) {
|
| 1131 |
console.error("產生分享連結時發生錯誤:", error);
|
|
|
|
| 1133 |
}
|
| 1134 |
});
|
| 1135 |
|
| 1136 |
+
|
| 1137 |
copyLinkBtn.addEventListener('click', () => {
|
| 1138 |
shareLinkInput.select();
|
| 1139 |
shareLinkInput.setSelectionRange(0, 99999); // 兼容移動設備
|
|
|
|
| 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 |
|
| 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 |
startRangeInput.max = words.length;
|
| 1290 |
endRangeInput.max = words.length;
|
| 1291 |
endRangeInput.placeholder = `到 ${words.length}`;
|
| 1292 |
+
updateRandomCountMax();
|
| 1293 |
showView('menu');
|
| 1294 |
};
|
| 1295 |
initializeApp();
|