import React, { useState, useEffect, useRef } from 'react'; import { Settings, Play, RotateCcw, CheckCircle, XCircle, Trophy, Check } from 'lucide-react'; // --- DATA KATAKANA --- const KATAKANA_DATA = { 'Seion (Dasar)': { 'A-O': [ { char: 'ア', romaji: 'a' }, { char: 'イ', romaji: 'i' }, { char: 'ウ', romaji: 'u' }, { char: 'エ', romaji: 'e' }, { char: 'オ', romaji: 'o' } ], 'Ka-Ko': [ { char: 'カ', romaji: 'ka' }, { char: 'キ', romaji: 'ki' }, { char: 'ク', romaji: 'ku' }, { char: 'ケ', romaji: 'ke' }, { char: 'コ', romaji: 'ko' } ], 'Sa-So': [ { char: 'サ', romaji: 'sa' }, { char: 'シ', romaji: 'shi' }, { char: 'ス', romaji: 'su' }, { char: 'セ', romaji: 'se' }, { char: 'ソ', romaji: 'so' } ], 'Ta-To': [ { char: 'タ', romaji: 'ta' }, { char: 'チ', romaji: 'chi' }, { char: 'ツ', romaji: 'tsu' }, { char: 'テ', romaji: 'te' }, { char: 'ト', romaji: 'to' } ], 'Na-No': [ { char: 'ナ', romaji: 'na' }, { char: 'ニ', romaji: 'ni' }, { char: 'ヌ', romaji: 'nu' }, { char: 'ネ', romaji: 'ne' }, { char: 'ノ', romaji: 'no' } ], 'Ha-Ho': [ { char: 'ハ', romaji: 'ha' }, { char: 'ヒ', romaji: 'hi' }, { char: 'フ', romaji: 'fu' }, { char: 'ヘ', romaji: 'he' }, { char: 'ホ', romaji: 'ho' } ], 'Ma-Mo': [ { char: 'マ', romaji: 'ma' }, { char: 'ミ', romaji: 'mi' }, { char: 'ム', romaji: 'mu' }, { char: 'メ', romaji: 'me' }, { char: 'モ', romaji: 'mo' } ], 'Ya-Yo': [ { char: 'ヤ', romaji: 'ya' }, { char: 'ユ', romaji: 'yu' }, { char: 'ヨ', romaji: 'yo' } ], 'Ra-Ro': [ { char: 'ラ', romaji: 'ra' }, { char: 'リ', romaji: 'ri' }, { char: 'ル', romaji: 'ru' }, { char: 'レ', romaji: 're' }, { char: 'ロ', romaji: 'ro' } ], 'Wa-N': [ { char: 'ワ', romaji: 'wa' }, { char: 'ヲ', romaji: 'wo' }, { char: 'ン', romaji: 'n' } ] }, 'Dakuten (Suara Tebal)': { 'Ga-Go': [ { char: 'ガ', romaji: 'ga' }, { char: 'ギ', romaji: 'gi' }, { char: 'グ', romaji: 'gu' }, { char: 'ゲ', romaji: 'ge' }, { char: 'ゴ', romaji: 'go' } ], 'Za-Zo': [ { char: 'ザ', romaji: 'za' }, { char: 'ジ', romaji: 'ji' }, { char: 'ズ', romaji: 'zu' }, { char: 'ゼ', romaji: 'ze' }, { char: 'ゾ', romaji: 'zo' } ], 'Da-Do': [ { char: 'ダ', romaji: 'da' }, { char: 'ヂ', romaji: 'ji' }, { char: 'ヅ', romaji: 'zu' }, { char: 'デ', romaji: 'de' }, { char: 'ド', romaji: 'do' } ], 'Ba-Bo': [ { char: 'バ', romaji: 'ba' }, { char: 'ビ', romaji: 'bi' }, { char: 'ブ', romaji: 'bu' }, { char: 'ベ', romaji: 'be' }, { char: 'ボ', romaji: 'bo' } ], }, 'Handakuten (Suara P)': { 'Pa-Po': [ { char: 'パ', romaji: 'pa' }, { char: 'ピ', romaji: 'pi' }, { char: 'プ', romaji: 'pu' }, { char: 'ペ', romaji: 'pe' }, { char: 'ポ', romaji: 'po' } ] } }; // --- COMPONENTS --- const Checkbox = ({ label, checked, onChange }) => ( ); const Button = ({ children, onClick, variant = 'primary', disabled = false, className = '' }) => { const baseStyle = "px-6 py-3 rounded-xl font-bold transition-all transform active:scale-95 shadow-md flex items-center justify-center gap-2"; const variants = { primary: "bg-blue-600 text-white hover:bg-blue-700 disabled:bg-slate-300 disabled:cursor-not-allowed disabled:shadow-none", secondary: "bg-white text-slate-700 border border-slate-200 hover:bg-slate-50", danger: "bg-red-500 text-white hover:bg-red-600", success: "bg-green-500 text-white hover:bg-green-600" }; return ( ); }; // --- MAIN APP COMPONENT --- export default function App() { const [appState, setAppState] = useState('setup'); // setup, quiz, result // Settings State const [selectedGroups, setSelectedGroups] = useState(['A-O', 'Ka-Ko', 'Sa-So', 'Ta-To', 'Na-No']); const [gameMode, setGameMode] = useState('fixed'); // fixed, endless const [questionCount, setQuestionCount] = useState(20); // Quiz State const [quizQueue, setQuizQueue] = useState([]); const [currentIndex, setCurrentIndex] = useState(0); const [score, setScore] = useState(0); const [streak, setStreak] = useState(0); const [input, setInput] = useState(''); const [feedback, setFeedback] = useState(null); // 'correct', 'incorrect', null const [shake, setShake] = useState(false); const inputRef = useRef(null); // Helpers const getAllGroups = () => { let groups = []; Object.keys(KATAKANA_DATA).forEach(category => { Object.keys(KATAKANA_DATA[category]).forEach(group => { groups.push(group); }); }); return groups; }; const toggleGroup = (group) => { if (selectedGroups.includes(group)) { setSelectedGroups(selectedGroups.filter(g => g !== group)); } else { setSelectedGroups([...selectedGroups, group]); } }; const toggleAll = () => { const all = getAllGroups(); if (selectedGroups.length === all.length) { setSelectedGroups([]); } else { setSelectedGroups(all); } }; const shuffleArray = (array) => { return array.sort(() => Math.random() - 0.5); }; // Start Game Logic const startGame = () => { let pool = []; Object.values(KATAKANA_DATA).forEach(category => { Object.entries(category).forEach(([groupName, chars]) => { if (selectedGroups.includes(groupName)) { pool = [...pool, ...chars]; } }); }); if (pool.length === 0) return; let queue = []; if (gameMode === 'fixed') { // Fill queue up to questionCount, reusing pool if necessary while (queue.length < questionCount) { queue = [...queue, ...shuffleArray([...pool])]; } queue = queue.slice(0, questionCount); } else { // Endless: initial pool queue = shuffleArray([...pool]); } setQuizQueue(queue); setCurrentIndex(0); setScore(0); setStreak(0); setInput(''); setFeedback(null); setAppState('quiz'); }; // Answer Logic const handleSubmit = (e) => { e.preventDefault(); if (feedback) return; // Prevent double submission during animation const currentQuestion = quizQueue[currentIndex]; const isCorrect = input.toLowerCase().trim() === currentQuestion.romaji; if (isCorrect) { setFeedback('correct'); setScore(s => s + 1); setStreak(s => s + 1); } else { setFeedback('incorrect'); setStreak(0); setShake(true); setTimeout(() => setShake(false), 500); } // Delay for next question setTimeout(() => { setFeedback(null); setInput(''); if (gameMode === 'fixed') { if (currentIndex + 1 >= quizQueue.length) { setAppState('result'); } else { setCurrentIndex(prev => prev + 1); } } else { // Endless mode if (currentIndex + 1 >= quizQueue.length) { // Reshuffle current pool to add more questions seamlessly let pool = []; Object.values(KATAKANA_DATA).forEach(category => { Object.entries(category).forEach(([groupName, chars]) => { if (selectedGroups.includes(groupName)) { pool = [...pool, ...chars]; } }); }); setQuizQueue([...quizQueue, ...shuffleArray(pool)]); } setCurrentIndex(prev => prev + 1); } }, isCorrect ? 600 : 1500); }; // Focus input on mount/update useEffect(() => { if (appState === 'quiz' && !feedback && inputRef.current) { inputRef.current.focus(); } }, [appState, feedback, currentIndex]); // --- VIEWS --- if (appState === 'setup') { return (
Mastering Japanese Katakana
{gameMode === 'fixed' ? 'Mode Latihan Standar' : 'Mode Tanpa Batas'}
{subMessage}