zahlzerlegung/CLAUDE.md
2026-04-28 01:54:27 +02:00

4.9 KiB
Raw Blame History

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 410 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. 8496 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-ratio brauchen Höhen-Constraints am Container, sonst Overflow auf breiten Schirmen — siehe HomeScreen.svelte:.cards-grid für die Formel C·3·(H(R1)·g) / (4·R) + (C1)·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.