Lashtw commited on
Commit
a16c71d
·
verified ·
1 Parent(s): 5279cc2

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +283 -46
index.html CHANGED
@@ -14,6 +14,8 @@
14
  <script src="https://cdnjs.cloudflare.com/ajax/libs/pako/2.1.0/pako.min.js"></script>
15
  <!-- 載入 PDF.js 函式庫 -->
16
  <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.min.js"></script>
 
 
17
 
18
  <!-- 【新】Firebase SDK -->
19
  <script type="module">
@@ -120,6 +122,29 @@
120
  #ai-progress-bar-inner, #audio-preload-bar {
121
  transition: width 0.3s ease-in-out;
122
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  </style>
124
  </head>
125
  <body class="bg-gray-50 min-h-screen flex flex-col items-center p-4 sm:p-8 font-sans">
@@ -212,6 +237,23 @@
212
  </button>
213
  </div>
214
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  <!-- 標題設定區塊 -->
216
  <div class="mb-6 p-4 bg-gray-50 rounded-2xl">
217
  <h3 class="text-xl font-semibold mb-4 text-gray-700">標題設定</h3>
@@ -330,20 +372,43 @@
330
  </div>
331
  </div>
332
 
333
- <!-- 提示功能區塊 -->
334
  <div id="hint-section" class="w-full max-w-md mt-4 text-center hidden">
335
- <button id="hint-btn" class="bg-amber-400 hover:bg-amber-500 text-amber-900 font-bold py-2 px-6 rounded-full transition-colors text-sm disabled:opacity-50 disabled:cursor-not-allowed">💡 取得提示</button>
336
- <p id="hint-display" class="mt-2 text-gray-600 font-semibold h-6"></p>
337
  </div>
338
 
339
- <!-- 測驗區塊 -->
340
  <div id="quiz-container" class="w-full max-w-md mt-4">
341
- <form id="quiz-form" class="flex items-center gap-2">
342
- <input id="answer-input" type="text" class="w-full p-4 border border-gray-300 rounded-full text-lg focus:ring-4 focus:ring-indigo-200 focus:border-indigo-500" placeholder="請輸入答案...">
343
- <button type="submit" class="bg-indigo-600 text-white p-4 rounded-full hover:bg-indigo-700 shrink-0 disabled:bg-indigo-300 disabled:cursor-not-allowed">
344
- <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /></svg>
345
- </button>
346
- </form>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347
  <div id="feedback-display" class="text-center mt-3 h-6 font-semibold"></div>
348
  </div>
349
 
@@ -561,6 +626,10 @@
561
  // The main logic is in the script tag below
562
  </script>
563
  <script>
 
 
 
 
564
  document.addEventListener('DOMContentLoaded', () => {
565
  // DOM 元素
566
  const mainMenu = document.getElementById('main-menu');
@@ -601,6 +670,10 @@
601
  const parseStatus = document.getElementById('parse-status');
602
  const aiProgressContainer = document.getElementById('ai-progress-container');
603
  const aiProgressBarInner = document.getElementById('ai-progress-bar-inner');
 
 
 
 
604
 
605
  // 學習介面
606
  const flashcardContainer = document.getElementById('flashcard-container');
@@ -622,15 +695,16 @@
622
 
623
  // 測驗相關
624
  const quizContainer = document.getElementById('quiz-container');
625
- const quizForm = document.getElementById('quiz-form');
626
  const answerInput = document.getElementById('answer-input');
627
- const submitBtn = quizForm.querySelector('button[type="submit"]');
628
  const feedbackDisplay = document.getElementById('feedback-display');
629
  const wrongAnswerFeedback = document.getElementById('wrong-answer-feedback');
630
  const confirmWrongBtn = document.getElementById('confirm-wrong-btn');
631
- const hintSection = document.getElementById('hint-section');
632
  const hintBtn = document.getElementById('hint-btn');
633
- const hintDisplay = document.getElementById('hint-display');
 
634
  const quizCompletionMessage = document.getElementById('quiz-completion-message');
635
 
636
  // 複習模式導覽
@@ -728,6 +802,135 @@
728
  'sentence-cloze': { title: '情境克漏字' },
729
  };
730
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
731
  // --- 視圖管理 ---
732
  const showView = (view) => {
733
  mainMenu.classList.toggle('hidden', view !== 'menu');
@@ -909,7 +1112,7 @@
909
  feedbackDisplay.textContent = '';
910
  answerInput.value = '';
911
  answerInput.disabled = false;
912
- submitBtn.disabled = false;
913
  wrongAnswerFeedback.classList.add('hidden');
914
  quizContainer.classList.remove('hidden');
915
  speedResultView.classList.add('hidden');
@@ -917,6 +1120,9 @@
917
  hintBtn.disabled = false;
918
  frontDisplay.classList.add('hidden');
919
  clozeQuestionContainer.classList.add('hidden');
 
 
 
920
 
921
  let frontText, backText;
922
 
@@ -926,7 +1132,7 @@
926
  case 'zh-en':
927
  frontText = card.chinese;
928
  backText = card.english;
929
- answerInput.placeholder = "請輸入英文答案...";
930
  break;
931
  case 'en-zh':
932
  frontText = card.english;
@@ -939,7 +1145,7 @@
939
  backText = card.english;
940
  [speakBtn, speakSlowBtn].forEach(btn => btn.classList.remove('hidden'));
941
  speakWord(card);
942
- answerInput.placeholder = "請輸入英文答案...";
943
  break;
944
  }
945
  } else {
@@ -955,7 +1161,7 @@
955
  break;
956
  case 'zh-en': case 'hard':
957
  frontText = card.chinese; backText = card.english;
958
- answerInput.placeholder = "請輸入英文答案...";
959
  break;
960
  case 'en-zh':
961
  frontText = card.english; backText = card.chinese;
@@ -966,7 +1172,7 @@
966
  frontText = '請聽發音'; backText = card.english;
967
  [speakBtn, speakSlowBtn].forEach(btn => btn.classList.remove('hidden'));
968
  speakWord(card);
969
- answerInput.placeholder = "請輸入英文答案...";
970
  break;
971
  case 'sentence-cloze':
972
  clozeQuestionContainer.classList.remove('hidden');
@@ -975,7 +1181,7 @@
975
  sentenceZhDisplay.classList.add('hidden');
976
  toggleTranslationBtn.textContent = '顯示翻譯';
977
  backText = card.sentence.answer || card.english; // 【修改】背面顯示正確答案
978
- answerInput.placeholder = "請填入空格中的單字...";
979
  break;
980
  }
981
  }
@@ -984,7 +1190,11 @@
984
  frontDisplay.textContent = frontText;
985
  }
986
  backDisplay.textContent = backText;
987
- setTimeout(() => answerInput.focus(), 100);
 
 
 
 
988
  };
989
 
990
  if (flashcardContainer.classList.contains('flipped')) {
@@ -1136,12 +1346,16 @@
1136
  if (isCorrect) {
1137
  correctSound.play();
1138
  answerInput.disabled = true;
1139
- submitBtn.disabled = true;
1140
  feedbackDisplay.textContent = '答對了!';
1141
  feedbackDisplay.classList.remove('text-red-500');
1142
  feedbackDisplay.classList.add('text-green-600');
1143
  triggerConfetti();
1144
 
 
 
 
 
1145
  if (!flashcardContainer.classList.contains('flipped')) {
1146
  flashcardContainer.classList.add('flipped');
1147
  }
@@ -1182,7 +1396,10 @@
1182
  updateCompletionUI();
1183
  }
1184
 
1185
- [flashcardContainer, quizContainer, progressBarContainer, hintSection].forEach(el => el.classList.add('hidden'));
 
 
 
1186
  quizCompletionMessage.classList.remove('hidden');
1187
 
1188
  setTimeout(() => showView('menu'), 3000);
@@ -1190,7 +1407,7 @@
1190
  }, 1500);
1191
  } else { // Review mode
1192
  answerInput.disabled = false;
1193
- submitBtn.disabled = false;
1194
  }
1195
  } else { // Incorrect answer
1196
  wrongSound.play();
@@ -1216,7 +1433,7 @@
1216
  setTimeout(() => { answerInput.classList.remove('shake'); }, 820);
1217
  } else { // Other quiz modes
1218
  answerInput.disabled = true;
1219
- submitBtn.disabled = true;
1220
  flashcardContainer.classList.add('flipped');
1221
  quizContainer.classList.add('hidden');
1222
  wrongAnswerFeedback.classList.remove('hidden');
@@ -1229,13 +1446,19 @@
1229
 
1230
  const updateHintButtonVisibility = () => {
1231
  const isQuiz = !['review', 'speed'].includes(currentMode);
1232
- hintSection.classList.toggle('hidden', !(isQuiz && quizIncorrectCount >= 2));
 
 
1233
  };
1234
 
1235
  confirmWrongBtn.addEventListener('click', () => {
1236
  const wrongCard = quizQueue.shift();
1237
  const reinsertIndex = Math.min(quizQueue.length, 3);
1238
  quizQueue.splice(reinsertIndex, 0, wrongCard);
 
 
 
 
1239
  displayCard();
1240
  });
1241
 
@@ -1272,7 +1495,7 @@
1272
 
1273
  const endSpeedChallenge = () => {
1274
  clearInterval(timerInterval);
1275
- answerInput.disabled = true; submitBtn.disabled = true;
1276
  quizContainer.classList.add('hidden'); speedResultView.classList.remove('hidden');
1277
  finalScore.textContent = currentScore;
1278
 
@@ -1727,7 +1950,17 @@
1727
 
1728
  prevBtn.addEventListener('click', () => { if (currentCardIndex > 0) { currentCardIndex--; displayCard(); }});
1729
  nextBtn.addEventListener('click', () => { if (currentCardIndex < wordsForCurrentMode.length - 1) { currentCardIndex++; displayCard(); }});
1730
- quizForm.addEventListener('submit', (e) => { e.preventDefault(); checkAnswer(); });
 
 
 
 
 
 
 
 
 
 
1731
 
1732
  speakBtn.addEventListener('click', (e) => {
1733
  e.stopPropagation();
@@ -1740,29 +1973,33 @@
1740
  if(word) speakWithBrowserTTS(word.english, 0.2);
1741
  });
1742
 
1743
- hintBtn.addEventListener('click', () => {
1744
- const card = currentMode === 'speed' ? currentSpeedCard : quizQueue[0];
1745
- if (!card) return;
1746
- const questionType = currentMode === 'speed' ? currentSpeedQuestionType : (currentMode === 'hard' ? 'zh-en' : currentMode);
1747
- let correctAnswer;
1748
- // 【修改】提示功能現在也使用正確的答案來源
1749
- switch (questionType) {
1750
- case 'sentence-cloze': correctAnswer = card.sentence.answer; break;
1751
- case 'zh-en': case 'listen': correctAnswer = card.english.replace(/[\((\[【].*?[\))\]】]/g, "").trim(); break;
1752
- case 'en-zh': correctAnswer = card.chinese.replace(/[\((\[【].*?[\))\]】]/g, "").trim(); break;
1753
- default: return;
1754
- }
1755
- if (correctAnswer) {
1756
- hintDisplay.textContent = `提示:答案以 '${correctAnswer[0]}' 開頭。`;
1757
- hintBtn.disabled = true;
1758
- }
1759
- });
 
 
1760
 
1761
  passwordForm.addEventListener('submit', (e) => {
1762
  e.preventDefault();
1763
  if (passwordInput.value === MANAGE_PASSWORD) {
1764
  passwordModal.classList.add('hidden');
1765
  renderWordList();
 
 
1766
  showView('manage');
1767
  } else {
1768
  passwordError.textContent = '密碼錯誤!';
@@ -2130,4 +2367,4 @@
2130
  });
2131
  </script>
2132
  </body>
2133
- </html>
 
14
  <script src="https://cdnjs.cloudflare.com/ajax/libs/pako/2.1.0/pako.min.js"></script>
15
  <!-- 載入 PDF.js 函式庫 -->
16
  <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.min.js"></script>
17
+ <!-- [第一步] 引入手寫辨識函式庫 -->
18
+ <script src="https://cdn.jsdelivr.net/gh/ChenYuHo/handwriting.js/handwriting.js"></script>
19
 
20
  <!-- 【新】Firebase SDK -->
21
  <script type="module">
 
122
  #ai-progress-bar-inner, #audio-preload-bar {
123
  transition: width 0.3s ease-in-out;
124
  }
125
+
126
+ /* [第二步] 新增手寫板樣式 */
127
+ #handwriting-container {
128
+ transition: all 0.3s ease;
129
+ }
130
+ #handwriting-canvas {
131
+ width: 100%;
132
+ max-width: 100%; /* 確保不超出容器 */
133
+ height: 250px; /* 手寫區域高度 */
134
+ background-color: #fff;
135
+ border: 2px dashed #cbd5e1; /* 虛線邊框更有「畫布」的感覺 */
136
+ border-radius: 0.75rem;
137
+ cursor: crosshair;
138
+ touch-action: none; /* [重要] 防止手指寫字時觸發網頁捲動 */
139
+ }
140
+ /* 讓答案框在唯讀模式下看起來像「顯示區」 */
141
+ input[readonly].handwriting-mode {
142
+ background-color: #f3f4f6;
143
+ color: #4f46e5;
144
+ font-weight: bold;
145
+ border-color: #818cf8;
146
+ cursor: not-allowed;
147
+ }
148
  </style>
149
  </head>
150
  <body class="bg-gray-50 min-h-screen flex flex-col items-center p-4 sm:p-8 font-sans">
 
237
  </button>
238
  </div>
239
 
240
+ <!-- [新增] 版本鎖定設定區塊 -->
241
+ <div class="mb-6 p-4 bg-yellow-50 rounded-2xl border-2 border-yellow-100">
242
+ <h3 class="text-xl font-semibold mb-4 text-yellow-800">🔒 版本鎖定設定</h3>
243
+ <p class="text-sm text-yellow-700 mb-3">選擇學生進行測驗時的輸入方式:</p>
244
+ <div class="flex items-center gap-6">
245
+ <label class="flex items-center cursor-pointer">
246
+ <input type="radio" name="version-lock" value="pc" class="h-5 w-5 text-yellow-600 focus:ring-yellow-500">
247
+ <span class="ml-2 text-gray-800 font-medium">鎖定 PC 版 (鍵盤打字)</span>
248
+ </label>
249
+ <label class="flex items-center cursor-pointer">
250
+ <input type="radio" name="version-lock" value="mobile" class="h-5 w-5 text-yellow-600 focus:ring-yellow-500">
251
+ <span class="ml-2 text-gray-800 font-medium">鎖定 行動版 (手寫輸入)</span>
252
+ </label>
253
+ <button id="save-version-btn" class="ml-auto bg-yellow-600 text-white font-bold py-2 px-6 rounded-full hover:bg-yellow-700 transition-colors">儲存設定</button>
254
+ </div>
255
+ </div>
256
+
257
  <!-- 標題設定區塊 -->
258
  <div class="mb-6 p-4 bg-gray-50 rounded-2xl">
259
  <h3 class="text-xl font-semibold mb-4 text-gray-700">標題設定</h3>
 
372
  </div>
373
  </div>
374
 
375
+ <!-- 舊的提示功能區塊 (保留但預設隱藏,新版已整合到下方) -->
376
  <div id="hint-section" class="w-full max-w-md mt-4 text-center hidden">
377
+ <!-- 已整合到下方 -->
378
+ <p id="hint-display" class="mt-2 text-gray-600 font-semibold h-6"></p>
379
  </div>
380
 
381
+ <!-- [第三步] 插入手寫板介面 (取代舊的 input form) -->
382
  <div id="quiz-container" class="w-full max-w-md mt-4">
383
+ <div class="flex flex-col gap-4">
384
+ <input id="answer-input" placeholder="輸入你的答案 (按 Enter 提交)" class="w-full p-4 border-2 border-gray-300 rounded-xl text-2xl focus:border-indigo-500 focus:ring-indigo-500 transition-colors" autocomplete="off">
385
+
386
+ <div id="handwriting-container" class="hidden flex-col gap-2">
387
+ <div class="relative w-full">
388
+ <canvas id="handwriting-canvas" width="500" height="250"></canvas>
389
+ <p class="absolute bottom-2 right-3 text-xs text-gray-400 pointer-events-none">請在此區域手寫</p>
390
+ </div>
391
+
392
+ <div class="flex gap-2 w-full">
393
+ <button id="hw-clear-btn" class="flex-1 py-3 bg-gray-200 text-gray-700 rounded-xl font-semibold hover:bg-gray-300 transition-colors flex items-center justify-center gap-1">
394
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm4 0a1 1 0 012 0v6a1 1 0 11-2 0V8z" clip-rule="evenodd" /></svg>
395
+ 重寫
396
+ </button>
397
+ <button id="hw-undo-btn" class="flex-1 py-3 bg-yellow-100 text-yellow-700 rounded-xl font-semibold hover:bg-yellow-200 transition-colors flex items-center justify-center gap-1">
398
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M9.707 16.707a1 1 0 01-1.414 0l-6-6a1 1 0 010-1.414l6-6a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l4.293 4.293a1 1 0 010 1.414z" clip-rule="evenodd" /></svg>
399
+ 復原
400
+ </button>
401
+ <button id="hw-recognize-btn" class="flex-[2] py-3 bg-indigo-600 text-white rounded-xl font-bold hover:bg-indigo-700 shadow-md transition-colors flex items-center justify-center gap-1">
402
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" /></svg>
403
+ 辨識輸入
404
+ </button>
405
+ </div>
406
+ </div>
407
+
408
+ <button id="submit-answer-btn" class="w-full bg-indigo-500 text-white p-4 rounded-xl text-xl font-bold hover:bg-indigo-600 transition-colors">提交答案</button>
409
+
410
+ <button id="hint-btn" class="w-full bg-yellow-500 text-white p-3 rounded-xl text-lg font-bold hover:bg-yellow-600 transition-colors hidden">顯示提示</button>
411
+ </div>
412
  <div id="feedback-display" class="text-center mt-3 h-6 font-semibold"></div>
413
  </div>
414
 
 
626
  // The main logic is in the script tag below
627
  </script>
628
  <script>
629
+ // [第四步] JavaScript 邏輯 (Script)
630
+ let effectiveVersion = 'pc'; // 預設為 PC 版
631
+ let handwritingCanvasObj = null; // [新增] 存放手寫板實例
632
+
633
  document.addEventListener('DOMContentLoaded', () => {
634
  // DOM 元素
635
  const mainMenu = document.getElementById('main-menu');
 
670
  const parseStatus = document.getElementById('parse-status');
671
  const aiProgressContainer = document.getElementById('ai-progress-container');
672
  const aiProgressBarInner = document.getElementById('ai-progress-bar-inner');
673
+
674
+ // [新增] 版本控制按鈕
675
+ const saveVersionBtn = document.getElementById('save-version-btn');
676
+ const versionRadios = document.getElementsByName('version-lock');
677
 
678
  // 學習介面
679
  const flashcardContainer = document.getElementById('flashcard-container');
 
695
 
696
  // 測驗相關
697
  const quizContainer = document.getElementById('quiz-container');
698
+ // 注意:原本的 quizForm 已經被移除,改用直接監聽按鈕
699
  const answerInput = document.getElementById('answer-input');
700
+ const submitAnswerBtn = document.getElementById('submit-answer-btn'); // [新增]
701
  const feedbackDisplay = document.getElementById('feedback-display');
702
  const wrongAnswerFeedback = document.getElementById('wrong-answer-feedback');
703
  const confirmWrongBtn = document.getElementById('confirm-wrong-btn');
704
+ // const hintSection = document.getElementById('hint-section'); // 舊的提示區塊,不再使用
705
  const hintBtn = document.getElementById('hint-btn');
706
+ const hintDisplay = document.getElementById('hint-display'); // 這裡暫時共用舊的 display 元素或需修改
707
+ // 為了配合新版���面,建議將提示顯示區域也移動到新區塊內,但為了最小修改,我們這裡用 JS 控制
708
  const quizCompletionMessage = document.getElementById('quiz-completion-message');
709
 
710
  // 複習模式導覽
 
802
  'sentence-cloze': { title: '情境克漏字' },
803
  };
804
 
805
+ // [新增] 初始化手寫板功能
806
+ function initHandwritingBoard() {
807
+ if (handwritingCanvasObj) return; // 避免重複初始化
808
+
809
+ // 建立 handwriting.js 實例 (參數: canvas元素, 筆畫粗細)
810
+ const canvasEl = document.getElementById('handwriting-canvas');
811
+ // 確保 canvas 寬度正確 (解決有些手機上寬度不對的問題)
812
+ canvasEl.width = canvasEl.parentElement.clientWidth || 300;
813
+
814
+ handwritingCanvasObj = new handwriting.Canvas(canvasEl, 3);
815
+
816
+ // 設定辨識後的回呼函式
817
+ handwritingCanvasObj.setCallBack(function(data, err) {
818
+ if (err) {
819
+ console.error(err);
820
+ alert("辨識失敗,請檢查網路連線"); // handwriting.js 需要連網
821
+ return;
822
+ }
823
+ // data 是候選字陣列,取第一個
824
+ if (data && data.length > 0) {
825
+ const result = data[0];
826
+ answerInput.value = result; // 填入答案框
827
+
828
+ // 視覺回饋
829
+ answerInput.classList.add('border-green-500', 'bg-green-50');
830
+ setTimeout(() => answerInput.classList.remove('border-green-500', 'bg-green-50'), 500);
831
+ }
832
+ });
833
+
834
+ // 設定筆畫樣式
835
+ handwritingCanvasObj.setLineWidth(5);
836
+ handwritingCanvasObj.setPenColor("#333");
837
+
838
+ // 讓 undo/redo 功能生效
839
+ handwritingCanvasObj.setOptions({
840
+ language: "en",
841
+ numOfReturn: 1
842
+ });
843
+
844
+ // 綁定按鈕事件
845
+ document.getElementById('hw-clear-btn').addEventListener('click', () => {
846
+ handwritingCanvasObj.erase();
847
+ answerInput.value = '';
848
+ answerInput.focus(); // 保持焦點
849
+ });
850
+
851
+ document.getElementById('hw-undo-btn').addEventListener('click', () => {
852
+ handwritingCanvasObj.undo();
853
+ });
854
+
855
+ document.getElementById('hw-recognize-btn').addEventListener('click', () => {
856
+ handwritingCanvasObj.recognize();
857
+ });
858
+ }
859
+
860
+ // [新增] 應用版本設定 (控制手寫板顯示)
861
+ function applyVersionSettings() {
862
+ const hwContainer = document.getElementById('handwriting-container');
863
+ const versionLockSetting = localStorage.getItem('flashcardsVersionLock');
864
+
865
+ // 優先使用儲存的設定,否則預設為 pc
866
+ if (versionLockSetting) {
867
+ effectiveVersion = versionLockSetting;
868
+ }
869
+
870
+ // 更新管理介面的 Radio 狀態
871
+ for(let radio of versionRadios) {
872
+ if(radio.value === effectiveVersion) {
873
+ radio.checked = true;
874
+ }
875
+ }
876
+
877
+ // 如果手寫容器還沒生成(避免報錯),先跳過
878
+ if (!hwContainer) return;
879
+
880
+ if (effectiveVersion === 'mobile') {
881
+ // --- 行動版模式 (手寫) ---
882
+ console.log("切換為手寫模式");
883
+
884
+ // 1. 顯示手寫板
885
+ hwContainer.classList.remove('hidden');
886
+ hwContainer.classList.add('flex');
887
+
888
+ // 2. 初始化手寫板 (如果還沒建立,且元素可見時才初始化以取得正確寬度)
889
+ // 使用 setTimeout 確保 display:flex 生效後才抓得到寬度
890
+ setTimeout(() => initHandwritingBoard(), 100);
891
+
892
+ // 3. 鎖定輸入框
893
+ answerInput.readOnly = true;
894
+ answerInput.placeholder = "請在下方手寫板書寫...";
895
+ answerInput.classList.add('handwriting-mode'); // 套用我們剛寫的 CSS
896
+
897
+ // 4. 移除之前可能加上的原生輸入法設定
898
+ if (window.google && window.google.ime) {
899
+ window.google.ime.setOptions({ ime: 'none', trigger: null });
900
+ }
901
+
902
+ } else {
903
+ // --- PC 版模式 (打字) ---
904
+ console.log("切換為打字模式");
905
+
906
+ // 1. 隱藏手寫板
907
+ hwContainer.classList.add('hidden');
908
+ hwContainer.classList.remove('flex');
909
+
910
+ // 2. 恢復輸入框
911
+ answerInput.readOnly = false;
912
+ answerInput.placeholder = "輸入你的答案 (按 Enter 提交)";
913
+ answerInput.classList.remove('handwriting-mode');
914
+ }
915
+ }
916
+
917
+ // [新增] 儲存版本設定
918
+ saveVersionBtn.addEventListener('click', () => {
919
+ let selectedValue = 'pc';
920
+ for(let radio of versionRadios) {
921
+ if(radio.checked) selectedValue = radio.value;
922
+ }
923
+ localStorage.setItem('flashcardsVersionLock', selectedValue);
924
+ effectiveVersion = selectedValue;
925
+ applyVersionSettings();
926
+
927
+ const originalText = saveVersionBtn.textContent;
928
+ saveVersionBtn.textContent = '已儲存!';
929
+ setTimeout(() => {
930
+ saveVersionBtn.textContent = originalText;
931
+ }, 2000);
932
+ });
933
+
934
  // --- 視圖管理 ---
935
  const showView = (view) => {
936
  mainMenu.classList.toggle('hidden', view !== 'menu');
 
1112
  feedbackDisplay.textContent = '';
1113
  answerInput.value = '';
1114
  answerInput.disabled = false;
1115
+ submitAnswerBtn.disabled = false; // [修改]
1116
  wrongAnswerFeedback.classList.add('hidden');
1117
  quizContainer.classList.remove('hidden');
1118
  speedResultView.classList.add('hidden');
 
1120
  hintBtn.disabled = false;
1121
  frontDisplay.classList.add('hidden');
1122
  clozeQuestionContainer.classList.add('hidden');
1123
+
1124
+ // [新增] 每次顯示卡片時,呼叫設定確保介面正確
1125
+ applyVersionSettings();
1126
 
1127
  let frontText, backText;
1128
 
 
1132
  case 'zh-en':
1133
  frontText = card.chinese;
1134
  backText = card.english;
1135
+ answerInput.placeholder = effectiveVersion === 'mobile' ? "請在下方手寫板書寫..." : "請輸入英文答案...";
1136
  break;
1137
  case 'en-zh':
1138
  frontText = card.english;
 
1145
  backText = card.english;
1146
  [speakBtn, speakSlowBtn].forEach(btn => btn.classList.remove('hidden'));
1147
  speakWord(card);
1148
+ answerInput.placeholder = effectiveVersion === 'mobile' ? "請在下方手寫板書寫..." : "請輸入英文答案...";
1149
  break;
1150
  }
1151
  } else {
 
1161
  break;
1162
  case 'zh-en': case 'hard':
1163
  frontText = card.chinese; backText = card.english;
1164
+ answerInput.placeholder = effectiveVersion === 'mobile' ? "請在下方手寫板書寫..." : "請輸入英文答案...";
1165
  break;
1166
  case 'en-zh':
1167
  frontText = card.english; backText = card.chinese;
 
1172
  frontText = '請聽發音'; backText = card.english;
1173
  [speakBtn, speakSlowBtn].forEach(btn => btn.classList.remove('hidden'));
1174
  speakWord(card);
1175
+ answerInput.placeholder = effectiveVersion === 'mobile' ? "請在下方手寫板書寫..." : "請輸入英文答案...";
1176
  break;
1177
  case 'sentence-cloze':
1178
  clozeQuestionContainer.classList.remove('hidden');
 
1181
  sentenceZhDisplay.classList.add('hidden');
1182
  toggleTranslationBtn.textContent = '顯示翻譯';
1183
  backText = card.sentence.answer || card.english; // 【修改】背面顯示正確答案
1184
+ answerInput.placeholder = effectiveVersion === 'mobile' ? "請填入空格中的單字..." : "請填入空格中的單字...";
1185
  break;
1186
  }
1187
  }
 
1190
  frontDisplay.textContent = frontText;
1191
  }
1192
  backDisplay.textContent = backText;
1193
+
1194
+ // 如果不是唯讀(手寫模式),才聚焦
1195
+ if(!answerInput.readOnly) {
1196
+ setTimeout(() => answerInput.focus(), 100);
1197
+ }
1198
  };
1199
 
1200
  if (flashcardContainer.classList.contains('flipped')) {
 
1346
  if (isCorrect) {
1347
  correctSound.play();
1348
  answerInput.disabled = true;
1349
+ submitAnswerBtn.disabled = true; // [修改]
1350
  feedbackDisplay.textContent = '答對了!';
1351
  feedbackDisplay.classList.remove('text-red-500');
1352
  feedbackDisplay.classList.add('text-green-600');
1353
  triggerConfetti();
1354
 
1355
+ if(handwritingCanvasObj) {
1356
+ handwritingCanvasObj.erase(); // 答對時清空手寫板
1357
+ }
1358
+
1359
  if (!flashcardContainer.classList.contains('flipped')) {
1360
  flashcardContainer.classList.add('flipped');
1361
  }
 
1396
  updateCompletionUI();
1397
  }
1398
 
1399
+ [flashcardContainer, quizContainer, progressBarContainer].forEach(el => el.classList.add('hidden'));
1400
+ // 提示功能已經移入 quizContainer,所以不需要單獨隱藏
1401
+ // if (hintSection) hintSection.classList.add('hidden');
1402
+
1403
  quizCompletionMessage.classList.remove('hidden');
1404
 
1405
  setTimeout(() => showView('menu'), 3000);
 
1407
  }, 1500);
1408
  } else { // Review mode
1409
  answerInput.disabled = false;
1410
+ submitAnswerBtn.disabled = false; // [修改]
1411
  }
1412
  } else { // Incorrect answer
1413
  wrongSound.play();
 
1433
  setTimeout(() => { answerInput.classList.remove('shake'); }, 820);
1434
  } else { // Other quiz modes
1435
  answerInput.disabled = true;
1436
+ submitAnswerBtn.disabled = true; // [修改]
1437
  flashcardContainer.classList.add('flipped');
1438
  quizContainer.classList.add('hidden');
1439
  wrongAnswerFeedback.classList.remove('hidden');
 
1446
 
1447
  const updateHintButtonVisibility = () => {
1448
  const isQuiz = !['review', 'speed'].includes(currentMode);
1449
+ if (hintBtn) {
1450
+ hintBtn.classList.toggle('hidden', !(isQuiz && quizIncorrectCount >= 2));
1451
+ }
1452
  };
1453
 
1454
  confirmWrongBtn.addEventListener('click', () => {
1455
  const wrongCard = quizQueue.shift();
1456
  const reinsertIndex = Math.min(quizQueue.length, 3);
1457
  quizQueue.splice(reinsertIndex, 0, wrongCard);
1458
+ // 答錯後繼續,需清空手寫板
1459
+ if(handwritingCanvasObj) {
1460
+ handwritingCanvasObj.erase();
1461
+ }
1462
  displayCard();
1463
  });
1464
 
 
1495
 
1496
  const endSpeedChallenge = () => {
1497
  clearInterval(timerInterval);
1498
+ answerInput.disabled = true; submitAnswerBtn.disabled = true;
1499
  quizContainer.classList.add('hidden'); speedResultView.classList.remove('hidden');
1500
  finalScore.textContent = currentScore;
1501
 
 
1950
 
1951
  prevBtn.addEventListener('click', () => { if (currentCardIndex > 0) { currentCardIndex--; displayCard(); }});
1952
  nextBtn.addEventListener('click', () => { if (currentCardIndex < wordsForCurrentMode.length - 1) { currentCardIndex++; displayCard(); }});
1953
+
1954
+ // [重要修改] 表單移除後,改用按鈕監聽
1955
+ // quizForm.addEventListener('submit', (e) => { e.preventDefault(); checkAnswer(); });
1956
+ submitAnswerBtn.addEventListener('click', checkAnswer);
1957
+ // 允許在 PC 模式下按 Enter 送出
1958
+ answerInput.addEventListener('keydown', (e) => {
1959
+ if(e.key === 'Enter') {
1960
+ e.preventDefault();
1961
+ if(!submitAnswerBtn.disabled) checkAnswer();
1962
+ }
1963
+ });
1964
 
1965
  speakBtn.addEventListener('click', (e) => {
1966
  e.stopPropagation();
 
1973
  if(word) speakWithBrowserTTS(word.english, 0.2);
1974
  });
1975
 
1976
+ if(hintBtn) {
1977
+ hintBtn.addEventListener('click', () => {
1978
+ const card = currentMode === 'speed' ? currentSpeedCard : quizQueue[0];
1979
+ if (!card) return;
1980
+ const questionType = currentMode === 'speed' ? currentSpeedQuestionType : (currentMode === 'hard' ? 'zh-en' : currentMode);
1981
+ let correctAnswer;
1982
+ // 【修改】提示功能現在也使用正確的答案來源
1983
+ switch (questionType) {
1984
+ case 'sentence-cloze': correctAnswer = card.sentence.answer; break;
1985
+ case 'zh-en': case 'listen': correctAnswer = card.english.replace(/[\((\[【].*?[\))\]】]/g, "").trim(); break;
1986
+ case 'en-zh': correctAnswer = card.chinese.replace(/[\((\[【].*?[\))\]】]/g, "").trim(); break;
1987
+ default: return;
1988
+ }
1989
+ if (correctAnswer) {
1990
+ hintDisplay.textContent = `提示:答案以 '${correctAnswer[0]}' 開頭。`;
1991
+ hintBtn.disabled = true;
1992
+ }
1993
+ });
1994
+ }
1995
 
1996
  passwordForm.addEventListener('submit', (e) => {
1997
  e.preventDefault();
1998
  if (passwordInput.value === MANAGE_PASSWORD) {
1999
  passwordModal.classList.add('hidden');
2000
  renderWordList();
2001
+ // 在進入管理畫面時也更新一下設定狀態
2002
+ applyVersionSettings();
2003
  showView('manage');
2004
  } else {
2005
  passwordError.textContent = '密碼錯誤!';
 
2367
  });
2368
  </script>
2369
  </body>
2370
+ </html>