feat: game practice loop with audio, scoring, and level-up modal (Task 11)

- useMorseAudio: wraps MorseAudioEngine, exposes currentSignal for FlashArea
- useProgress: fetches /api/progress + /api/mnemonics, recordAnswer with local stats
- FlashArea: gold flash on signal, height varies dot/dash
- ScoreBar: level name, score, streak display
- PracticeCard: play button, answer input, correct/incorrect feedback with mnemonic
- GamePage: full practice loop, level-up modal at streak%10, anti-repeat history
- Fix: incorrect attempt recorded only once per challenge (no stat inflation on retries)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-30 02:19:58 +00:00
parent 8415e569dd
commit 10afc2bbb0
7 changed files with 377 additions and 9 deletions
+3 -9
View File
@@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'
import { useAuth } from './hooks/useAuth'
import { LoginPage } from './pages/LoginPage'
import { OnboardingPage } from './pages/OnboardingPage'
import { GamePage } from './pages/GamePage'
import './index.css'
type AppRoute = 'login' | 'sent' | 'onboarding' | 'game' | 'admin'
@@ -51,15 +52,8 @@ export default function App() {
{route === 'onboarding' && token && (
<OnboardingPage token={token} onComplete={() => setRoute('game')} />
)}
{route === 'game' && (
<div>
<h1 style={{ fontFamily: 'var(--font-header)' }}>Practice</h1>
<p>Hello, {profile?.display_name || profile?.email || 'Adventurer'}!</p>
<p>Game loop coming in Task 11.</p>
<button className="btn" style={{ marginTop: '1rem' }} onClick={() => { logout(); setRoute('login') }}>
Log out
</button>
</div>
{route === 'game' && token && (
<GamePage token={token} onLogout={() => { logout(); setRoute('login') }} />
)}
{route === 'admin' && (
<div>