import{useEffect,useMemo,useState}from "react"; import{FAMILIES,FAMILY_LABELS,WRITING_BY_LETTER,WRITING_LETTERS,type LetterFamily,type WritingLetter}from "../content/writing"; import{WritingCanvas}from "../components/WritingCanvas"; import{useVoice}from "../lib/voice-context"; import{playWritingPrompt,playWritingScript}from "../lib/writing-voice"; import type{TraceScore}from "../lib/trace-score"; const DONE_KEY = "wg.writing.done"; const STICKER_KEY = "wg.stickers"; const QUIZ_ROUNDS = 8; function loadDone(): string[]{try{const raw = localStorage.getItem(DONE_KEY);const parsed = raw ? (JSON.parse(raw) as string[]) : [];return Array.isArray(parsed) ? parsed : []}catch{return []}}function saveDone(letters: string[]): void{try{localStorage.setItem(DONE_KEY,JSON.stringify(letters))}catch{// device storage unavailable}}function shuffledLetters(): WritingLetter[]{const copy = [...WRITING_LETTERS];for (let index = copy.length - 1; index > 0; index -= 1){const swap = Math.floor(Math.random() * (index + 1));[copy[index],copy[swap]] = [copy[swap],copy[index]]}return copy}export function Writing(){const{buddy}= useVoice();const [mode,setMode] = useState<"practise" | "check">("practise");const [family,setFamily] = useState<LetterFamily | "all">("all");const [letter,setLetter] = useState<WritingLetter>(WRITING_LETTERS[0]);const [animateKey,setAnimateKey] = useState(0);const [showDemo,setShowDemo] = useState(false);const [done,setDone] = useState<string[]>(loadDone);const [stars,setStars] = useState(() => Number(localStorage.getItem(STICKER_KEY) ?? 0));const [quiz,setQuiz] = useState<WritingLetter[]>([]);const [quizIndex,setQuizIndex] = useState(0);const [quizPassed,setQuizPassed] = useState(false);const [quizAttempted,setQuizAttempted] = useState(false);const [quizReveal,setQuizReveal] = useState(false);const [quizDone,setQuizDone] = useState(false);const [quizScore,setQuizScore] = useState(0);const [canvasResetting,setCanvasResetting] = useState(false);const visibleLetters = useMemo(() => (family === "all" ? WRITING_LETTERS : WRITING_LETTERS.filter((item) => item.family === family)),[family],);const quizLetter = quiz[quizIndex];useEffect(() => {if (mode === "practise") {void playWritingScript(letter.letter,buddy); setAnimateKey((current) => current + 1); setShowDemo(true);}},[letter,buddy,mode]);function choose(entry: WritingLetter){setLetter(entry);setShowDemo(false);setAnimateKey((current) => current + 1)}function showMe(){setAnimateKey((current) => current + 1);setShowDemo(true);void playWritingScript(letter.letter,buddy)}function startQuiz(){setMode("check");setQuiz(shuffledLetters().slice(0,QUIZ_ROUNDS));setQuizIndex(0);setQuizPassed(false);setQuizAttempted(false);setQuizReveal(false);setQuizDone(false);setQuizScore(0)}useEffect(() => {if (mode === "check" && quiz[quizIndex]) {void playWritingPrompt(quiz[quizIndex].letter,buddy);}},[mode,quiz,quizIndex,buddy]);function onScore(score: TraceScore){if (mode !== "check" || quizPassed) return;setQuizAttempted(true);if (!score.pass) return;setQuizPassed(true);setQuizScore((current) => current + 1);markDone(quiz[quizIndex].letter)}function nextQuizLetter(){if (quizIndex + 1 >= quiz.length){setQuizDone(true);return}setQuizIndex((current) => current + 1);setQuizPassed(false);setQuizAttempted(false);setQuizReveal(false);setAnimateKey((current) => current + 1)}function markDone(letterName: string){if (done.includes(letterName)) return;const fresh = [...done,letterName];setDone(fresh);saveDone(fresh);setStars((current) => {const next = current + 1; localStorage.setItem(STICKER_KEY,String(next)); return next;})}return (<div className="stack"> <section className="page-head"> <p className="eyebrow">Writing</p> <h1>Letter formation,step by step</h1> <p className="hero-lede"> Watch the letter,hear how it starts and moves,then trace it on the line. The middle line is halfway up,so every small letter sits between it and the writing line. </p> </section> <div className="writing-modes"> <button type="button" className={`writing-mode${mode === "practise" ? " writing-mode-active" : ""}`} onClick={() => setMode("practise")} > Practise a letter </button> <button type="button" className={`writing-mode${mode === "check" ? " writing-mode-active" : ""}`} onClick={startQuiz} > Check: can you write it? </button> <span className="muted">🌱 {stars} {stars === 1 ? "sticker" : "stickers"} · {done.length}/26 letters</span> </div> {mode === "practise" ? (<> <section className="card writing-families"> <div className="family-chips"> <button type="button" className={`family-chip${family === "all" ? " family-chip-active" : ""}`} onClick={() => setFamily("all")}> All letters </button> {FAMILIES.map((item) => (<button key={item} type="button" className={`family-chip family-chip-${item}${family === item ? " family-chip-active" : ""}`} onClick={() => setFamily(item)} > {FAMILY_LABELS[item]} </button>))} </div> <div className="writing-letters"> {visibleLetters.map((entry) => (<button key={entry.letter} type="button" className={`writing-letter${entry.letter === letter.letter ? " writing-letter-active" : ""}${done.includes(entry.letter) ? " writing-letter-done" : ""}`} onClick={() => choose(entry)} aria-pressed={entry.letter === letter.letter} > {entry.letter} {done.includes(entry.letter) && <span className="writing-letter-tick">✓</span>} </button>))} </div> </section> <section className="card writing-card" data-letter={letter.letter}> <div className="writing-head"> <div> <span className="eyebrow">{FAMILY_LABELS[letter.family]}</span> <h2>Write “{letter.letter}”</h2> <p className="muted">{letter.script}</p> </div> <div className="writing-head-actions"> <button type="button" className="cta" onClick={showMe}> ▶ Show me </button> <button type="button" className="cta cta-secondary" onClick={() => void playWritingScript(letter.letter,buddy)}> 🔊 Hear it again </button> </div> </div> <WritingCanvas letter={letter} animateKey={animateKey} showGuide showDemo={showDemo} onResetChange={setCanvasResetting} onScore={(score) => {if (score.pass) markDone(letter.letter);}} /> <div className="writing-next"> <button type="button" className="link-button" onClick={() => choose(WRITING_BY_LETTER.get(String.fromCharCode(((letter.letter.charCodeAt(0) - 97 + 25) % 26) + 97))!)}> ← Previous letter </button> <button type="button" className="link-button" onClick={() => choose(WRITING_BY_LETTER.get(String.fromCharCode(((letter.letter.charCodeAt(0) - 97 + 1) % 26) + 97))!)}> Next letter → </button> </div> </section> </>) : quizDone ? (<section className="card writing-card"> <h2>Writing check complete</h2> <p className="slide-prompt">You wrote {quizScore} of {quiz.length} letters correctly.</p> <div className="game-actions"> <button type="button" className="cta" onClick={startQuiz}>Check again →</button> <button type="button" className="cta cta-secondary" onClick={() => setMode("practise")}>Back to practise</button> </div> </section>) : quizLetter ? (<section className="card writing-card writing-quiz" data-letter={quizAttempted ? quizLetter.letter : undefined}> <div className="writing-head"> <div> <span className="eyebrow">Check {quizIndex + 1} of {quiz.length}</span> <h2>Can you write the letter?</h2> <p className="muted"> {quizPassed ? `That was “${quizLetter.letter}” — well done.` : quizAttempted ? `It was “${quizLetter.letter}”. Look at the shape and try again.` : "Listen, then write it on the line."} </p> </div> <div className="writing-head-actions"> <button type="button" className="cta cta-secondary" onClick={() => void playWritingPrompt(quizLetter.letter,buddy)} disabled={canvasResetting}> 🔊 Hear it again </button> {quizAttempted && !quizPassed && (<button type="button" className="cta" disabled={canvasResetting} onClick={() => {setQuizReveal(true); setAnimateKey((current) => current + 1); void playWritingScript(quizLetter.letter,buddy);}} > ▶ Show me </button>)} </div> </div> <WritingCanvas key={`${quizLetter.letter}-${quizIndex}-${quizReveal ? "reveal" : "blank"}`} letter={quizLetter} animateKey={animateKey} showGuide={quizReveal} showDemo={quizReveal} onResetChange={setCanvasResetting} onScore={onScore} /> {quizPassed && (<div className="game-actions"> <button type="button" className="cta" onClick={nextQuizLetter}> {quizIndex + 1 >= quiz.length ? "Finish →" : "Next letter →"} </button> </div>)} </section>) : null} <section className="card"> <h2>How we teach it</h2> <ul className="check-list"> <li>Letters are grouped into the four UK handwriting families: long ladder,one-armed robot,curly caterpillar and zigzag monster.</li> <li>Each letter has a numbered start dot and a spoken direction — correct start and movement matter more than speed.</li> <li>Small letters sit between the middle line and the writing line; ascenders reach the top line; descenders go below the line.</li> </ul> </section> </div>)}
