Lashtw commited on
Commit
853b09a
·
verified ·
1 Parent(s): b924097

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +163 -47
index.html CHANGED
@@ -151,6 +151,7 @@
151
 
152
  <!-- 教師工具 -->
153
  <div id="teacher-tools" class="mt-8 pt-4 border-t flex justify-end items-center gap-3">
 
154
  <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>
155
  <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>
156
  </div>
@@ -289,6 +290,12 @@
289
  </button>
290
  </div>
291
 
 
 
 
 
 
 
292
  <!-- 複習模式導覽 -->
293
  <div id="review-nav-container" class="flex items-center justify-between w-full max-w-md mt-10 space-x-4">
294
  <button id="prev-btn" class="nav-btn">
@@ -371,12 +378,46 @@
371
  </div>
372
  </div>
373
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
374
 
375
  <!-- 清除確認 Modal -->
376
  <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">
377
  <div class="bg-white p-8 rounded-2xl shadow-xl w-full max-w-sm">
378
  <h3 class="text-2xl font-bold mb-4 text-gray-800">確認操作</h3>
379
- <p class="text-gray-600 mb-6">您確定要清除所有單字嗎?此操作無法復原。</p>
380
  <div class="flex justify-end gap-4 mt-6">
381
  <button type="button" id="cancel-clear-btn" class="px-6 py-2 bg-gray-200 text-gray-800 rounded-lg hover:bg-gray-300 transition-colors">取消</button>
382
  <button type="button" id="confirm-clear-btn" class="px-6 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors">確認清除</button>
@@ -418,6 +459,7 @@
418
  const teacherTools = document.getElementById('teacher-tools');
419
  const manageWordsBtn = document.getElementById('manage-words-btn');
420
  const shareGameBtn = document.getElementById('share-game-btn');
 
421
  const randomQuestionsCheckbox = document.getElementById('random-questions-checkbox');
422
  const randomQuestionsCountInput = document.getElementById('random-questions-count');
423
 
@@ -459,6 +501,7 @@
459
  const hintSection = document.getElementById('hint-section');
460
  const hintBtn = document.getElementById('hint-btn');
461
  const hintDisplay = document.getElementById('hint-display');
 
462
 
463
  // 複習模式導覽
464
  const reviewNavContainer = document.getElementById('review-nav-container');
@@ -475,7 +518,7 @@
475
  const finalScore = document.getElementById('final-score');
476
  const newHighscoreMsg = document.getElementById('new-highscore-msg');
477
  const playAgainBtn = document.getElementById('play-again-btn');
478
-
479
  // Modals
480
  const passwordModal = document.getElementById('password-modal');
481
  const passwordForm = document.getElementById('password-form');
@@ -497,6 +540,11 @@
497
  const copyFeedback = document.getElementById('copy-feedback');
498
  const closeShareModalBtn = document.getElementById('close-share-modal-btn');
499
  const qrcodeContainer = document.getElementById('qrcode-container');
 
 
 
 
 
500
  const confirmClearModal = document.getElementById('confirm-clear-modal');
501
  const cancelClearBtn = document.getElementById('cancel-clear-btn');
502
  const confirmClearBtn = document.getElementById('confirm-clear-btn');
@@ -514,6 +562,7 @@
514
  let words = [];
515
  let quizQueue = [];
516
  let wordsForCurrentMode = [];
 
517
  let quizIncorrectCount = 0;
518
  let currentCardIndex = 0;
519
  let currentMode = '';
@@ -528,6 +577,7 @@
528
  let timeLeft = 60;
529
  let currentSpeedCard = null;
530
  let currentSpeedQuestionType = '';
 
531
 
532
  const modeDetails = {
533
  'review': { title: '複習模式' }, 'zh-en': { title: '中翻英測驗' },
@@ -565,7 +615,6 @@
565
  // --- 單字管理功能 ---
566
  const renderWordList = () => {
567
  wordListContainer.innerHTML = '';
568
- // 移除字母排序,直接按照陣列順序渲染
569
  words.forEach((word, index) => {
570
  const item = document.createElement('div');
571
  item.className = 'list-item p-3 bg-gray-100 rounded-lg flex items-center justify-between';
@@ -683,6 +732,8 @@
683
  }
684
 
685
  flashcardContainer.style.cursor = 'default';
 
 
686
  [speakBtn, speakSlowBtn].forEach(btn => btn.classList.add('hidden'));
687
  feedbackDisplay.textContent = '';
688
  answerInput.value = '';
@@ -752,7 +803,6 @@
752
 
753
  if (flashcardContainer.classList.contains('flipped')) {
754
  flashcardContainer.classList.remove('flipped');
755
- // Wait for the flip animation (0.6s) to mostly complete before changing content
756
  setTimeout(updateCardContent, 600);
757
  } else {
758
  updateCardContent();
@@ -771,21 +821,19 @@
771
  progressBarContainer.classList.toggle('hidden', isReview || isSpeed);
772
  speedHud.classList.toggle('hidden', !isSpeed);
773
  hudPlaceholder.classList.toggle('hidden', isSpeed);
 
774
 
775
  const start = parseInt(startRangeInput.value, 10);
776
  const end = parseInt(endRangeInput.value, 10);
777
 
778
  let wordPool;
779
- // 1. First, determine the pool of words based on the range.
780
  if (!isNaN(start) && !isNaN(end) && start > 0 && end >= start && end <= words.length) {
781
  wordPool = words.slice(start - 1, end);
782
  } else {
783
  wordPool = [...words];
784
  }
785
 
786
- // 2. Then, determine the final word set for the mode.
787
  if (mode === 'review') {
788
- // For review mode, ALWAYS use the full pool, ignoring random settings.
789
  wordsForCurrentMode = wordPool;
790
  } else if (randomQuestionsCheckbox.checked) {
791
  const randomCount = parseInt(randomQuestionsCountInput.value, 10);
@@ -796,7 +844,6 @@
796
  return;
797
  }
798
  } else {
799
- // For other modes without random, use the entire pool.
800
  wordsForCurrentMode = wordPool;
801
  }
802
 
@@ -817,6 +864,10 @@
817
  quizQueue = shuffleArray([...wordsForCurrentMode]);
818
  }
819
 
 
 
 
 
820
  quizIncorrectCount = 0;
821
  updateHintButtonVisibility();
822
 
@@ -840,22 +891,14 @@
840
 
841
  const wordIndex = words.findIndex(w => w.english === card.english && w.chinese === card.chinese);
842
 
843
- let correctAnswer;
844
- let answerLang;
845
 
846
  if (isReview) {
847
  const isFlipped = flashcardContainer.classList.contains('flipped');
848
- if(isFlipped) { // Chinese side showing, user should type English
849
- correctAnswer = card.english.split('(')[0].trim();
850
- answerLang = 'en';
851
- } else { // English side showing, user should type Chinese
852
- correctAnswer = card.chinese.split('(')[0].trim();
853
- answerLang = 'zh';
854
- }
855
  } else {
856
- let effectiveMode = currentMode;
857
- if (isSpeed) effectiveMode = currentSpeedQuestionType;
858
- if (currentMode === 'hard') effectiveMode = 'zh-en';
859
  switch (effectiveMode) {
860
  case 'zh-en': case 'listen':
861
  correctAnswer = card.english.split('(')[0].trim();
@@ -903,19 +946,36 @@
903
  if (quizQueue.length > 0) {
904
  displayCard();
905
  } else {
906
- feedbackDisplay.textContent = '恭喜你,全部完成了!';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
907
  if (currentMode !== 'hard') {
908
  completionStatus[currentMode] = true;
909
  localStorage.setItem('completionStatus', JSON.stringify(completionStatus));
910
  updateCompletionUI();
911
  }
912
- answerInput.disabled = true;
913
- submitBtn.disabled = true;
 
 
 
914
  }
915
  }, 1500);
916
- }
917
-
918
- if (isReview) {
919
  answerInput.disabled = false;
920
  submitBtn.disabled = false;
921
  }
@@ -955,9 +1015,8 @@
955
 
956
 
957
  const updateHintButtonVisibility = () => {
958
- const isQuiz = currentMode !== 'review' && currentMode !== 'speed';
959
- const shouldShow = isQuiz && quizIncorrectCount >= 2;
960
- hintSection.classList.toggle('hidden', !shouldShow);
961
  };
962
 
963
  confirmWrongBtn.addEventListener('click', () => {
@@ -1052,6 +1111,42 @@
1052
  });
1053
  };
1054
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1055
  const generateQRCode = (text) => {
1056
  qrcodeContainer.innerHTML = '';
1057
  try {
@@ -1071,7 +1166,33 @@
1071
  document.querySelectorAll('.mode-btn[data-mode]').forEach(btn => {
1072
  btn.addEventListener('click', () => setupLearningView(btn.dataset.mode));
1073
  });
1074
- backToMenuBtn.addEventListener('click', () => { clearInterval(timerInterval); showView('menu'); });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1075
 
1076
  flashcardContainer.addEventListener('click', () => {
1077
  if (currentMode === 'review') {
@@ -1152,6 +1273,12 @@
1152
  shareModal.classList.remove('hidden');
1153
  });
1154
 
 
 
 
 
 
 
1155
  generateLinkBtn.addEventListener('click', async () => {
1156
  generateLinkBtn.textContent = '產生中...';
1157
  generateLinkBtn.disabled = true;
@@ -1179,21 +1306,17 @@
1179
 
1180
  const longUrl = url.toString();
1181
 
1182
- // 使用TinyURL API縮短網址
1183
  const response = await fetch(`https://tinyurl.com/api-create.php?url=${encodeURIComponent(longUrl)}`);
1184
  if (response.ok) {
1185
  const shortUrl = await response.text();
1186
  shareLinkInput.value = shortUrl;
1187
  generateQRCode(shortUrl);
1188
  } else {
1189
- // 如果API失敗,則退回使用長網址
1190
  shareLinkInput.value = longUrl;
1191
  generateQRCode(longUrl);
1192
  copyFeedback.textContent = '縮短網址失敗,已產生原始連結。';
1193
  }
1194
-
1195
  shareResultContainer.classList.remove('hidden');
1196
-
1197
  } catch (error) {
1198
  console.error("產生分享連結時發生錯誤:", error);
1199
  alert("產生分享連結時發生錯誤,可能是網路問題或單字列表過大。");
@@ -1206,7 +1329,7 @@
1206
 
1207
  copyLinkBtn.addEventListener('click', () => {
1208
  shareLinkInput.select();
1209
- shareLinkInput.setSelectionRange(0, 99999); // 兼容移動設備
1210
  try {
1211
  document.execCommand('copy');
1212
  copyFeedback.textContent = '已成功複製!';
@@ -1230,7 +1353,9 @@
1230
 
1231
  confirmClearBtn.addEventListener('click', () => {
1232
  words = [];
 
1233
  saveWordsToStorage();
 
1234
  renderWordList();
1235
  confirmClearModal.classList.add('hidden');
1236
  });
@@ -1242,17 +1367,8 @@
1242
 
1243
  confirmDeleteBtn.addEventListener('click', () => {
1244
  if (indexToDelete > -1) {
1245
- const allDeleteButtons = wordListContainer.querySelectorAll('.delete-btn');
1246
- let itemElement = null;
1247
- allDeleteButtons.forEach(btn => {
1248
- if(parseInt(btn.dataset.index, 10) === indexToDelete) {
1249
- itemElement = btn.closest('.list-item');
1250
- }
1251
- });
1252
-
1253
- if (itemElement) {
1254
- itemElement.classList.add('removing');
1255
- }
1256
 
1257
  setTimeout(() => {
1258
  if (indexToDelete > -1) {
@@ -1329,6 +1445,7 @@
1329
  lessonTitle = localStorage.getItem('flashcardsLessonTitle') || 'L1';
1330
  updateMainTitle();
1331
 
 
1332
  completionStatus = JSON.parse(localStorage.getItem('completionStatus')) || {};
1333
  updateCompletionUI();
1334
  highScore = parseInt(localStorage.getItem('flashcardsHighScore') || '0', 10);
@@ -1364,7 +1481,6 @@
1364
  };
1365
  initializeApp();
1366
 
1367
- // Append global styles
1368
  const style = document.createElement('style');
1369
  style.textContent = `
1370
  .mode-btn { color: white; font-weight: bold; padding: 1rem 1.5rem; border-radius: 1.5rem; transition: all 0.3s; transform: translateY(0); display: flex; justify-content: center; align-items: center; text-align: center; height: 100%;}
 
151
 
152
  <!-- 教師工具 -->
153
  <div id="teacher-tools" class="mt-8 pt-4 border-t flex justify-end items-center gap-3">
154
+ <button id="grade-report-btn" class="text-white font-semibold py-2 px-5 rounded-full transition-transform hover:scale-105 bg-blue-500 hover:bg-blue-600 text-sm">📊 成績報告</button>
155
  <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>
156
  <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>
157
  </div>
 
290
  </button>
291
  </div>
292
 
293
+ <!-- 測驗完成提示訊息 -->
294
+ <div id="quiz-completion-message" class="hidden w-full max-w-md mt-4 flex flex-col items-center text-center p-6 bg-white rounded-2xl shadow-lg">
295
+ <h2 class="text-3xl font-bold text-green-600">測驗完成!</h2>
296
+ <p class="mt-4 text-lg text-gray-700">成績已記錄,即將返回主選單...</p>
297
+ </div>
298
+
299
  <!-- 複習模式導覽 -->
300
  <div id="review-nav-container" class="flex items-center justify-between w-full max-w-md mt-10 space-x-4">
301
  <button id="prev-btn" class="nav-btn">
 
378
  </div>
379
  </div>
380
 
381
+ <!-- 成績報告 Modal -->
382
+ <div id="grade-report-modal" class="hidden fixed inset-0 bg-gray-900 bg-opacity-75 flex items-center justify-center z-50 p-4">
383
+ <div class="bg-white p-8 rounded-2xl shadow-xl w-full max-w-4xl">
384
+ <div class="flex justify-between items-center mb-6">
385
+ <h3 class="text-3xl font-bold text-gray-800">成績報告</h3>
386
+ <button type="button" id="close-report-modal-btn" class="p-2 rounded-full hover:bg-gray-200 transition-colors">
387
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /></svg>
388
+ </button>
389
+ </div>
390
+ <div id="report-content" class="grid grid-cols-1 lg:grid-cols-3 gap-6">
391
+ <!-- Report for zh-en -->
392
+ <div class="p-4 bg-teal-50 rounded-lg border border-teal-200">
393
+ <h4 class="text-xl font-bold text-teal-800 mb-4">中翻英測驗</h4>
394
+ <div id="report-zh-en" class="space-y-3 text-left text-gray-700 max-h-80 overflow-y-auto pr-2">
395
+ <!-- Content will be generated by JS -->
396
+ </div>
397
+ </div>
398
+ <!-- Report for en-zh -->
399
+ <div class="p-4 bg-amber-50 rounded-lg border border-amber-200">
400
+ <h4 class="text-xl font-bold text-amber-800 mb-4">英翻中測驗</h4>
401
+ <div id="report-en-zh" class="space-y-3 text-left text-gray-700 max-h-80 overflow-y-auto pr-2">
402
+ <!-- Content will be generated by JS -->
403
+ </div>
404
+ </div>
405
+ <!-- Report for listen -->
406
+ <div class="p-4 bg-rose-50 rounded-lg border border-rose-200">
407
+ <h4 class="text-xl font-bold text-rose-800 mb-4">聽力測驗</h4>
408
+ <div id="report-listen" class="space-y-3 text-left text-gray-700 max-h-80 overflow-y-auto pr-2">
409
+ <!-- Content will be generated by JS -->
410
+ </div>
411
+ </div>
412
+ </div>
413
+ </div>
414
+ </div>
415
 
416
  <!-- 清除確認 Modal -->
417
  <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">
418
  <div class="bg-white p-8 rounded-2xl shadow-xl w-full max-w-sm">
419
  <h3 class="text-2xl font-bold mb-4 text-gray-800">確認操作</h3>
420
+ <p class="text-gray-600 mb-6">您確定要清除所有單字和成績紀錄嗎?此操作無法復原。</p>
421
  <div class="flex justify-end gap-4 mt-6">
422
  <button type="button" id="cancel-clear-btn" class="px-6 py-2 bg-gray-200 text-gray-800 rounded-lg hover:bg-gray-300 transition-colors">取消</button>
423
  <button type="button" id="confirm-clear-btn" class="px-6 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors">確認清除</button>
 
459
  const teacherTools = document.getElementById('teacher-tools');
460
  const manageWordsBtn = document.getElementById('manage-words-btn');
461
  const shareGameBtn = document.getElementById('share-game-btn');
462
+ const gradeReportBtn = document.getElementById('grade-report-btn');
463
  const randomQuestionsCheckbox = document.getElementById('random-questions-checkbox');
464
  const randomQuestionsCountInput = document.getElementById('random-questions-count');
465
 
 
501
  const hintSection = document.getElementById('hint-section');
502
  const hintBtn = document.getElementById('hint-btn');
503
  const hintDisplay = document.getElementById('hint-display');
504
+ const quizCompletionMessage = document.getElementById('quiz-completion-message');
505
 
506
  // 複習模式導覽
507
  const reviewNavContainer = document.getElementById('review-nav-container');
 
518
  const finalScore = document.getElementById('final-score');
519
  const newHighscoreMsg = document.getElementById('new-highscore-msg');
520
  const playAgainBtn = document.getElementById('play-again-btn');
521
+
522
  // Modals
523
  const passwordModal = document.getElementById('password-modal');
524
  const passwordForm = document.getElementById('password-form');
 
540
  const copyFeedback = document.getElementById('copy-feedback');
541
  const closeShareModalBtn = document.getElementById('close-share-modal-btn');
542
  const qrcodeContainer = document.getElementById('qrcode-container');
543
+ const gradeReportModal = document.getElementById('grade-report-modal');
544
+ const closeReportModalBtn = document.getElementById('close-report-modal-btn');
545
+ const reportZhEn = document.getElementById('report-zh-en');
546
+ const reportEnZh = document.getElementById('report-en-zh');
547
+ const reportListen = document.getElementById('report-listen');
548
  const confirmClearModal = document.getElementById('confirm-clear-modal');
549
  const cancelClearBtn = document.getElementById('cancel-clear-btn');
550
  const confirmClearBtn = document.getElementById('confirm-clear-btn');
 
562
  let words = [];
563
  let quizQueue = [];
564
  let wordsForCurrentMode = [];
565
+ let gradeReports = {};
566
  let quizIncorrectCount = 0;
567
  let currentCardIndex = 0;
568
  let currentMode = '';
 
577
  let timeLeft = 60;
578
  let currentSpeedCard = null;
579
  let currentSpeedQuestionType = '';
580
+ let quizStartTime = 0;
581
 
582
  const modeDetails = {
583
  'review': { title: '複習模式' }, 'zh-en': { title: '中翻英測驗' },
 
615
  // --- 單字管理功能 ---
616
  const renderWordList = () => {
617
  wordListContainer.innerHTML = '';
 
618
  words.forEach((word, index) => {
619
  const item = document.createElement('div');
620
  item.className = 'list-item p-3 bg-gray-100 rounded-lg flex items-center justify-between';
 
732
  }
733
 
734
  flashcardContainer.style.cursor = 'default';
735
+ flashcardContainer.classList.remove('hidden');
736
+ quizCompletionMessage.classList.add('hidden');
737
  [speakBtn, speakSlowBtn].forEach(btn => btn.classList.add('hidden'));
738
  feedbackDisplay.textContent = '';
739
  answerInput.value = '';
 
803
 
804
  if (flashcardContainer.classList.contains('flipped')) {
805
  flashcardContainer.classList.remove('flipped');
 
806
  setTimeout(updateCardContent, 600);
807
  } else {
808
  updateCardContent();
 
821
  progressBarContainer.classList.toggle('hidden', isReview || isSpeed);
822
  speedHud.classList.toggle('hidden', !isSpeed);
823
  hudPlaceholder.classList.toggle('hidden', isSpeed);
824
+ quizCompletionMessage.classList.add('hidden');
825
 
826
  const start = parseInt(startRangeInput.value, 10);
827
  const end = parseInt(endRangeInput.value, 10);
828
 
829
  let wordPool;
 
830
  if (!isNaN(start) && !isNaN(end) && start > 0 && end >= start && end <= words.length) {
831
  wordPool = words.slice(start - 1, end);
832
  } else {
833
  wordPool = [...words];
834
  }
835
 
 
836
  if (mode === 'review') {
 
837
  wordsForCurrentMode = wordPool;
838
  } else if (randomQuestionsCheckbox.checked) {
839
  const randomCount = parseInt(randomQuestionsCountInput.value, 10);
 
844
  return;
845
  }
846
  } else {
 
847
  wordsForCurrentMode = wordPool;
848
  }
849
 
 
864
  quizQueue = shuffleArray([...wordsForCurrentMode]);
865
  }
866
 
867
+ if (isQuiz) {
868
+ quizStartTime = Date.now();
869
+ }
870
+
871
  quizIncorrectCount = 0;
872
  updateHintButtonVisibility();
873
 
 
891
 
892
  const wordIndex = words.findIndex(w => w.english === card.english && w.chinese === card.chinese);
893
 
894
+ let correctAnswer, answerLang;
 
895
 
896
  if (isReview) {
897
  const isFlipped = flashcardContainer.classList.contains('flipped');
898
+ correctAnswer = isFlipped ? card.english.split('(')[0].trim() : card.chinese.split('(')[0].trim();
899
+ answerLang = isFlipped ? 'en' : 'zh';
 
 
 
 
 
900
  } else {
901
+ let effectiveMode = isSpeed ? currentSpeedQuestionType : (currentMode === 'hard' ? 'zh-en' : currentMode);
 
 
902
  switch (effectiveMode) {
903
  case 'zh-en': case 'listen':
904
  correctAnswer = card.english.split('(')[0].trim();
 
946
  if (quizQueue.length > 0) {
947
  displayCard();
948
  } else {
949
+ // 測驗完成
950
+ const elapsedTime = Math.round((Date.now() - quizStartTime) / 1000);
951
+ const reportData = {
952
+ total: wordsForCurrentMode.length,
953
+ mistakes: quizIncorrectCount,
954
+ time: elapsedTime,
955
+ date: new Date().toISOString(),
956
+ status: 'completed'
957
+ };
958
+
959
+ const history = gradeReports[currentMode] || [];
960
+ history.push(reportData);
961
+ gradeReports[currentMode] = history;
962
+ localStorage.setItem('gradeReports', JSON.stringify(gradeReports));
963
+
964
+ quizStartTime = 0; // Reset timer
965
+
966
  if (currentMode !== 'hard') {
967
  completionStatus[currentMode] = true;
968
  localStorage.setItem('completionStatus', JSON.stringify(completionStatus));
969
  updateCompletionUI();
970
  }
971
+
972
+ [flashcardContainer, quizContainer, progressBarContainer, hintSection].forEach(el => el.classList.add('hidden'));
973
+ quizCompletionMessage.classList.remove('hidden');
974
+
975
+ setTimeout(() => showView('menu'), 3000);
976
  }
977
  }, 1500);
978
+ } else { // Review mode
 
 
979
  answerInput.disabled = false;
980
  submitBtn.disabled = false;
981
  }
 
1015
 
1016
 
1017
  const updateHintButtonVisibility = () => {
1018
+ const isQuiz = !['review', 'speed'].includes(currentMode);
1019
+ hintSection.classList.toggle('hidden', !(isQuiz && quizIncorrectCount >= 2));
 
1020
  };
1021
 
1022
  confirmWrongBtn.addEventListener('click', () => {
 
1111
  });
1112
  };
1113
 
1114
+ const renderGradeReport = () => {
1115
+ const reportTargets = { 'zh-en': reportZhEn, 'en-zh': reportEnZh, 'listen': reportListen };
1116
+ for (const mode in reportTargets) {
1117
+ const history = gradeReports[mode] || [];
1118
+ const targetDiv = reportTargets[mode];
1119
+ targetDiv.innerHTML = ''; // Clear previous content
1120
+
1121
+ if (history.length === 0) {
1122
+ targetDiv.innerHTML = `<p class="text-gray-500 p-4 text-center">尚未有紀錄</p>`;
1123
+ } else {
1124
+ history.slice().reverse().forEach(report => {
1125
+ const minutes = Math.floor(report.time / 60);
1126
+ const seconds = report.time % 60;
1127
+ const isCompleted = report.status === 'completed';
1128
+
1129
+ const reportElement = document.createElement('div');
1130
+ reportElement.className = 'p-3 mb-3 bg-white rounded-lg shadow-sm border';
1131
+ reportElement.innerHTML = `
1132
+ <div class="flex justify-between items-center mb-2">
1133
+ <p class="text-sm font-semibold">${new Date(report.date).toLocaleString('zh-TW', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })}</p>
1134
+ <span class="px-2 py-1 text-xs font-bold rounded-full ${isCompleted ? 'bg-green-100 text-green-800' : 'bg-yellow-100 text-yellow-800'}">
1135
+ ${isCompleted ? '已完成' : '未完成'}
1136
+ </span>
1137
+ </div>
1138
+ <div class="text-sm space-y-1">
1139
+ <p><strong>題目:</strong> ${isCompleted ? report.total : `${report.attempted || 0}/${report.total}`} 題</p>
1140
+ <p><strong>答錯:</strong> <span class="font-bold text-red-500">${report.mistakes} 次</span></p>
1141
+ <p><strong>時間:</strong> ${minutes} 分 ${seconds} 秒</p>
1142
+ </div>
1143
+ `;
1144
+ targetDiv.appendChild(reportElement);
1145
+ });
1146
+ }
1147
+ }
1148
+ };
1149
+
1150
  const generateQRCode = (text) => {
1151
  qrcodeContainer.innerHTML = '';
1152
  try {
 
1166
  document.querySelectorAll('.mode-btn[data-mode]').forEach(btn => {
1167
  btn.addEventListener('click', () => setupLearningView(btn.dataset.mode));
1168
  });
1169
+
1170
+ backToMenuBtn.addEventListener('click', () => {
1171
+ clearInterval(timerInterval);
1172
+ const isQuiz = !['review', 'speed', ''].includes(currentMode);
1173
+ if (isQuiz && quizStartTime > 0) {
1174
+ const attempted = wordsForCurrentMode.length - quizQueue.length;
1175
+ // Only save if at least one question was attempted
1176
+ if (attempted > 0) {
1177
+ const elapsedTime = Math.round((Date.now() - quizStartTime) / 1000);
1178
+ const reportData = {
1179
+ total: wordsForCurrentMode.length,
1180
+ attempted: attempted,
1181
+ mistakes: quizIncorrectCount,
1182
+ time: elapsedTime,
1183
+ date: new Date().toISOString(),
1184
+ status: 'abandoned'
1185
+ };
1186
+ const history = gradeReports[currentMode] || [];
1187
+ history.push(reportData);
1188
+ gradeReports[currentMode] = history;
1189
+ localStorage.setItem('gradeReports', JSON.stringify(gradeReports));
1190
+ }
1191
+ }
1192
+ quizStartTime = 0;
1193
+ currentMode = '';
1194
+ showView('menu');
1195
+ });
1196
 
1197
  flashcardContainer.addEventListener('click', () => {
1198
  if (currentMode === 'review') {
 
1273
  shareModal.classList.remove('hidden');
1274
  });
1275
 
1276
+ gradeReportBtn.addEventListener('click', () => {
1277
+ renderGradeReport();
1278
+ gradeReportModal.classList.remove('hidden');
1279
+ });
1280
+ closeReportModalBtn.addEventListener('click', () => gradeReportModal.classList.add('hidden'));
1281
+
1282
  generateLinkBtn.addEventListener('click', async () => {
1283
  generateLinkBtn.textContent = '產生中...';
1284
  generateLinkBtn.disabled = true;
 
1306
 
1307
  const longUrl = url.toString();
1308
 
 
1309
  const response = await fetch(`https://tinyurl.com/api-create.php?url=${encodeURIComponent(longUrl)}`);
1310
  if (response.ok) {
1311
  const shortUrl = await response.text();
1312
  shareLinkInput.value = shortUrl;
1313
  generateQRCode(shortUrl);
1314
  } else {
 
1315
  shareLinkInput.value = longUrl;
1316
  generateQRCode(longUrl);
1317
  copyFeedback.textContent = '縮短網址失敗,已產生原始連結。';
1318
  }
 
1319
  shareResultContainer.classList.remove('hidden');
 
1320
  } catch (error) {
1321
  console.error("產生分享連結時發生錯誤:", error);
1322
  alert("產生分享連結時發生錯誤,可能是網路問題或單字列表過大。");
 
1329
 
1330
  copyLinkBtn.addEventListener('click', () => {
1331
  shareLinkInput.select();
1332
+ shareLinkInput.setSelectionRange(0, 99999);
1333
  try {
1334
  document.execCommand('copy');
1335
  copyFeedback.textContent = '已成功複製!';
 
1353
 
1354
  confirmClearBtn.addEventListener('click', () => {
1355
  words = [];
1356
+ gradeReports = {};
1357
  saveWordsToStorage();
1358
+ localStorage.removeItem('gradeReports');
1359
  renderWordList();
1360
  confirmClearModal.classList.add('hidden');
1361
  });
 
1367
 
1368
  confirmDeleteBtn.addEventListener('click', () => {
1369
  if (indexToDelete > -1) {
1370
+ const itemElement = wordListContainer.querySelector(`.list-item button[data-index="${indexToDelete}"]`)?.closest('.list-item');
1371
+ if (itemElement) itemElement.classList.add('removing');
 
 
 
 
 
 
 
 
 
1372
 
1373
  setTimeout(() => {
1374
  if (indexToDelete > -1) {
 
1445
  lessonTitle = localStorage.getItem('flashcardsLessonTitle') || 'L1';
1446
  updateMainTitle();
1447
 
1448
+ gradeReports = JSON.parse(localStorage.getItem('gradeReports')) || {};
1449
  completionStatus = JSON.parse(localStorage.getItem('completionStatus')) || {};
1450
  updateCompletionUI();
1451
  highScore = parseInt(localStorage.getItem('flashcardsHighScore') || '0', 10);
 
1481
  };
1482
  initializeApp();
1483
 
 
1484
  const style = document.createElement('style');
1485
  style.textContent = `
1486
  .mode-btn { color: white; font-weight: bold; padding: 1rem 1.5rem; border-radius: 1.5rem; transition: all 0.3s; transform: translateY(0); display: flex; justify-content: center; align-items: center; text-align: center; height: 100%;}