docs(04-polish-audio): create phase plan
This commit is contained in:
+12
-5
@@ -89,7 +89,14 @@ Plans:
|
||||
3. Sprachausgabe liest Companion-Text vor wenn aktiviert (Web Speech API, Deutsch)
|
||||
4. Export und Import funktionieren: Exportieren → Browser-Daten loeschen → Importieren → Fortschritt wiederhergestellt
|
||||
5. Keine unbehandelten Exceptions in der Browser-Konsole bei normalem Gebrauch
|
||||
**Plans**: TBD
|
||||
**Plans:** 4 plans
|
||||
|
||||
Plans:
|
||||
- [ ] 04-01-PLAN.md — Audio system (Web Audio API sounds, Web Speech API speech, mute button)
|
||||
- [ ] 04-02-PLAN.md — Animation polish (leaf-fall, stardust, companion float), responsive, error handler
|
||||
- [ ] 04-03-PLAN.md — Export/Import/Reset in parent area
|
||||
- [ ] 04-04-PLAN.md — Wire audio into all screens, end-to-end human verification
|
||||
|
||||
**UI hint**: yes
|
||||
|
||||
## Progress
|
||||
@@ -99,7 +106,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4
|
||||
|
||||
| Phase | Plans Complete | Status | Completed |
|
||||
|-------|----------------|--------|-----------|
|
||||
| 1. Grundgerüst + Tippmechanik | 0/5 | Planning complete | - |
|
||||
| 2. Gemini-Integration + Asset-Pipeline | 0/5 | Planning complete | - |
|
||||
| 3. Komplettes Spielerlebnis | 0/5 | Planning complete | - |
|
||||
| 4. Polish + Audio | 0/? | Not started | - |
|
||||
| 1. Grundgerüst + Tippmechanik | 5/5 | Complete | - |
|
||||
| 2. Gemini-Integration + Asset-Pipeline | 5/5 | Complete | - |
|
||||
| 3. Komplettes Spielerlebnis | 5/5 | Complete | - |
|
||||
| 4. Polish + Audio | 0/4 | Planning complete | - |
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
---
|
||||
phase: 04-polish-audio
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- src/audio/sounds.ts
|
||||
- src/audio/speech.ts
|
||||
- src/types.ts
|
||||
- index.html
|
||||
- src/styles/main.css
|
||||
- src/main.ts
|
||||
autonomous: true
|
||||
requirements: [AUDI-01, AUDI-02, AUDI-03, AUDI-04, AUDI-05]
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "playCorrectSound() erzeugt 800Hz Sinus-Ton fuer 0.1s bei audioEnabled=true"
|
||||
- "playRewardSound() erzeugt aufsteigende Tonfolge fuer 0.8s"
|
||||
- "playForestElementSound() erzeugt gefiltertes weisses Rauschen fuer 0.5s"
|
||||
- "Mute-Button im DOM sichtbar, toggle aendert audioEnabled in Settings und persistiert"
|
||||
- "speakText() liest deutschen Text via Web Speech API vor wenn speechEnabled=true"
|
||||
- "AudioContext wird lazy bei erster User-Interaktion erstellt (Autoplay Policy)"
|
||||
artifacts:
|
||||
- path: "src/audio/sounds.ts"
|
||||
provides: "Web Audio API sound effects"
|
||||
exports: ["playCorrectSound", "playRewardSound", "playForestElementSound", "initAudioOnInteraction"]
|
||||
- path: "src/audio/speech.ts"
|
||||
provides: "Web Speech API text-to-speech"
|
||||
exports: ["speakText"]
|
||||
- path: "index.html"
|
||||
provides: "Mute button element outside screens"
|
||||
contains: "mute-btn"
|
||||
key_links:
|
||||
- from: "src/audio/sounds.ts"
|
||||
to: "src/storage/db.ts"
|
||||
via: "getSettings() check for audioEnabled"
|
||||
pattern: "getSettings.*audioEnabled"
|
||||
- from: "src/audio/speech.ts"
|
||||
to: "src/storage/db.ts"
|
||||
via: "getSettings() check for speechEnabled"
|
||||
pattern: "getSettings.*speechEnabled"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Audio-System fuer Zauberwald: Web Audio API Sound-Effekte (3 Sounds), Mute-Button, optionale Sprachausgabe via Web Speech API.
|
||||
|
||||
Purpose: Auditives Feedback macht das Tipperlebnis lebendig und belohnend -- per D-01 bis D-05 aus CONTEXT.md.
|
||||
Output: Zwei neue Module (sounds.ts, speech.ts), Mute-Button in index.html, speechEnabled Feld in Settings.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@$HOME/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/04-polish-audio/04-CONTEXT.md
|
||||
|
||||
@src/types.ts
|
||||
@src/storage/db.ts
|
||||
@src/main.ts
|
||||
@index.html
|
||||
@src/styles/main.css
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Audio module + Speech module + Settings extension</name>
|
||||
<files>src/audio/sounds.ts, src/audio/speech.ts, src/types.ts</files>
|
||||
<read_first>src/types.ts, src/storage/db.ts, .planning/phases/04-polish-audio/04-CONTEXT.md</read_first>
|
||||
<action>
|
||||
1. In `src/types.ts`: Add `speechEnabled: boolean` to the `Settings` interface (per D-05). Keep existing fields unchanged.
|
||||
|
||||
2. Create `src/audio/sounds.ts` (per D-01, D-02):
|
||||
- Module-level `let audioCtx: AudioContext | null = null`
|
||||
- `initAudioOnInteraction()`: Creates/resumes AudioContext on first call. Intended to be called from a user-interaction event handler. Export this.
|
||||
- Helper `isAudioEnabled()`: async, calls `getSettings(db)` from db.ts (needs db reference -- use a module-level `let db: IDBDatabase | null` set via an `setAudioDB(d)` function, or import `getDB()` from app.ts). Check `settings?.audioEnabled !== false`. Return boolean.
|
||||
- `playCorrectSound()`: async. Check `isAudioEnabled()`. If false, return. Ensure audioCtx exists (call initAudioOnInteraction if needed). Create OscillatorNode: type sine, frequency 800Hz, connect to gain (0.15), start, stop after 0.1s. Ramp gain to 0 over last 0.02s for click-free cutoff.
|
||||
- `playRewardSound()`: async. Check enabled. Ascending tone sequence: play 3-4 notes (e.g. C5=523, E5=659, G5=784, C6=1047) each ~0.2s apart, each 0.15s duration, sine wave, gain 0.12. Total ~0.8s.
|
||||
- `playForestElementSound()`: async. Check enabled. Create white noise buffer (AudioBuffer, fill with Math.random()*2-1), play through BiquadFilterNode (bandpass, frequency 3000Hz, Q 0.5), gain 0.1, duration 0.5s with gain ramp-down.
|
||||
- All functions: wrap in try/catch (never throw to caller).
|
||||
|
||||
3. Create `src/audio/speech.ts` (per D-04, D-05):
|
||||
- `speakText(text: string)`: async. Check `speechEnabled` from Settings via getSettings. If not enabled, return. Check `window.speechSynthesis` exists. Create `SpeechSynthesisUtterance(text)`, set `lang='de-DE'`, `rate=0.9`, `pitch=1.1`. Try to find a German voice (`getVoices().find(v => v.lang.startsWith('de'))`), set if found. Call `speechSynthesis.speak(utterance)`. Wrap in try/catch, silent fallback.
|
||||
- Handle voice loading: voices may load async. Use `speechSynthesis.onvoiceschanged` if `getVoices()` returns empty initially.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/dev/workspace/zauberwald && npx tsc --noEmit 2>&1 | head -20</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- src/audio/sounds.ts exports playCorrectSound, playRewardSound, playForestElementSound, initAudioOnInteraction
|
||||
- src/audio/speech.ts exports speakText
|
||||
- Settings interface in types.ts includes speechEnabled: boolean
|
||||
- All three sound functions check audioEnabled before playing
|
||||
- speakText checks speechEnabled before speaking
|
||||
- TypeScript compiles without errors
|
||||
</acceptance_criteria>
|
||||
<done>Three sound functions and speakText function exist, all check Settings before executing, TypeScript compiles clean</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Mute button in HTML + CSS + wiring in main.ts</name>
|
||||
<files>index.html, src/styles/main.css, src/main.ts</files>
|
||||
<read_first>index.html, src/main.ts, src/styles/main.css, src/storage/db.ts</read_first>
|
||||
<action>
|
||||
1. In `index.html` (per D-03): Add a mute button BEFORE the `<script>` tag, outside all `<section>` screens:
|
||||
```html
|
||||
<button id="mute-btn" class="mute-btn" aria-label="Audio stumm schalten" title="Audio">
|
||||
<span class="mute-btn__icon" id="mute-icon">🔊</span>
|
||||
</button>
|
||||
```
|
||||
Use speaker emoji (U+1F50A for on, U+1F507 for muted).
|
||||
|
||||
2. In `src/styles/main.css`: Add `.mute-btn` styles:
|
||||
- `position: fixed; bottom: 16px; right: 16px; z-index: 100;`
|
||||
- `width: 44px; height: 44px; border-radius: 50%;`
|
||||
- `background: var(--bg-cream); border: 2px solid var(--accent-green);`
|
||||
- `cursor: pointer; font-size: 20px; display: flex; align-items: center; justify-content: center;`
|
||||
- `box-shadow: 0 2px 8px rgba(0,0,0,0.1);`
|
||||
- `transition: transform 0.2s ease;`
|
||||
- `.mute-btn:hover { transform: scale(1.1); }`
|
||||
- `.mute-btn--muted { border-color: var(--text-light); opacity: 0.6; }`
|
||||
|
||||
3. In `src/main.ts`: After `initApp`, wire up the mute button:
|
||||
- Import `getSettings`, `saveSettings` from db.ts, `getDB` from app.ts, `initAudioOnInteraction` from audio/sounds.ts
|
||||
- On DOMContentLoaded (after initApp): read settings, set initial icon state
|
||||
- `muteBtn.onclick`: toggle `audioEnabled`, save to settings, update icon (U+1F50A vs U+1F507), toggle `.mute-btn--muted` class
|
||||
- Also call `initAudioOnInteraction()` on first mute-btn click (satisfies autoplay policy per D-02)
|
||||
- Add a one-time click/keydown listener on document body that calls `initAudioOnInteraction()` for autoplay policy compliance
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/dev/workspace/zauberwald && npx tsc --noEmit 2>&1 | head -20</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- index.html contains element with id="mute-btn" outside all section.screen elements
|
||||
- main.css contains .mute-btn with position: fixed
|
||||
- main.ts imports from audio/sounds and wires mute button click handler
|
||||
- Mute button toggles audioEnabled in Settings store
|
||||
- initAudioOnInteraction called on first user interaction
|
||||
</acceptance_criteria>
|
||||
<done>Mute button visible in fixed position, toggles audio state, persists across reload, AudioContext initialized on first interaction</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- `npx tsc --noEmit` passes
|
||||
- `npm run lint` passes (biome)
|
||||
- index.html has mute-btn element
|
||||
- src/audio/sounds.ts and src/audio/speech.ts exist with correct exports
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
Audio system ready to be wired into lesson/reward/forest screens (Plan 04). Mute button functional and persisting state.
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/04-polish-audio/04-01-SUMMARY.md`
|
||||
</output>
|
||||
@@ -0,0 +1,278 @@
|
||||
---
|
||||
phase: 04-polish-audio
|
||||
plan: 02
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- src/styles/main.css
|
||||
- src/main.ts
|
||||
autonomous: true
|
||||
requirements: [PLSH-01, PLSH-02, PLSH-03, PLSH-04, PLSH-05]
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "Fallende Buchstaben schweben seitlich wie Blaetter (sinusfoermiger Sway)"
|
||||
- "Sternenstaub-Particles erscheinen bei richtigem Tastendruck (CSS pseudo-elements)"
|
||||
- "Companion-Avatar schwebt sanft auf und ab"
|
||||
- "Bei 700px Breite: Tasten kleiner, Fonts skaliert, Grid angepasst"
|
||||
- "Unbehandelte Exceptions zeigen kindgerechtes Overlay mit Reload-Button"
|
||||
- "Alle Animationen respektieren prefers-reduced-motion"
|
||||
artifacts:
|
||||
- path: "src/styles/main.css"
|
||||
provides: "Leaf-fall animation, stardust particles, companion float, responsive, error overlay"
|
||||
contains: "@keyframes leaf-fall"
|
||||
- path: "src/main.ts"
|
||||
provides: "Global error handler"
|
||||
contains: "addEventListener.*error"
|
||||
key_links:
|
||||
- from: "src/styles/main.css"
|
||||
to: "index.html"
|
||||
via: "Error overlay element"
|
||||
pattern: "error-overlay"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Visuelles Polish: Verbesserte Animationen (Blaetter-Fall, Sternenstaub, Avatar-Schweben), Responsive bis 700px, globaler Error-Handler.
|
||||
|
||||
Purpose: Sanfte, kindgerechte Animationen und robuste Fehlerbehandlung per D-06 bis D-11 aus CONTEXT.md.
|
||||
Output: Erweiterte main.css mit neuen Animationen + Responsive, Error-Handler in main.ts + Overlay in index.html.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@$HOME/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/phases/04-polish-audio/04-CONTEXT.md
|
||||
|
||||
@src/styles/main.css
|
||||
@src/main.ts
|
||||
@index.html
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Enhanced CSS animations + responsive layout</name>
|
||||
<files>src/styles/main.css</files>
|
||||
<read_first>src/styles/main.css, .planning/phases/04-polish-audio/04-CONTEXT.md</read_first>
|
||||
<action>
|
||||
1. Replace `@keyframes letter-float` with `@keyframes leaf-fall` (per D-06):
|
||||
```css
|
||||
@keyframes leaf-fall {
|
||||
0% { transform: translateY(-20px) translateX(0) rotate(0deg); opacity: 0; }
|
||||
10% { opacity: 1; }
|
||||
25% { transform: translateY(25%) translateX(15px) rotate(5deg); }
|
||||
50% { transform: translateY(50%) translateX(-10px) rotate(-3deg); }
|
||||
75% { transform: translateY(75%) translateX(12px) rotate(4deg); }
|
||||
100% { transform: translateY(100%) translateX(-5px) rotate(-2deg); opacity: 0.8; }
|
||||
}
|
||||
```
|
||||
Update `.falling-letter` to use `animation: leaf-fall 2.5s ease-in-out infinite` instead of `letter-float`.
|
||||
|
||||
2. Enhance `.falling-letter--dissolve` with stardust particles (per D-07):
|
||||
Add CSS `::before` and `::after` pseudo-elements:
|
||||
```css
|
||||
.falling-letter--dissolve::before,
|
||||
.falling-letter--dissolve::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent-gold);
|
||||
animation: stardust 0.6s ease-out forwards;
|
||||
}
|
||||
.falling-letter--dissolve::before {
|
||||
top: 30%;
|
||||
left: 20%;
|
||||
animation-delay: 0s;
|
||||
}
|
||||
.falling-letter--dissolve::after {
|
||||
top: 40%;
|
||||
right: 20%;
|
||||
background: var(--accent-pink);
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
```
|
||||
Add `@keyframes stardust`:
|
||||
```css
|
||||
@keyframes stardust {
|
||||
0% { transform: scale(1) translate(0, 0); opacity: 1; }
|
||||
100% { transform: scale(0) translate(var(--dx, 20px), var(--dy, -30px)); opacity: 0; }
|
||||
}
|
||||
```
|
||||
Add 4 more particles via box-shadow on the pseudo-elements for more sparkle effect.
|
||||
|
||||
3. Add/enhance companion float animation (per D-08):
|
||||
```css
|
||||
.companion-area__avatar {
|
||||
animation: companion-float 3s ease-in-out infinite;
|
||||
}
|
||||
@keyframes companion-float {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-8px); }
|
||||
}
|
||||
```
|
||||
|
||||
4. Add `prefers-reduced-motion` (per D-09):
|
||||
```css
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.falling-letter,
|
||||
.companion-area__avatar,
|
||||
.forest__element-img {
|
||||
animation: none !important;
|
||||
}
|
||||
.falling-letter--dissolve::before,
|
||||
.falling-letter--dissolve::after {
|
||||
animation: none !important;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
5. Add responsive styles at bottom (per D-10):
|
||||
```css
|
||||
@media (max-width: 700px) {
|
||||
.keyboard__key {
|
||||
min-width: 32px;
|
||||
height: 32px;
|
||||
font-size: clamp(10px, 2.5vw, 14px);
|
||||
}
|
||||
.keyboard__key--space {
|
||||
min-width: 120px;
|
||||
}
|
||||
h1 { font-size: clamp(20px, 5vw, 32px); }
|
||||
h2 { font-size: clamp(18px, 4vw, 24px); }
|
||||
body { font-size: clamp(14px, 3.5vw, 16px); }
|
||||
.welcome__companions {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
.forest__grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-rows: repeat(4, 1fr);
|
||||
}
|
||||
.lesson__keyboard {
|
||||
transform: scale(0.85);
|
||||
transform-origin: top center;
|
||||
}
|
||||
}
|
||||
```
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/dev/workspace/zauberwald && grep -c "leaf-fall\|stardust\|companion-float\|prefers-reduced-motion\|max-width.*700" src/styles/main.css</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- main.css contains @keyframes leaf-fall with translateX sway
|
||||
- main.css contains @keyframes stardust on .falling-letter--dissolve pseudo-elements
|
||||
- main.css contains @keyframes companion-float on .companion-area__avatar
|
||||
- main.css contains @media (prefers-reduced-motion: reduce) disabling animations
|
||||
- main.css contains @media (max-width: 700px) with keyboard key size 32px
|
||||
- .falling-letter uses leaf-fall animation instead of letter-float
|
||||
</acceptance_criteria>
|
||||
<done>All animations enhanced with leaf-fall sway, stardust particles, companion float. Responsive down to 700px. prefers-reduced-motion respected.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Global error handler + error overlay</name>
|
||||
<files>index.html, src/main.ts, src/styles/main.css</files>
|
||||
<read_first>index.html, src/main.ts, src/styles/main.css</read_first>
|
||||
<action>
|
||||
1. In `index.html` (per D-11): Add error overlay element BEFORE the script tag, outside all sections:
|
||||
```html
|
||||
<div id="error-overlay" class="error-overlay" style="display:none">
|
||||
<div class="error-overlay__content">
|
||||
<p class="error-overlay__icon">🌳</p>
|
||||
<p class="error-overlay__message">Oh, der Wald braucht kurz eine Pause...</p>
|
||||
<button class="error-overlay__btn" onclick="location.reload()">Nochmal versuchen</button>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
2. In `src/styles/main.css`: Add error overlay styles:
|
||||
```css
|
||||
.error-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(255, 248, 240, 0.95);
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.error-overlay__content {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
.error-overlay__icon {
|
||||
font-size: 4rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.error-overlay__message {
|
||||
font-family: 'Quicksand', sans-serif;
|
||||
font-size: 1.25rem;
|
||||
color: var(--text-dark);
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.error-overlay__btn {
|
||||
font-family: 'Quicksand', sans-serif;
|
||||
font-size: 1rem;
|
||||
padding: 0.75rem 2rem;
|
||||
background: var(--accent-green);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
```
|
||||
|
||||
3. In `src/main.ts` (per D-11): Add global error handlers BEFORE DOMContentLoaded:
|
||||
```typescript
|
||||
window.addEventListener('error', (event) => {
|
||||
console.error('Global error:', event.error);
|
||||
const overlay = document.getElementById('error-overlay');
|
||||
if (overlay) overlay.style.display = 'flex';
|
||||
});
|
||||
window.addEventListener('unhandledrejection', (event) => {
|
||||
console.error('Unhandled rejection:', event.reason);
|
||||
const overlay = document.getElementById('error-overlay');
|
||||
if (overlay) overlay.style.display = 'flex';
|
||||
});
|
||||
```
|
||||
Keep the existing `DOMContentLoaded` listener intact.
|
||||
|
||||
NOTE: Plan 01 also modifies main.ts (mute button wiring). These changes are independent -- error handlers go at the top of the file, mute button wiring goes inside the DOMContentLoaded callback. If Plan 01 runs first, read the updated file. If this plan runs first, the other will read the updated file.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/dev/workspace/zauberwald && grep -c "error-overlay\|addEventListener.*error\|unhandledrejection" index.html src/main.ts src/styles/main.css</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- index.html contains div#error-overlay with child elements
|
||||
- main.ts contains window.addEventListener for both 'error' and 'unhandledrejection'
|
||||
- main.css contains .error-overlay styles with position: fixed and z-index: 9999
|
||||
- Error overlay shows "Oh, der Wald braucht kurz eine Pause..." message
|
||||
- Reload button calls location.reload()
|
||||
</acceptance_criteria>
|
||||
<done>Unhandled exceptions show kindgerechtes overlay with reload button. Console.error preserved for debugging.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- `npx tsc --noEmit` passes
|
||||
- `npm run lint` passes
|
||||
- grep confirms leaf-fall, stardust, companion-float, reduced-motion, 700px responsive in CSS
|
||||
- grep confirms error handler in main.ts
|
||||
- grep confirms error-overlay in index.html
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
Animations polished (leaf-fall sway, stardust particles, companion float), responsive to 700px, global error handler catching uncaught exceptions with child-friendly overlay.
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/04-polish-audio/04-02-SUMMARY.md`
|
||||
</output>
|
||||
@@ -0,0 +1,231 @@
|
||||
---
|
||||
phase: 04-polish-audio
|
||||
plan: 03
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- src/ui/parent.ts
|
||||
- index.html
|
||||
- src/styles/main.css
|
||||
- src/storage/db.ts
|
||||
autonomous: true
|
||||
requirements: [PRNT-05, PRNT-06, PRNT-07]
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "Export-Button ladet JSON-Datei mit Progress und Settings herunter (keine Blobs)"
|
||||
- "Import-Button oeffnet Dateiauswahl, validiert JSON, ueberschreibt nach Bestaetigung"
|
||||
- "Reset loescht alle 5 IndexedDB Stores nach doppelter Bestaetigung und reloaded"
|
||||
artifacts:
|
||||
- path: "src/ui/parent.ts"
|
||||
provides: "Export, Import, Reset functions"
|
||||
exports: ["exportData", "importData", "resetAllData"]
|
||||
- path: "index.html"
|
||||
provides: "Daten section in parent screen with Export/Import/Reset buttons"
|
||||
contains: "export-btn"
|
||||
key_links:
|
||||
- from: "src/ui/parent.ts"
|
||||
to: "src/storage/db.ts"
|
||||
via: "getProgress, getSettings for export; saveProgress, saveSettings for import; db.clear for reset"
|
||||
pattern: "getProgress.*getSettings|clear"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Export/Import/Reset im Elternbereich: Fortschritt als JSON exportieren, JSON importieren mit Validierung, alles zuruecksetzen mit doppelter Bestaetigung.
|
||||
|
||||
Purpose: Datensicherung und Reset-Moeglichkeit per D-12 bis D-14 aus CONTEXT.md. Eltern koennen Fortschritt sichern und bei Bedarf wiederherstellen.
|
||||
Output: Daten-Sektion im Parent-Screen mit drei Buttons, Logik in parent.ts.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@$HOME/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/phases/04-polish-audio/04-CONTEXT.md
|
||||
|
||||
@src/ui/parent.ts
|
||||
@src/storage/db.ts
|
||||
@src/types.ts
|
||||
@index.html
|
||||
@src/styles/main.css
|
||||
</context>
|
||||
|
||||
<interfaces>
|
||||
<!-- Key types the executor needs from existing code -->
|
||||
|
||||
From src/types.ts:
|
||||
```typescript
|
||||
export interface Progress {
|
||||
id: 1;
|
||||
currentLevel: number;
|
||||
completedLevels: number[];
|
||||
totalSessions: number;
|
||||
sessionDates: string[];
|
||||
selectedCharacter: CompanionType;
|
||||
selectedLayout: KeyboardLayout;
|
||||
totalCorrect: number;
|
||||
totalErrors: number;
|
||||
errorKeyCounts: Record<string, number>;
|
||||
lastPlayedLevels: number[];
|
||||
}
|
||||
|
||||
export interface Settings {
|
||||
id: 1;
|
||||
audioEnabled: boolean;
|
||||
apiKey: string;
|
||||
apiCallsToday: { text: number; image: number };
|
||||
lastApiCallDate: string;
|
||||
}
|
||||
```
|
||||
|
||||
From src/storage/db.ts:
|
||||
```typescript
|
||||
export function getProgress(db: IDBDatabase): Promise<Progress | null>;
|
||||
export function saveProgress(db: IDBDatabase, progress: Progress): Promise<void>;
|
||||
export function getSettings(db: IDBDatabase): Promise<Settings | null>;
|
||||
export function saveSettings(db: IDBDatabase, settings: Settings): Promise<void>;
|
||||
```
|
||||
|
||||
From src/ui/parent.ts:
|
||||
```typescript
|
||||
export function showParentCodePrompt(): void;
|
||||
export function initParentScreen(db: IDBDatabase, onBack: () => void): void;
|
||||
```
|
||||
</interfaces>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: HTML Daten-Section + Export/Import/Reset buttons</name>
|
||||
<files>index.html, src/styles/main.css</files>
|
||||
<read_first>index.html, src/styles/main.css</read_first>
|
||||
<action>
|
||||
1. In `index.html`: Add a third `<section>` inside `#parent-content`, after the Einstellungen section (per D-12, D-13, D-14):
|
||||
```html
|
||||
<section class="parent__section">
|
||||
<h2>Daten</h2>
|
||||
<div class="parent__data-actions">
|
||||
<button class="parent__action-btn" id="export-btn">Fortschritt exportieren</button>
|
||||
<div class="parent__import-row">
|
||||
<button class="parent__action-btn" id="import-btn">Fortschritt importieren</button>
|
||||
<input type="file" id="import-file" accept=".json" style="display:none">
|
||||
</div>
|
||||
<button class="parent__action-btn parent__action-btn--danger" id="reset-btn">Alles zuruecksetzen</button>
|
||||
</div>
|
||||
<p class="parent__data-info" id="data-feedback" style="display:none"></p>
|
||||
</section>
|
||||
```
|
||||
|
||||
2. In `src/styles/main.css`: Add styles for data section:
|
||||
```css
|
||||
.parent__data-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
max-width: 300px;
|
||||
}
|
||||
.parent__action-btn {
|
||||
font-family: 'Quicksand', sans-serif;
|
||||
font-size: 1rem;
|
||||
padding: 0.6rem 1.2rem;
|
||||
border: 2px solid var(--accent-green);
|
||||
background: var(--bg-cream);
|
||||
color: var(--text-dark);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.parent__action-btn:hover {
|
||||
background: var(--bg-forest);
|
||||
}
|
||||
.parent__action-btn--danger {
|
||||
border-color: #D46A6A;
|
||||
color: #D46A6A;
|
||||
}
|
||||
.parent__action-btn--danger:hover {
|
||||
background: #FDE8E8;
|
||||
}
|
||||
.parent__data-info {
|
||||
font-size: 0.9rem;
|
||||
color: var(--accent-green);
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
```
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/dev/workspace/zauberwald && grep -c "export-btn\|import-btn\|reset-btn\|import-file\|parent__action-btn" index.html src/styles/main.css</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- index.html contains section with h2 "Daten" inside parent-content
|
||||
- index.html contains buttons with ids export-btn, import-btn, reset-btn
|
||||
- index.html contains hidden input#import-file with accept=".json"
|
||||
- main.css contains .parent__action-btn and .parent__action-btn--danger styles
|
||||
</acceptance_criteria>
|
||||
<done>Daten section visible in parent area with Export, Import, Reset buttons properly styled</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Export/Import/Reset logic in parent.ts</name>
|
||||
<files>src/ui/parent.ts, src/storage/db.ts</files>
|
||||
<read_first>src/ui/parent.ts, src/storage/db.ts, src/types.ts</read_first>
|
||||
<action>
|
||||
1. In `src/ui/parent.ts`, add Export function (per D-12):
|
||||
- `async function exportData(db: IDBDatabase)`: Get progress + settings via existing functions. Build JSON object `{ version: 1, exportedAt: new Date().toISOString(), progress, settings }`. Create Blob, create object URL, create temporary `<a>` link with download="zauberwald-backup.json", click it, revoke URL. Show feedback "Exportiert!" in `#data-feedback`.
|
||||
|
||||
2. Add Import function (per D-13):
|
||||
- `async function importData(db: IDBDatabase, file: File)`: Read file as text. Parse JSON. Validate structure:
|
||||
- Must have `progress` object with required fields (currentLevel: number, completedLevels: array, totalSessions: number, selectedCharacter: string, selectedLayout: string)
|
||||
- Must have `settings` object with required fields (audioEnabled: boolean)
|
||||
- If validation fails, show error in `#data-feedback` ("Ungueltige Datei") and return
|
||||
- Show confirm dialog: `confirm("Fortschritt ueberschreiben? Aktuelle Daten gehen verloren.")`
|
||||
- If confirmed: `saveProgress(db, { ...parsed.progress, id: 1 })`, `saveSettings(db, { ...parsed.settings, id: 1 })`. Show "Importiert!" feedback. Reload after 1.5s.
|
||||
|
||||
3. Add Reset function (per D-14):
|
||||
- `async function resetAllData(db: IDBDatabase)`: First confirm: `confirm("Wirklich alles loeschen?")`. Second confirm: `confirm("Alle Fortschritte und Einstellungen werden geloescht. Bist du sicher?")`. If both confirmed:
|
||||
- Clear all 5 stores: `db.transaction(storeName, 'readwrite').objectStore(storeName).clear()` for each of: progress, companionAssets, styleReference, forestElements, settings
|
||||
- After all clear: `location.reload()`
|
||||
- Optionally add a helper `clearStore(db, name)` returning a Promise.
|
||||
|
||||
4. Wire buttons in `loadParentData()` (called after code entry):
|
||||
- `document.getElementById('export-btn')!.onclick = () => exportData(db)`
|
||||
- `document.getElementById('import-btn')!.onclick = () => document.getElementById('import-file')!.click()`
|
||||
- `document.getElementById('import-file')!.addEventListener('change', (e) => { const file = (e.target as HTMLInputElement).files?.[0]; if (file) importData(db, file); })`
|
||||
- `document.getElementById('reset-btn')!.onclick = () => resetAllData(db)`
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/dev/workspace/zauberwald && npx tsc --noEmit 2>&1 | head -20</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- parent.ts contains exportData function that creates JSON download
|
||||
- parent.ts contains importData function that validates and confirms before overwriting
|
||||
- parent.ts contains resetAllData function with double confirmation
|
||||
- Export excludes Blobs (only progress + settings)
|
||||
- Import validates required fields before saving
|
||||
- Reset clears all 5 IndexedDB stores
|
||||
- All three buttons wired in loadParentData
|
||||
- TypeScript compiles without errors
|
||||
</acceptance_criteria>
|
||||
<done>Export downloads zauberwald-backup.json, Import validates+confirms+overwrites, Reset double-confirms+clears+reloads</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- `npx tsc --noEmit` passes
|
||||
- `npm run lint` passes
|
||||
- grep confirms export-btn, import-btn, reset-btn in index.html
|
||||
- grep confirms exportData, importData, resetAllData in parent.ts
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
Parent area has working Export (JSON download), Import (file upload + validation + confirmation), and Reset (double confirmation + full wipe).
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/04-polish-audio/04-03-SUMMARY.md`
|
||||
</output>
|
||||
@@ -0,0 +1,190 @@
|
||||
---
|
||||
phase: 04-polish-audio
|
||||
plan: 04
|
||||
type: execute
|
||||
wave: 2
|
||||
depends_on: ["04-01", "04-02", "04-03"]
|
||||
files_modified:
|
||||
- src/ui/screens.ts
|
||||
- src/forest/reward.ts
|
||||
- src/forest/scene.ts
|
||||
- src/app.ts
|
||||
autonomous: false
|
||||
|
||||
requirements: [PLSH-01, PLSH-02, PLSH-03, PLSH-04, PLSH-05, AUDI-01, AUDI-02, AUDI-03, AUDI-04, AUDI-05, PRNT-05, PRNT-06, PRNT-07]
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "Richtiger Tastendruck in lesson spielt kurzen Ton"
|
||||
- "Belohnungs-Screen spielt aufsteigende Tonfolge"
|
||||
- "Neues Waldelement in Forest spielt Glitzer-Sound"
|
||||
- "Companion-Texte werden vorgelesen wenn speechEnabled=true"
|
||||
- "Gesamte App funktioniert fehlerfrei als zusammenhaengendes Erlebnis"
|
||||
artifacts:
|
||||
- path: "src/ui/screens.ts"
|
||||
provides: "playCorrectSound() call on correct keypress"
|
||||
contains: "playCorrectSound"
|
||||
- path: "src/forest/reward.ts"
|
||||
provides: "playRewardSound() + speakText() calls"
|
||||
contains: "playRewardSound"
|
||||
- path: "src/forest/scene.ts"
|
||||
provides: "playForestElementSound() on new element"
|
||||
contains: "playForestElementSound"
|
||||
key_links:
|
||||
- from: "src/ui/screens.ts"
|
||||
to: "src/audio/sounds.ts"
|
||||
via: "import + call in correct keypress handler"
|
||||
pattern: "import.*playCorrectSound"
|
||||
- from: "src/forest/reward.ts"
|
||||
to: "src/audio/sounds.ts"
|
||||
via: "import + call in reward init"
|
||||
pattern: "import.*playRewardSound"
|
||||
- from: "src/forest/scene.ts"
|
||||
to: "src/audio/sounds.ts"
|
||||
via: "import + call on element appear"
|
||||
pattern: "import.*playForestElementSound"
|
||||
- from: "src/forest/reward.ts"
|
||||
to: "src/audio/speech.ts"
|
||||
via: "import + speakText for companion comment"
|
||||
pattern: "import.*speakText"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Wire audio into all touch points and verify the complete Phase 4 experience end-to-end.
|
||||
|
||||
Purpose: Connect the audio system (Plan 01), animations (Plan 02), and export/import (Plan 03) into a cohesive experience. Human verification confirms everything works together.
|
||||
Output: Fully wired audio in lesson/reward/forest, human-verified complete app.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@$HOME/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/phases/04-polish-audio/04-CONTEXT.md
|
||||
@.planning/phases/04-polish-audio/04-01-SUMMARY.md
|
||||
@.planning/phases/04-polish-audio/04-02-SUMMARY.md
|
||||
@.planning/phases/04-polish-audio/04-03-SUMMARY.md
|
||||
|
||||
@src/ui/screens.ts
|
||||
@src/forest/reward.ts
|
||||
@src/forest/scene.ts
|
||||
@src/app.ts
|
||||
@src/audio/sounds.ts
|
||||
@src/audio/speech.ts
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Wire audio + speech into lesson, reward, forest, and app</name>
|
||||
<files>src/ui/screens.ts, src/forest/reward.ts, src/forest/scene.ts, src/app.ts</files>
|
||||
<read_first>src/ui/screens.ts, src/forest/reward.ts, src/forest/scene.ts, src/app.ts, src/audio/sounds.ts, src/audio/speech.ts</read_first>
|
||||
<action>
|
||||
1. In `src/ui/screens.ts` (per AUDI-01):
|
||||
- Import `playCorrectSound` from `../audio/sounds`
|
||||
- In the keydown handler where `result.correct` is true (two locations: letter mode ~line 214, word mode ~line 289), add `playCorrectSound()` call. Fire-and-forget (no await needed, sound functions handle errors internally).
|
||||
|
||||
2. In `src/forest/reward.ts` (per AUDI-02, AUDI-05):
|
||||
- Import `playRewardSound` from `../audio/sounds`
|
||||
- Import `speakText` from `../audio/speech`
|
||||
- In `initRewardScreen` (or wherever the reward is displayed), call `playRewardSound()` when the reward image/content appears.
|
||||
- After companion comment text is set, call `speakText(commentText)` to read it aloud.
|
||||
|
||||
3. In `src/forest/scene.ts` (per AUDI-03):
|
||||
- Import `playForestElementSound` from `../audio/sounds`
|
||||
- When a new forest element is rendered/appears (the element with `forest-element-appear` animation), call `playForestElementSound()`.
|
||||
|
||||
4. In `src/app.ts` (per AUDI-05):
|
||||
- Import `speakText` from `../audio/speech`
|
||||
- Where the companion greeting text is displayed on the forest screen, call `speakText(greetingText)` to read it aloud.
|
||||
|
||||
All audio calls are fire-and-forget. The sound/speech modules internally check settings and handle errors. No try/catch needed at call sites.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/dev/workspace/zauberwald && npx tsc --noEmit 2>&1 | head -20 && grep -c "playCorrectSound\|playRewardSound\|playForestElementSound\|speakText" src/ui/screens.ts src/forest/reward.ts src/forest/scene.ts src/app.ts</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- screens.ts imports and calls playCorrectSound on correct keypress
|
||||
- reward.ts imports and calls playRewardSound when reward displays
|
||||
- reward.ts imports and calls speakText for companion comment
|
||||
- scene.ts imports and calls playForestElementSound on new element
|
||||
- app.ts imports and calls speakText for greeting
|
||||
- TypeScript compiles without errors
|
||||
</acceptance_criteria>
|
||||
<done>All audio touch points wired: correct keystroke sound, reward melody, forest element glitter, speech for companion texts</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<name>Task 2: End-to-end verification of Phase 4</name>
|
||||
<files>none</files>
|
||||
<action>Human verifies the complete Phase 4 experience by testing all audio, animation, responsive, error handling, and export/import/reset features in the browser.</action>
|
||||
<what-built>
|
||||
Complete Phase 4 polish: audio system (3 sounds + speech), enhanced animations (leaf-fall, stardust, companion float), responsive layout, error handler, export/import/reset.
|
||||
</what-built>
|
||||
<how-to-verify>
|
||||
Start the dev server: `npm run dev`
|
||||
|
||||
Open http://<vps-ip>:5173 in browser.
|
||||
|
||||
**1. Audio (AUDI-01..03):**
|
||||
- Start a lesson, type correct letters -- hear short tone on each correct press
|
||||
- Complete a lesson -- hear ascending reward melody on reward screen
|
||||
- Go back to forest -- hear glitter sound when new element appears
|
||||
- If no sound: check mute button state
|
||||
|
||||
**2. Mute Button (AUDI-04):**
|
||||
- Click mute button (bottom-right) -- icon changes to muted
|
||||
- Type correct letters -- no sound
|
||||
- Reload page -- mute state preserved
|
||||
- Click again -- unmuted, sounds resume
|
||||
|
||||
**3. Speech (AUDI-05):**
|
||||
- Enable speech in parent area settings (if toggle exists) or check if companion texts are read aloud
|
||||
- Greeting on forest screen should be spoken in German
|
||||
|
||||
**4. Animations (PLSH-01..03):**
|
||||
- Falling letters sway side-to-side like leaves (not just float up/down)
|
||||
- Correct keypress shows sparkle/stardust particles
|
||||
- Companion avatar gently bobs up and down
|
||||
|
||||
**5. Responsive (PLSH-04):**
|
||||
- Resize browser to < 700px width -- keyboard shrinks, fonts scale, grid adjusts
|
||||
|
||||
**6. Error Handler (PLSH-05):**
|
||||
- Open console, run: `throw new Error('test')` -- child-friendly overlay appears
|
||||
- Click "Nochmal versuchen" -- page reloads
|
||||
|
||||
**7. Export/Import/Reset (PRNT-05..07):**
|
||||
- Open parent area (Ctrl+Shift+E, code 1234)
|
||||
- Click "Fortschritt exportieren" -- JSON file downloads
|
||||
- Click "Fortschritt importieren" -- file picker opens, select the exported file, confirm
|
||||
- Click "Alles zuruecksetzen" -- two confirmations, then app resets to welcome screen
|
||||
|
||||
**8. No console errors:**
|
||||
- Play through a complete lesson cycle with console open -- no unhandled errors
|
||||
</how-to-verify>
|
||||
<verify>Human confirms all 8 verification areas pass</verify>
|
||||
<done>All Phase 4 features verified: audio plays at correct moments, mute persists, animations smooth, responsive works, error handler catches exceptions, export/import/reset functional</done>
|
||||
<resume-signal>Type "approved" or describe issues to fix</resume-signal>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- TypeScript compiles: `npx tsc --noEmit`
|
||||
- Linting passes: `npm run lint`
|
||||
- All audio imports present in screens.ts, reward.ts, scene.ts, app.ts
|
||||
- Human verifies complete flow
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
App is visuell und auditiv angenehm, alle Phase 4 requirements met, bereit zum Testen mit dem Kind.
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/04-polish-audio/04-04-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user