+ {/* 設定パネル(カテゴリ選択・出題数・制限時間) */}
+
+
+ {/* カテゴリ選択 */}
+
+
+ カテゴリ:
+
+
+
+ {categories.map((cat) => (
+
+ ))}
+
+
+
+ {/* 出題数選択 */}
+
+
出題数:
+
+ {[
+ { label: "5問", value: "5" },
+ { label: "10問", value: "10" },
+ { label: "20問", value: "20" },
+ { label: "全問", value: "all" },
+ ].map((opt) => (
+
+ ))}
+
+
+
+ {/* 制限時間選択(ゲーム要素: 15秒, 30秒, 45秒) */}
+
+
+ 制限時間:
+
+
+ {[
+ { label: "なし", value: "off" },
+ { label: "15秒", value: "15" },
+ { label: "30秒", value: "30" },
+ { label: "45秒", value: "45" },
+ ].map((opt) => (
+
+ ))}
+
+
+
+
+
+ {isFinished ? (
+ /* 全問題完了時のスコア結果画面 */
+
+
演習終了!
+
+ スコア:{" "}
+ {score} /{" "}
+ {questions.length} (
+ {questions.length > 0 ? Math.round((score / questions.length) * 100) : 0}%)
+
+
+
+ ) : (
+ /* 演習中画面 */
+ <>
+ {/* 進捗・現在スコア・タイマー表示 */}
+
+
+ 問題{" "}
+
+ {currentIndex + 1}
+ {" "}
+ / {questions.length}
+
+
+ {/* 制限時間カウントダウンバッジ */}
+ {selectedTimer !== "off" && (
+
+ ⏱️ 残り時間:
+
+ {timeLeft !== null ? `${timeLeft}秒` : "-"}
+
+
+ )}
+
+
+ 正解数:{" "}
+ {score}
+
+
+
+ {/* 制限時間のプログレスバー */}
+ {selectedTimer !== "off" && (
+
+ )}
+
+ {/* 出題カード表示 */}
+
+
+ {currentQuestion?.categoryTitle}
+
+
+ {currentQuestion?.sentence}
+
+
+ {currentQuestion?.translation}
+
+
+
+ {/* 解答入力エリア */}
+
+
+
setUserAnswer(e.target.value)}
+ onKeyDown={handleKeyDown}
+ placeholder="空欄に入る適切な語形を入力..."
+ className="w-full rounded-2xl border-2 border-zinc-300 bg-white px-5 py-4 text-center text-2xl font-semibold text-zinc-900 outline-none transition-colors focus:border-teal-500 focus:ring-4 focus:ring-teal-500/20 disabled:bg-zinc-100 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-50 dark:focus:border-teal-400 dark:disabled:bg-zinc-800"
+ />
+
+ {/* 特殊文字ボタンキーボード */}
+
+ {SPECIAL_KEYS.map((char) => (
+
+ ))}
+
+
+
+ 💡 ↑ / ↓ 矢印キー で文字にアクセント記号(á, é, í, ó, ú, ñ
+ など)を付与・切替できます。
+
+
+
+ {/* 判定結果バッジ */}
+ {isAnswered && (
+
+ {isCorrect ? (
+
+
+ ✨ 正解です!
+
+ {currentQuestion?.explanation && (
+
+ 💡 解説: {currentQuestion.explanation}
+
+ )}
+
+ ) : (
+
+
+ {isTimeOut ? "⏰ 時間切れです!" : "❌ 不正解です"}
+
+
+ 正解:{" "}
+
+ {currentQuestion?.answer}
+
+
+ {currentQuestion?.explanation && (
+
+ 💡 解説: {currentQuestion.explanation}
+
+ )}
+
+ )}
+
+ )}
+
+ {/* 操作ボタン */}
+
+ {isAnswered ? (
+
+ ) : (
+ <>
+
+
+ >
+ )}
+
+
+ >
+ )}
+
+ );
+}
diff --git a/components/spanish/SpanishFillInPracticeLoader.tsx b/components/spanish/SpanishFillInPracticeLoader.tsx
new file mode 100644
index 0000000..b2df466
--- /dev/null
+++ b/components/spanish/SpanishFillInPracticeLoader.tsx
@@ -0,0 +1,69 @@
+import fs from "node:fs/promises";
+import path from "node:path";
+import SpanishFillInPractice, { type FillInQuestionItem } from "./SpanishFillInPractice";
+
+/**
+ * MDX教材ファイル(app/learn/spanish/04/page.mdx)から
+ * 穴埋め文法問題データを動的に抽出する関数。
+ * データのベタ打ちを排除し、教材ファイルを唯一のデータソースとして使用する。
+ */
+async function getFillInQuestionsFromMdx(): Promise