4.9 KiB
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
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-ratiobrauchen Höhen-Constraints am Container, sonst Overflow auf breiten Schirmen — sieheHomeScreen.svelte:.cards-gridfür die FormelC·3·(H−(R−1)·g) / (4·R) + (C−1)·g. 100dvhstatt100vh(mobile Browser-Chrome).
Pitfalls
isolatedModules: truein tsconfig: Type-Imports brauchenimport type.- Svelte 5 Runes: Nicht mit
let-reactivity (alter Svelte 4-Stil) mischen.$effectkann Cleanup-Function returnieren. stageBumpKeyals Trigger-Mechanismus: nur inkrementieren, wenn Stufe wirklich wechselt (nicht bei jeder richtigen Antwort) — siehegame.ts:answer.