---
phase: 03-komplettes-spielerlebnis
plan: 04
type: execute
wave: 3
depends_on: [03-01, 03-02, 03-03]
files_modified:
  - src/app.ts
  - src/forest/reward.ts
  - index.html
  - src/styles/main.css
autonomous: true
requirements: [RWRD-01, RWRD-02, RWRD-03, RWRD-04, TYPE-07]
must_haves:
  truths:
    - "After lesson completion, reward screen shows with new forest element image"
    - "Image pre-generation starts at lesson begin and is awaited on reward screen"
    - "Placeholder 'Der Wald denkt nach...' with leaf animation shows while image loads"
    - "Reward screen has 'Weiter ueben' and 'Zurueck zum Wald' buttons"
    - "Full flow works: forest -> lesson (3 phases) -> reward -> forest"
    - "New element persisted in IndexedDB and visible in forest after reward"
  artifacts:
    - path: "src/forest/reward.ts"
      provides: "generateForestReward() pre-generation, initRewardScreen() display"
      exports: ["startPreGeneration", "initRewardScreen"]
    - path: "src/app.ts"
      provides: "Updated flow: lesson -> reward -> forest, review/repeat triggers"
      contains: "showScreen.*reward"
    - path: "index.html"
      provides: "Reward screen HTML structure"
      contains: "screen-reward"
  key_links:
    - from: "src/app.ts"
      to: "src/forest/reward.ts"
      via: "startPreGeneration at lesson start, initRewardScreen at lesson end"
      pattern: "startPreGeneration"
    - from: "src/forest/reward.ts"
      to: "src/api/gemini.ts"
      via: "generateImage() for forest element"
      pattern: "generateImage"
    - from: "src/forest/reward.ts"
      to: "src/storage/db.ts"
      via: "saveForestElement() to persist"
      pattern: "saveForestElement"
    - from: "src/app.ts"
      to: "src/forest/scene.ts"
      via: "renderForestScene(db, newElementId) after reward"
      pattern: "renderForestScene"
---

<objective>
Wire the complete game flow: lesson -> reward -> forest. Build reward screen with pre-generated forest element image, companion comment, and navigation buttons. Connect review/repetition triggers in app.ts.

Purpose: This closes the game loop — the child finishes a lesson, sees a magical reward, and returns to a growing forest.
Output: Reward module, reward screen HTML/CSS, updated app.ts with full flow including review/repetition logic.
</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/03-komplettes-spielerlebnis/03-01-SUMMARY.md
@.planning/phases/03-komplettes-spielerlebnis/03-02-SUMMARY.md
@.planning/phases/03-komplettes-spielerlebnis/03-03-SUMMARY.md
@src/app.ts
@src/api/gemini.ts
@src/companion/companion.ts
@src/storage/db.ts
@src/forest/scene.ts
@src/types.ts
@index.html
@src/companion/fallbacks.ts

<interfaces>
<!-- From src/api/gemini.ts (existing): -->
export async function generateImage(prompt: string, referenceImages: Blob[], config: GeminiConfig): Promise<Blob | null>;
export async function generateText(prompt: string, systemPrompt: string, config: GeminiConfig): Promise<string | null>;
export async function checkRateLimit(db: IDBDatabase, type: 'text' | 'image'): Promise<boolean>;
export async function incrementApiCall(db: IDBDatabase, type: 'text' | 'image'): Promise<void>;

<!-- From src/storage/db.ts (existing): -->
export function saveForestElement(db: IDBDatabase, element: ForestElement): Promise<number>;
export function getForestElements(db: IDBDatabase): Promise<ForestElement[]>;
export function getStyleReference(db: IDBDatabase): Promise<StyleReference | null>;

<!-- From src/companion/companion.ts (existing): -->
export async function getForestComment(characterType: CompanionType, elementDescription: string, db: IDBDatabase): Promise<string>;

<!-- From src/companion/fallbacks.ts (existing): -->
export function getRandomFallbackForestComment(): string;
import { getRandomFallbackImage } from './fallbacks'; // returns SVG blob

<!-- From src/forest/scene.ts (after Plan 03): -->
export async function renderForestScene(db: IDBDatabase, newElementId?: number): Promise<void>;
export async function initForestScreen(db: IDBDatabase, onStartLesson: (level: number) => void): Promise<void>;

<!-- From src/ui/screens.ts (after Plan 02): -->
export function initLessonScreen(
  db: IDBDatabase, level: number, layout: KeyboardLayout,
  onComplete: (level: number, stats: { totalCorrect: number; totalErrors: number; errorKeys: Record<string, number> }) => void,
  onCancel?: () => void,
  isReview?: boolean, showEncouragement?: boolean,
): () => void;

<!-- From src/types.ts (after Plan 01): -->
export interface Progress {
  ...
  totalCorrect: number;
  totalErrors: number;
  errorKeyCounts: Record<string, number>;
  lastPlayedLevels: number[];
}
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Reward module with pre-generation and reward screen</name>
  <files>src/forest/reward.ts, index.html, src/styles/main.css</files>
  <read_first>src/api/gemini.ts, src/storage/db.ts, src/companion/companion.ts, src/companion/fallbacks.ts, src/types.ts, index.html, src/styles/main.css</read_first>
  <action>
    **Create `src/forest/reward.ts`** (per D-10, D-11, D-12):

    ```typescript
    import { loadConfig } from '../api/config';
    import { generateImage, checkRateLimit, incrementApiCall } from '../api/gemini';
    import { getStyleReference, saveForestElement, getForestElements } from '../storage/db';
    import { getForestComment } from '../companion/companion';
    import { getRandomFallbackForestComment } from '../companion/fallbacks';
    import type { CompanionType, ForestElement } from '../types';
    ```

    **Pre-generation (per D-11, RWRD-02):**
    ```typescript
    let pregenPromise: Promise<{ blob: Blob; description: string }> | null = null;

    export function startPreGeneration(db: IDBDatabase): void {
      pregenPromise = generateForestElement(db);
    }
    ```

    `generateForestElement(db)` async function:
    1. Check rate limit for 'image': `await checkRateLimit(db, 'image')`
    2. If rate-limited, return fallback: use `getRandomFallbackImage()` from fallbacks.ts
       - NOTE: Check if `getRandomFallbackImage` exists. If not, create inline fallback: load one of the SVGs from `src/assets/fallback-images/` via fetch.
       - Actually, look at existing fallbacks.ts — it has `getRandomFallbackImage` or similar. If not, we need a simple function that picks a random fallback SVG and returns it as a Blob.
    3. Load config via `loadConfig()`. If no config, use fallback.
    4. Get style reference via `getStyleReference(db)`. Build referenceImages array (style ref blob if available).
    5. Pick random forest element type from a list: `['Blume', 'Pilz', 'Schmetterling', 'Vogel', 'Reh', 'Eichhoernchen', 'Hase', 'Frosch', 'Igel', 'Marienkaefer', 'Libelle', 'Schnecke']`
    6. Build prompt: `"Kinderbuch-Illustration, Aquarell-Stil, ${elementType}, magischer Wald, warm, einladend, fuer Kinder, Pastellfarben, kein Text, einzelnes Element auf transparentem Hintergrund"`
    7. Call `generateImage(prompt, referenceImages, config)`
    8. If result null, use fallback
    9. Increment API call: `await incrementApiCall(db, 'image')`
    10. Return `{ blob, description: elementType }`

    **Fallback image function:**
    Add to reward.ts:
    ```typescript
    const FALLBACK_SVGS = [
      '/src/assets/fallback-images/blume.svg',
      '/src/assets/fallback-images/pilz.svg',
      '/src/assets/fallback-images/vogel.svg',
      '/src/assets/fallback-images/schmetterling.svg',
      '/src/assets/fallback-images/reh.svg',
      '/src/assets/fallback-images/baum.svg',
      '/src/assets/fallback-images/bach.svg',
    ];

    async function getRandomFallbackImage(): Promise<{ blob: Blob; description: string }> {
      const idx = Math.floor(Math.random() * FALLBACK_SVGS.length);
      const path = FALLBACK_SVGS[idx]!;
      const name = path.split('/').pop()!.replace('.svg', '');
      try {
        const res = await fetch(path);
        const blob = await res.blob();
        return { blob, description: name };
      } catch {
        // Ultimate fallback: empty SVG blob
        const svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="50" r="40" fill="#E8F5E4"/></svg>';
        return { blob: new Blob([svg], { type: 'image/svg+xml' }), description: 'element' };
      }
    }
    ```

    **initRewardScreen (per RWRD-01, RWRD-03, RWRD-04):**
    ```typescript
    export async function initRewardScreen(
      db: IDBDatabase,
      level: number,
      characterType: CompanionType,
      onContinue: () => void,
      onBackToForest: () => void,
    ): Promise<number> // returns saved element ID
    ```

    1. Show placeholder immediately: set `#reward-image` area to placeholder HTML:
       ```html
       <div class="reward__placeholder">
         <span class="reward__leaf reward__leaf--1">🍃</span>
         <span class="reward__leaf reward__leaf--2">🍂</span>
         <span class="reward__leaf reward__leaf--3">🍃</span>
         <p>Der Wald denkt nach...</p>
       </div>
       ```
    2. Await `pregenPromise` (or `generateForestElement(db)` if promise is null)
    3. Get companion comment: `await getForestComment(characterType, result.description, db)`
    4. Determine grid position for new element: count existing elements, position = `{ x: col, y: row }` based on next free slot in 4x3 grid (slot index = existingCount % 12). Add random offset +-15px.
    5. Save to IndexedDB:
       ```typescript
       const element: ForestElement = {
         levelCompleted: level,
         imageBlob: result.blob,
         imagePrompt: prompt,
         description: result.description,
         companionText: comment,
         position: { x: col, y: row },
         createdAt: new Date().toISOString(),
       };
       const elementId = await saveForestElement(db, element);
       ```
    6. Replace placeholder with actual image: `URL.createObjectURL(result.blob)` in `<img>` tag
    7. Show companion comment in `#reward-companion-text`
    8. Wire buttons: `#reward-continue-btn` -> onContinue, `#reward-back-btn` -> onBackToForest
    9. Return elementId (for forest scene animation)

    **Update index.html** — replace empty `<section id="screen-reward">`:
    ```html
    <section id="screen-reward" class="screen">
      <div class="reward">
        <h2 class="reward__title">Schau mal!</h2>
        <div class="reward__image-area" id="reward-image-area">
          <!-- Placeholder or generated image -->
        </div>
        <div class="reward__companion" id="reward-companion">
          <img id="reward-companion-avatar" class="companion-area__avatar" src="" alt="">
          <p id="reward-companion-text" class="companion-area__text"></p>
        </div>
        <div class="reward__actions">
          <button class="reward__btn reward__btn--continue" id="reward-continue-btn">Weiter ueben</button>
          <button class="reward__btn reward__btn--back" id="reward-back-btn">Zurueck zum Wald</button>
        </div>
      </div>
    </section>
    ```

    **Add CSS to `src/styles/main.css`:**
    ```css
    /* Reward Screen */
    .reward {
      display: flex;
      flex-direction: column;
      align-items: center;
      padding: 2rem;
      max-width: 600px;
      margin: 0 auto;
      text-align: center;
    }

    .reward__title {
      font-family: var(--font-heading);
      color: var(--accent-gold);
      font-size: 1.8rem;
      margin-bottom: 1.5rem;
    }

    .reward__image-area {
      width: 280px;
      height: 280px;
      display: flex;
      align-items: center;
      justify-content: center;
      margin-bottom: 1.5rem;
      border-radius: 16px;
      background: var(--bg-forest);
      overflow: hidden;
    }

    .reward__image-area img {
      max-width: 100%;
      max-height: 100%;
      object-fit: contain;
      animation: reward-image-appear 0.8s ease-out;
    }

    @keyframes reward-image-appear {
      from { opacity: 0; transform: scale(0.5); }
      to { opacity: 1; transform: scale(1); }
    }

    .reward__placeholder {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 0.5rem;
      color: var(--text-warm);
      font-family: var(--font-heading);
    }

    .reward__leaf {
      font-size: 1.5rem;
      animation: leaf-float 2s ease-in-out infinite;
    }

    .reward__leaf--1 { animation-delay: 0s; }
    .reward__leaf--2 { animation-delay: 0.5s; }
    .reward__leaf--3 { animation-delay: 1s; }

    @keyframes leaf-float {
      0%, 100% { transform: translateY(0) rotate(0deg); }
      50% { transform: translateY(-10px) rotate(15deg); }
    }

    .reward__companion {
      display: flex;
      align-items: center;
      gap: 1rem;
      margin-bottom: 1.5rem;
      padding: 1rem;
      background: rgba(255, 255, 255, 0.7);
      border-radius: 12px;
    }

    .reward__actions {
      display: flex;
      gap: 1rem;
    }

    .reward__btn {
      font-family: var(--font-heading);
      font-size: 1.1rem;
      padding: 0.75rem 1.5rem;
      border: none;
      border-radius: 12px;
      cursor: pointer;
      transition: transform 0.2s;
    }

    .reward__btn:hover {
      transform: scale(1.05);
    }

    .reward__btn--continue {
      background: var(--accent-green);
      color: white;
    }

    .reward__btn--back {
      background: var(--bg-cream);
      color: var(--text-warm);
      border: 2px solid var(--accent-green);
    }
    ```
  </action>
  <verify>
    <automated>npx tsc --noEmit && grep -c "startPreGeneration" src/forest/reward.ts && grep -c "initRewardScreen" src/forest/reward.ts && grep -c "reward-continue-btn" index.html</automated>
  </verify>
  <acceptance_criteria>
    - grep "startPreGeneration" src/forest/reward.ts returns export
    - grep "initRewardScreen" src/forest/reward.ts returns export
    - grep "generateImage" src/forest/reward.ts shows image generation call
    - grep "saveForestElement" src/forest/reward.ts shows persistence
    - grep "Der Wald denkt nach" src/forest/reward.ts shows placeholder text
    - grep "reward-continue-btn" index.html returns match
    - grep "reward-back-btn" index.html returns match
    - grep "reward__placeholder" src/styles/main.css returns match
    - grep "leaf-float" src/styles/main.css returns match (animated leaves)
    - npx tsc --noEmit exits 0
  </acceptance_criteria>
  <done>Reward module with pre-generation, placeholder, fallback, IndexedDB persistence. Reward screen HTML with image area, companion comment, two action buttons. All CSS styled.</done>
</task>

<task type="auto">
  <name>Task 2: Wire complete flow in app.ts with review/repeat triggers</name>
  <files>src/app.ts</files>
  <read_first>src/app.ts, src/forest/reward.ts, src/forest/scene.ts, src/ui/screens.ts, src/types.ts, src/storage/db.ts, src/companion/characters.ts</read_first>
  <action>
    Rewrite flow logic in `src/app.ts` (per D-10, D-11, D-05, D-06, TYPE-07):

    **Import additions:**
    ```typescript
    import { startPreGeneration, initRewardScreen } from './forest/reward';
    import { renderForestScene } from './forest/scene';
    ```

    **Update `startLessonFromForest(level: number)`:**
    1. Determine if this is a review session (per D-05): `progress.totalSessions > 0 && progress.totalSessions % 3 === 0`
    2. Determine if encouragement needed (per D-06): check `progress.lastPlayedLevels` — if last 2 entries are the same level as current, set `showEncouragement = true`
    3. Start pre-generation: `startPreGeneration(db)` — fire and forget (per D-11)
    4. Call updated `initLessonScreen(db, level, layout, handleLessonComplete, handleLessonCancel, isReview, showEncouragement)`

    **Update `handleLessonComplete`:**
    Change signature to accept stats:
    ```typescript
    async function handleLessonComplete(
      completedLevel: number,
      stats: { totalCorrect: number; totalErrors: number; errorKeys: Record<string, number> }
    ): Promise<void>
    ```

    1. Update progress with stats (per D-15, D-16):
       ```typescript
       progress.totalCorrect += stats.totalCorrect;
       progress.totalErrors += stats.totalErrors;
       for (const [key, count] of Object.entries(stats.errorKeys)) {
         progress.errorKeyCounts[key] = (progress.errorKeyCounts[key] ?? 0) + count;
       }
       // Track last played levels (keep last 3)
       progress.lastPlayedLevels.push(completedLevel);
       if (progress.lastPlayedLevels.length > 3) {
         progress.lastPlayedLevels = progress.lastPlayedLevels.slice(-3);
       }
       ```
    2. Existing level advancement logic (keep as-is)
    3. Save progress
    4. Show REWARD screen instead of going directly to forest:
       ```typescript
       showScreen('reward');
       const elementId = await initRewardScreen(
         db,
         completedLevel,
         progress.selectedCharacter,
         () => {
           // "Weiter ueben" — start next lesson
           startLessonFromForest(Math.min(progress.currentLevel, MAX_LEVEL));
         },
         async () => {
           // "Zurueck zum Wald" — show forest with new element animated
           await initForestScreen(db, startLessonFromForest);
           await renderForestScene(db, elementId);
           showScreen('forest');
         },
       );
       ```

    **Set companion avatar on reward screen:**
    After `showScreen('reward')`, set the reward companion avatar:
    ```typescript
    const companion = companions.find(c => c.type === progress.selectedCharacter);
    if (companion) {
      const avatar = document.getElementById('reward-companion-avatar') as HTMLImageElement;
      avatar.src = companion.avatarUrl;
      avatar.alt = companion.name;
    }
    ```

    **Handle backward compatibility:**
    The `handleLessonComplete` in `startLessonFromForest` is passed as callback. Make sure the `initLessonScreen` onComplete signature matches (it was updated in Plan 02 to pass stats).

    **Handle missing fields in existing progress data:**
    When loading progress, ensure new fields have defaults:
    ```typescript
    if (!progress.errorKeyCounts) progress.errorKeyCounts = {};
    if (!progress.lastPlayedLevels) progress.lastPlayedLevels = [];
    if (progress.totalCorrect === undefined) progress.totalCorrect = 0;
    if (progress.totalErrors === undefined) progress.totalErrors = 0;
    ```
    Add this migration logic after `getProgress(db)` calls.

    **Parent area shortcut:**
    Add global keydown listener for Ctrl+Shift+E (this will be used by Plan 05, but wire the detection here):
    ```typescript
    document.addEventListener('keydown', (e) => {
      if (e.ctrlKey && e.shiftKey && e.key === 'E') {
        e.preventDefault();
        showParentCodePrompt();
      }
    });
    ```
    For now, `showParentCodePrompt` can be a stub that will be implemented in Plan 05.
  </action>
  <verify>
    <automated>npx tsc --noEmit && npx vitest run</automated>
  </verify>
  <acceptance_criteria>
    - grep "startPreGeneration" src/app.ts returns match (pre-gen at lesson start)
    - grep "initRewardScreen" src/app.ts returns match (reward after lesson)
    - grep "showScreen.*reward" src/app.ts returns match (navigate to reward)
    - grep "renderForestScene" src/app.ts returns match (forest update after reward)
    - grep "totalSessions.*% 3" src/app.ts returns match (review trigger)
    - grep "lastPlayedLevels" src/app.ts returns match (repeat detection)
    - grep "errorKeyCounts" src/app.ts returns match (stats accumulation)
    - grep "Ctrl.*Shift.*E" src/app.ts or grep "ctrlKey.*shiftKey" src/app.ts returns match
    - npx tsc --noEmit exits 0
  </acceptance_criteria>
  <done>Complete flow wired: forest -> lesson (with pre-gen) -> reward (with image + comment) -> forest (with animation). Review triggers every 3rd session. Repetition protection tracks last played levels. Stats accumulated to progress.</done>
</task>

</tasks>

<verification>
- `npx tsc --noEmit` — no type errors
- `npx vitest run` — all tests pass
- Full flow testable: start lesson -> complete 3 phases -> see reward -> navigate back
</verification>

<success_criteria>
1. Reward screen shows generated/fallback image with companion comment after every lesson
2. Pre-generation starts at lesson begin, reward screen awaits it with placeholder
3. Placeholder shows "Der Wald denkt nach..." with animated leaves
4. "Weiter ueben" starts next lesson, "Zurueck zum Wald" returns to forest
5. New element saved to IndexedDB and animated in forest grid
6. Every 3rd session triggers review mode
7. After 2x same level, encouragement displayed
8. Exercise stats (correct, errors, errorKeys) accumulated in Progress
</success_criteria>

<output>
After completion, create `.planning/phases/03-komplettes-spielerlebnis/03-04-SUMMARY.md`
</output>
