---
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">&#x1F50A;</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>
