# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Touch-bedienbare Lern-PWA für Grundschulkinder (Schulanfänger, **kein Lesen vorausgesetzt**). Kinder üben Zahlzerlegungen für Zielzahlen 4–10 auf Zeit, thematisch als Raketenflug. Detaillierte Produktspezifikation in `BRAINSTORM.md` — bei UI-/UX-Entscheidungen dort zuerst nachsehen. Kernprinzipien aus `BRAINSTORM.md`, die bei jeder Änderung beachtet werden müssen: - **Kein Text** in der Spielfläche — nur Symbole, Zahlen, Animationen. - **Keine Vermenschlichung** (keine Gesichter auf Rakete/Objekten). - **Positives Feedback only** — keine Bestrafung bei falschen Antworten, kein Knall/Absturz. - **Große Touchflächen** (mind. 84–96 px), `user-select: none`, `touch-action: manipulation`. - **Sounds müssen abschaltbar** sein (global via Settings-Store). ## Commands ```bash npm run dev # Vite-Dev-Server auf :5173 npm run build # Production-Build (Vite + vite-plugin-pwa) npm run preview # Build lokal servieren npm run check # svelte-check (Type-Check) npm test # Vitest einmal ausführen npm run test:watch npx vitest run path/to/foo.test.ts # einzelne Test-Datei ``` Visuelle Verifikation mit `firefox-devtools`-MCP: Firefox via `swaymsg` floating + 1980×1200 stellen, dann `navigate_page` + `screenshot_page`. Beispiele für relevante Viewport-Größen: 1920×1080, 1280×720, 390×844. ## Architecture ### Tech-Stack Svelte 5 (Runes-Mode: `$props()`, `$state()`, `$derived()`, `$effect()`) + Vite 6 + TypeScript (strict) + `vite-plugin-pwa`. Vitest mit jsdom-Environment. **Kein Backend** — alle Daten in `localStorage`. ### Drei-Schichten-Trennung in `src/lib/` ``` game/ — Pure Logic, framework-frei, Vitest-getestet stores/ — Svelte-Stores; halten Reactive-State, syncen zu localStorage components/, screens/, audio/ — UI; konsumieren Stores, lösen Aktionen aus ``` Diese Trennung ist load-bearing: **Logik nie in Komponenten**, **Storage-Zugriffe nur in `game/persistence.ts`**, **State-Mutations gehen über Store-Setter** (`startGame`, `answer`, `setRoundSeconds` …) statt direkter `.set()`-Aufrufe. ### State-Flow `route` (writable) entscheidet, welcher Screen rendert (App.svelte switched per `if`). `game` ist der Spielzustand, wird per `startGame(target)` initialisiert und tickt selbst über `requestAnimationFrame` (`game.ts:tick`). Bei Antwort → `answer(value)` → ggf. `correctCount++`, neue Aufgabe via `generateTask`, `stageBumpKey` inkrementiert (FX-Trigger). Bei `correctCount === 10` oder Timer = 0 → `finalize()` schreibt in `progress`-Store, der via `subscribe`-Hook automatisch in `localStorage` persistiert. `settings` und `progress` laden initial aus `localStorage` (mit Schema-Toleranz in `loadProgress`/`loadSettings`) und schreiben automatisch bei Änderungen zurück. ### Stufen-Mapping (Game-Design-Subtilität) `stages.ts:stageFor(correct)` mappt 0..1→0 (Boden), 2..3→1 (Luftballon), 4..6→2 (Wolken), 7..9→3 (Mond), 10+→4 (Sterne). Stufe 5 (Regenbogenland) ist **kein** stageFor-Rückgabewert, sondern die Win-Celebration im ResultScreen. `BRAINSTORM.md` listet 5 Stufen aber nur 4 Schwellen (2/4/7/10) — Sterne und Regenbogenland sind im Endspiel zusammengefasst. ### Sound `soundManager.ts` synthetisiert alle Sounds **generativ via WebAudio** (Oszillatoren + Noise-Bursts) — **keine Audio-Asset-Dateien** im Projekt. Mute global über `settings.soundOn`. Browser-Autoplay-Policy: `unlockAudio()` im ersten User-Tap aufrufen (HomeScreen, ResultScreen). ### PWA `vite-plugin-pwa` mit `registerType: 'autoUpdate'` generiert Service Worker und Manifest. Icons in `public/icons/` werden per `rsvg-convert` aus `favicon.svg` gerendert (192/512). Bei Manifest-Änderungen muss SW neu generiert werden (`npm run build`). ### Test-Setup `src/test-setup.ts` shimmt `localStorage`, weil **Node 25 ein kaputtes experimentelles `localStorage` mitbringt** (es überschreibt jsdoms Implementation, aber `clear` etc. fehlen). Der Setup-File ersetzt es vor jedem Test mit einer in-memory-Implementierung. Wenn neue jsdom-abhängige Globals nötig sind, dort hinzufügen. ## Styling-Konventionen - Farben aus CSS-Variablen in `src/app.css` (`--c-*`). Nicht hartcodieren. - Karten/Komponenten mit `aspect-ratio` brauchen Höhen-Constraints am Container, sonst Overflow auf breiten Schirmen — siehe `HomeScreen.svelte:.cards-grid` für die Formel `C·3·(H−(R−1)·g) / (4·R) + (C−1)·g`. - `100dvh` statt `100vh` (mobile Browser-Chrome). ## Pitfalls - **`isolatedModules: true`** in tsconfig: Type-Imports brauchen `import type`. - **Svelte 5 Runes**: Nicht mit `let`-reactivity (alter Svelte 4-Stil) mischen. `$effect` kann Cleanup-Function returnieren. - **`stageBumpKey`** als Trigger-Mechanismus: nur inkrementieren, wenn Stufe **wirklich** wechselt (nicht bei jeder richtigen Antwort) — siehe `game.ts:answer`.