Spaces:
Running
Running
Update index.html
Browse files- index.html +141 -35
index.html
CHANGED
|
@@ -351,6 +351,18 @@
|
|
| 351 |
</div>
|
| 352 |
</div>
|
| 353 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 354 |
<footer class="fixed bottom-4 right-4 text-xs text-gray-500 text-right z-50">
|
| 355 |
<p>遊戲設計者:新竹縣精華國中藍星宇</p>
|
| 356 |
<p>FB教育社群:<a href="https://www.facebook.com/groups/1554372228718393" target="_blank" rel="noopener noreferrer" class="text-blue-500 hover:underline">萬物皆數</a></p>
|
|
@@ -444,6 +456,11 @@
|
|
| 444 |
const confirmClearModal = document.getElementById('confirm-clear-modal');
|
| 445 |
const cancelClearBtn = document.getElementById('cancel-clear-btn');
|
| 446 |
const confirmClearBtn = document.getElementById('confirm-clear-btn');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 447 |
|
| 448 |
// 應用程式狀態
|
| 449 |
let words = [];
|
|
@@ -455,6 +472,7 @@
|
|
| 455 |
let completionStatus = {};
|
| 456 |
let bookTitle = '';
|
| 457 |
let lessonTitle = '';
|
|
|
|
| 458 |
const MANAGE_PASSWORD = 'Ghjh';
|
| 459 |
let highScore = 0;
|
| 460 |
let currentScore = 0;
|
|
@@ -528,13 +546,9 @@
|
|
| 528 |
editChineseInput.value = words[index].chinese;
|
| 529 |
editWordModal.classList.remove('hidden');
|
| 530 |
} else if (button.classList.contains('delete-btn')) {
|
| 531 |
-
|
| 532 |
-
|
| 533 |
-
|
| 534 |
-
words.splice(index, 1);
|
| 535 |
-
saveWordsToStorage();
|
| 536 |
-
renderWordList();
|
| 537 |
-
}, 300);
|
| 538 |
}
|
| 539 |
});
|
| 540 |
|
|
@@ -680,7 +694,7 @@
|
|
| 680 |
|
| 681 |
quizContainer.classList.remove('hidden');
|
| 682 |
reviewNavContainer.classList.toggle('hidden', !isReview);
|
| 683 |
-
progressBarContainer.classList.toggle('hidden',
|
| 684 |
speedHud.classList.toggle('hidden', !isSpeed);
|
| 685 |
hudPlaceholder.classList.toggle('hidden', isSpeed);
|
| 686 |
|
|
@@ -721,54 +735,113 @@
|
|
| 721 |
};
|
| 722 |
|
| 723 |
const checkAnswer = () => {
|
| 724 |
-
|
| 725 |
-
|
| 726 |
-
|
| 727 |
const isSpeed = currentMode === 'speed';
|
| 728 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
| 729 |
if (!card) return;
|
|
|
|
| 730 |
const wordIndex = words.findIndex(w => w.english === card.english && w.chinese === card.chinese);
|
|
|
|
| 731 |
let correctAnswer;
|
| 732 |
-
|
| 733 |
-
|
| 734 |
-
|
| 735 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 736 |
}
|
| 737 |
-
const isCorrect = userAnswer.toLowerCase() === correctAnswer.toLowerCase();
|
| 738 |
|
| 739 |
if (isCorrect) {
|
| 740 |
-
answerInput.disabled = true;
|
| 741 |
-
|
| 742 |
-
|
| 743 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 744 |
if (isSpeed) {
|
| 745 |
-
currentScore++;
|
|
|
|
| 746 |
setTimeout(startSpeedCard, 500);
|
| 747 |
} else {
|
| 748 |
-
quizQueue.shift();
|
|
|
|
| 749 |
setTimeout(() => {
|
| 750 |
-
if (
|
| 751 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 752 |
feedbackDisplay.textContent = '恭喜你,全部完成了!';
|
| 753 |
if (currentMode !== 'hard') {
|
| 754 |
-
completionStatus[currentMode] = true;
|
|
|
|
|
|
|
| 755 |
}
|
| 756 |
-
answerInput.disabled = true;
|
|
|
|
| 757 |
}
|
| 758 |
}, 1500);
|
| 759 |
}
|
| 760 |
-
} else {
|
| 761 |
-
quizIncorrectCount++;
|
|
|
|
| 762 |
answerInput.classList.add('shake');
|
| 763 |
-
|
| 764 |
-
|
|
|
|
|
|
|
| 765 |
}
|
|
|
|
| 766 |
if (isSpeed) {
|
| 767 |
-
feedbackDisplay.textContent = '答錯了,再試一次!';
|
|
|
|
| 768 |
setTimeout(() => { answerInput.classList.remove('shake'); feedbackDisplay.textContent = ''; }, 1000);
|
| 769 |
-
} else {
|
| 770 |
-
|
| 771 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 772 |
wrongAnswerFeedback.classList.remove('hidden');
|
| 773 |
setTimeout(() => { answerInput.classList.remove('shake'); }, 820);
|
| 774 |
}
|
|
@@ -776,6 +849,7 @@
|
|
| 776 |
if (!isSpeed) saveWordsToStorage();
|
| 777 |
};
|
| 778 |
|
|
|
|
| 779 |
const updateHintButtonVisibility = () => {
|
| 780 |
const isQuiz = currentMode !== 'review' && currentMode !== 'speed';
|
| 781 |
const shouldShow = isQuiz && quizIncorrectCount >= 2;
|
|
@@ -994,6 +1068,38 @@
|
|
| 994 |
confirmClearModal.classList.add('hidden');
|
| 995 |
});
|
| 996 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 997 |
// --- 應用程式初始化 ---
|
| 998 |
const initializeApp = () => {
|
| 999 |
const urlParams = new URLSearchParams(window.location.search);
|
|
|
|
| 351 |
</div>
|
| 352 |
</div>
|
| 353 |
|
| 354 |
+
<!-- 單字刪除確認 Modal -->
|
| 355 |
+
<div id="confirm-delete-modal" class="hidden fixed inset-0 bg-gray-900 bg-opacity-75 flex items-center justify-center z-50 p-4">
|
| 356 |
+
<div class="bg-white p-8 rounded-2xl shadow-xl w-full max-w-sm">
|
| 357 |
+
<h3 class="text-2xl font-bold mb-4 text-gray-800">確認刪除</h3>
|
| 358 |
+
<p class="text-gray-600 mb-6">您確定要刪除單字 "<span id="word-to-delete" class="font-bold"></span>" 嗎?此操作無法復原。</p>
|
| 359 |
+
<div class="flex justify-end gap-4 mt-6">
|
| 360 |
+
<button type="button" id="cancel-delete-btn" class="px-6 py-2 bg-gray-200 text-gray-800 rounded-lg hover:bg-gray-300 transition-colors">取消</button>
|
| 361 |
+
<button type="button" id="confirm-delete-btn" class="px-6 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors">確認刪除</button>
|
| 362 |
+
</div>
|
| 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>
|
|
|
|
| 456 |
const confirmClearModal = document.getElementById('confirm-clear-modal');
|
| 457 |
const cancelClearBtn = document.getElementById('cancel-clear-btn');
|
| 458 |
const confirmClearBtn = document.getElementById('confirm-clear-btn');
|
| 459 |
+
const confirmDeleteModal = document.getElementById('confirm-delete-modal');
|
| 460 |
+
const wordToDeleteSpan = document.getElementById('word-to-delete');
|
| 461 |
+
const cancelDeleteBtn = document.getElementById('cancel-delete-btn');
|
| 462 |
+
const confirmDeleteBtn = document.getElementById('confirm-delete-btn');
|
| 463 |
+
|
| 464 |
|
| 465 |
// 應用程式狀態
|
| 466 |
let words = [];
|
|
|
|
| 472 |
let completionStatus = {};
|
| 473 |
let bookTitle = '';
|
| 474 |
let lessonTitle = '';
|
| 475 |
+
let indexToDelete = -1;
|
| 476 |
const MANAGE_PASSWORD = 'Ghjh';
|
| 477 |
let highScore = 0;
|
| 478 |
let currentScore = 0;
|
|
|
|
| 546 |
editChineseInput.value = words[index].chinese;
|
| 547 |
editWordModal.classList.remove('hidden');
|
| 548 |
} else if (button.classList.contains('delete-btn')) {
|
| 549 |
+
indexToDelete = index;
|
| 550 |
+
wordToDeleteSpan.textContent = words[index].english;
|
| 551 |
+
confirmDeleteModal.classList.remove('hidden');
|
|
|
|
|
|
|
|
|
|
|
|
|
| 552 |
}
|
| 553 |
});
|
| 554 |
|
|
|
|
| 694 |
|
| 695 |
quizContainer.classList.remove('hidden');
|
| 696 |
reviewNavContainer.classList.toggle('hidden', !isReview);
|
| 697 |
+
progressBarContainer.classList.toggle('hidden', isReview || isSpeed);
|
| 698 |
speedHud.classList.toggle('hidden', !isSpeed);
|
| 699 |
hudPlaceholder.classList.toggle('hidden', isSpeed);
|
| 700 |
|
|
|
|
| 735 |
};
|
| 736 |
|
| 737 |
const checkAnswer = () => {
|
| 738 |
+
const userAnswer = answerInput.value.trim();
|
| 739 |
+
if (!userAnswer) return;
|
| 740 |
+
|
| 741 |
const isSpeed = currentMode === 'speed';
|
| 742 |
+
const isReview = currentMode === 'review';
|
| 743 |
+
|
| 744 |
+
if (isSpeed && timeLeft <= 0) return;
|
| 745 |
+
|
| 746 |
+
const card = isReview ? wordsForCurrentMode[currentCardIndex] : (isSpeed ? currentSpeedCard : quizQueue[0]);
|
| 747 |
if (!card) return;
|
| 748 |
+
|
| 749 |
const wordIndex = words.findIndex(w => w.english === card.english && w.chinese === card.chinese);
|
| 750 |
+
|
| 751 |
let correctAnswer;
|
| 752 |
+
let answerLang;
|
| 753 |
+
let effectiveMode = currentMode;
|
| 754 |
+
if (isSpeed) effectiveMode = currentSpeedQuestionType;
|
| 755 |
+
if (currentMode === 'hard') effectiveMode = 'zh-en';
|
| 756 |
+
|
| 757 |
+
switch (effectiveMode) {
|
| 758 |
+
case 'zh-en':
|
| 759 |
+
case 'listen':
|
| 760 |
+
correctAnswer = card.english.split('(')[0].trim();
|
| 761 |
+
answerLang = 'en';
|
| 762 |
+
break;
|
| 763 |
+
case 'en-zh':
|
| 764 |
+
case 'review':
|
| 765 |
+
correctAnswer = card.chinese.split('(')[0].trim();
|
| 766 |
+
answerLang = 'zh';
|
| 767 |
+
break;
|
| 768 |
+
}
|
| 769 |
+
|
| 770 |
+
let isCorrect;
|
| 771 |
+
if (answerLang === 'en') {
|
| 772 |
+
const normalize = (str) => str.toLowerCase().replace(/[.,/#!$%^&*;:{}=\-_`~()]/g, "").trim();
|
| 773 |
+
isCorrect = normalize(userAnswer) === normalize(correctAnswer);
|
| 774 |
+
} else if (answerLang === 'zh') {
|
| 775 |
+
const possibleAnswers = correctAnswer.split(/[;;]/).map(a => a.trim());
|
| 776 |
+
isCorrect = possibleAnswers.some(ans => userAnswer.trim().includes(ans) && ans !== '');
|
| 777 |
+
} else {
|
| 778 |
+
isCorrect = userAnswer.trim().toLowerCase() === correctAnswer.trim().toLowerCase();
|
| 779 |
}
|
|
|
|
| 780 |
|
| 781 |
if (isCorrect) {
|
| 782 |
+
answerInput.disabled = true;
|
| 783 |
+
submitBtn.disabled = true;
|
| 784 |
+
feedbackDisplay.textContent = '答對了!';
|
| 785 |
+
feedbackDisplay.classList.remove('text-red-500');
|
| 786 |
+
feedbackDisplay.classList.add('text-green-600');
|
| 787 |
+
triggerConfetti();
|
| 788 |
+
flashcardContainer.classList.add('flipped');
|
| 789 |
+
|
| 790 |
+
if (wordIndex !== -1) words[wordIndex].proficiency = (words[wordIndex].proficiency || 0) + 1;
|
| 791 |
+
|
| 792 |
if (isSpeed) {
|
| 793 |
+
currentScore++;
|
| 794 |
+
scoreDisplay.textContent = `得分: ${currentScore}`;
|
| 795 |
setTimeout(startSpeedCard, 500);
|
| 796 |
} else {
|
| 797 |
+
if (!isReview) quizQueue.shift();
|
| 798 |
+
|
| 799 |
setTimeout(() => {
|
| 800 |
+
if (isReview) {
|
| 801 |
+
if (currentCardIndex < wordsForCurrentMode.length - 1) {
|
| 802 |
+
currentCardIndex++;
|
| 803 |
+
displayCard();
|
| 804 |
+
} else {
|
| 805 |
+
feedbackDisplay.textContent = '恭喜你,全部複習完了!';
|
| 806 |
+
}
|
| 807 |
+
} else if (quizQueue.length > 0) {
|
| 808 |
+
displayCard();
|
| 809 |
+
} else {
|
| 810 |
feedbackDisplay.textContent = '恭喜你,全部完成了!';
|
| 811 |
if (currentMode !== 'hard') {
|
| 812 |
+
completionStatus[currentMode] = true;
|
| 813 |
+
localStorage.setItem('completionStatus', JSON.stringify(completionStatus));
|
| 814 |
+
updateCompletionUI();
|
| 815 |
}
|
| 816 |
+
answerInput.disabled = true;
|
| 817 |
+
submitBtn.disabled = true;
|
| 818 |
}
|
| 819 |
}, 1500);
|
| 820 |
}
|
| 821 |
+
} else { // Incorrect answer
|
| 822 |
+
quizIncorrectCount++;
|
| 823 |
+
updateHintButtonVisibility();
|
| 824 |
answerInput.classList.add('shake');
|
| 825 |
+
|
| 826 |
+
if (wordIndex !== -1) {
|
| 827 |
+
words[wordIndex].proficiency = 0;
|
| 828 |
+
words[wordIndex].incorrectCount = (words[wordIndex].incorrectCount || 0) + 1;
|
| 829 |
}
|
| 830 |
+
|
| 831 |
if (isSpeed) {
|
| 832 |
+
feedbackDisplay.textContent = '答錯了,再試一次!';
|
| 833 |
+
feedbackDisplay.classList.add('text-red-500');
|
| 834 |
setTimeout(() => { answerInput.classList.remove('shake'); feedbackDisplay.textContent = ''; }, 1000);
|
| 835 |
+
} else if (isReview) {
|
| 836 |
+
feedbackDisplay.textContent = '答錯了!';
|
| 837 |
+
feedbackDisplay.classList.add('text-red-500');
|
| 838 |
+
flashcardContainer.classList.add('flipped');
|
| 839 |
+
setTimeout(() => { answerInput.classList.remove('shake'); }, 820);
|
| 840 |
+
} else { // Other quiz modes
|
| 841 |
+
answerInput.disabled = true;
|
| 842 |
+
submitBtn.disabled = true;
|
| 843 |
+
flashcardContainer.classList.add('flipped');
|
| 844 |
+
quizContainer.classList.add('hidden');
|
| 845 |
wrongAnswerFeedback.classList.remove('hidden');
|
| 846 |
setTimeout(() => { answerInput.classList.remove('shake'); }, 820);
|
| 847 |
}
|
|
|
|
| 849 |
if (!isSpeed) saveWordsToStorage();
|
| 850 |
};
|
| 851 |
|
| 852 |
+
|
| 853 |
const updateHintButtonVisibility = () => {
|
| 854 |
const isQuiz = currentMode !== 'review' && currentMode !== 'speed';
|
| 855 |
const shouldShow = isQuiz && quizIncorrectCount >= 2;
|
|
|
|
| 1068 |
confirmClearModal.classList.add('hidden');
|
| 1069 |
});
|
| 1070 |
|
| 1071 |
+
cancelDeleteBtn.addEventListener('click', () => {
|
| 1072 |
+
confirmDeleteModal.classList.add('hidden');
|
| 1073 |
+
indexToDelete = -1;
|
| 1074 |
+
});
|
| 1075 |
+
|
| 1076 |
+
confirmDeleteBtn.addEventListener('click', () => {
|
| 1077 |
+
if (indexToDelete > -1) {
|
| 1078 |
+
const allDeleteButtons = wordListContainer.querySelectorAll('.delete-btn');
|
| 1079 |
+
let itemElement = null;
|
| 1080 |
+
allDeleteButtons.forEach(btn => {
|
| 1081 |
+
if(parseInt(btn.dataset.index, 10) === indexToDelete) {
|
| 1082 |
+
itemElement = btn.closest('.list-item');
|
| 1083 |
+
}
|
| 1084 |
+
});
|
| 1085 |
+
|
| 1086 |
+
if (itemElement) {
|
| 1087 |
+
itemElement.classList.add('removing');
|
| 1088 |
+
}
|
| 1089 |
+
|
| 1090 |
+
setTimeout(() => {
|
| 1091 |
+
if (indexToDelete > -1) {
|
| 1092 |
+
words.splice(indexToDelete, 1);
|
| 1093 |
+
saveWordsToStorage();
|
| 1094 |
+
renderWordList();
|
| 1095 |
+
}
|
| 1096 |
+
}, 300);
|
| 1097 |
+
}
|
| 1098 |
+
confirmDeleteModal.classList.add('hidden');
|
| 1099 |
+
indexToDelete = -1;
|
| 1100 |
+
});
|
| 1101 |
+
|
| 1102 |
+
|
| 1103 |
// --- 應用程式初始化 ---
|
| 1104 |
const initializeApp = () => {
|
| 1105 |
const urlParams = new URLSearchParams(window.location.search);
|