---
phase: 03-komplettes-spielerlebnis
plan: 03
type: execute
wave: 1
depends_on: []
files_modified:
  - src/forest/scene.ts
  - index.html
  - src/styles/main.css
autonomous: true
requirements: [FRST-01, FRST-02, FRST-03]
must_haves:
  truths:
    - "Forest screen shows SVG woodland background"
    - "A 4x3 grid overlays the SVG for placing forest element images"
    - "New elements appear with fade-in + scale-up animation"
    - "All previously earned elements load from IndexedDB on screen init"
  artifacts:
    - path: "src/forest/scene.ts"
      provides: "renderForestScene() that loads elements from IDB and renders in grid"
      exports: ["renderForestScene"]
    - path: "index.html"
      provides: "Forest scene container with SVG background and grid overlay"
      contains: "forest-scene"
  key_links:
    - from: "src/forest/scene.ts"
      to: "src/storage/db.ts"
      via: "getForestElements() to load persisted elements"
      pattern: "getForestElements"
---

<objective>
Build the visual forest scene: SVG background with a 4x3 grid overlay where generated forest element images are placed. Elements load from IndexedDB and new ones animate in.

Purpose: The growing forest IS the reward system — the child sees their world fill up with each session.
Output: Visual forest scene in forest screen with grid, animations, and IndexedDB loading.
</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
@src/forest/scene.ts
@src/storage/db.ts
@src/types.ts
@index.html
@src/styles/main.css

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

<!-- From src/types.ts (existing): -->
export interface ForestElement {
  id?: number;
  levelCompleted: number;
  imageBlob: Blob;
  imagePrompt: string;
  description: string;
  companionText: string;
  position: { x: number; y: number };
  createdAt: string;
}
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: SVG forest background and grid HTML structure</name>
  <files>index.html, src/styles/main.css</files>
  <read_first>index.html, src/styles/main.css, src/forest/scene.ts</read_first>
  <action>
    **Update index.html** `#screen-forest .forest` section (per D-07, D-09):

    Add a forest scene container ABOVE the level-map but BELOW the greeting:
    ```html
    <div class="forest__scene" id="forest-scene">
      <svg class="forest__bg" viewBox="0 0 800 400" xmlns="http://www.w3.org/2000/svg">
        <!-- Ground -->
        <rect x="0" y="300" width="800" height="100" fill="#C8E6C0" rx="0"/>
        <!-- Hills -->
        <ellipse cx="200" cy="320" rx="250" ry="80" fill="#A8D8A8"/>
        <ellipse cx="650" cy="330" rx="200" ry="60" fill="#B8E0B0"/>
        <!-- Trees (background) -->
        <g opacity="0.3">
          <ellipse cx="80" cy="240" rx="40" ry="60" fill="#7DB87D"/>
          <ellipse cx="720" cy="250" rx="35" ry="55" fill="#6DA86D"/>
          <ellipse cx="400" cy="230" rx="45" ry="70" fill="#8DC88D"/>
        </g>
        <!-- Sky gradient -->
        <defs>
          <linearGradient id="sky-grad" x1="0" y1="0" x2="0" y2="1">
            <stop offset="0%" stop-color="#E8F4FD"/>
            <stop offset="100%" stop-color="#F0F8E8"/>
          </linearGradient>
        </defs>
        <rect x="0" y="0" width="800" height="300" fill="url(#sky-grad)"/>
        <!-- Clouds -->
        <ellipse cx="150" cy="60" rx="60" ry="25" fill="white" opacity="0.5"/>
        <ellipse cx="550" cy="80" rx="70" ry="20" fill="white" opacity="0.4"/>
        <!-- Sun -->
        <circle cx="700" cy="50" r="30" fill="#F8E8A0" opacity="0.6"/>
      </svg>
      <div class="forest__grid" id="forest-grid">
        <!-- 12 slots for forest elements, filled dynamically -->
      </div>
    </div>
    ```

    NOTE: The SVG layers must be ordered so sky-rect is FIRST (painted first, at back), then hills/ground on top. Adjust SVG element order: sky rect first, then clouds/sun, then background trees, then hills, then ground.

    **Add CSS to `src/styles/main.css`:**

    ```css
    /* Forest Scene */
    .forest__scene {
      position: relative;
      width: 100%;
      max-width: 800px;
      margin: 0 auto 1.5rem;
      border-radius: 16px;
      overflow: hidden;
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
    }

    .forest__bg {
      display: block;
      width: 100%;
      height: auto;
    }

    .forest__grid {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      display: grid;
      grid-template-columns: repeat(4, 1fr);
      grid-template-rows: repeat(3, 1fr);
      padding: 1rem;
      gap: 0.5rem;
    }

    .forest__element {
      display: flex;
      align-items: center;
      justify-content: center;
    }

    .forest__element img {
      max-width: 100%;
      max-height: 100%;
      object-fit: contain;
      border-radius: 8px;
      filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.15));
    }

    /* New element animation (per D-08, FRST-02) */
    .forest__element--new {
      animation: forest-element-appear 1.2s ease-out forwards;
    }

    @keyframes forest-element-appear {
      0% {
        opacity: 0;
        transform: scale(0.3);
      }
      60% {
        opacity: 1;
        transform: scale(1.05);
      }
      100% {
        opacity: 1;
        transform: scale(1);
      }
    }
    ```
  </action>
  <verify>
    <automated>grep -c "forest-scene" index.html && grep -c "forest__grid" index.html && grep -c "forest__element--new" src/styles/main.css</automated>
  </verify>
  <acceptance_criteria>
    - grep "forest-scene" index.html returns match
    - grep "forest__grid" index.html returns match
    - grep "forest-element-appear" src/styles/main.css returns match (animation keyframes)
    - grep "grid-template-columns.*repeat(4" src/styles/main.css returns match (4x3 grid)
    - grep "grid-template-rows.*repeat(3" src/styles/main.css returns match
    - SVG viewBox="0 0 800 400" present in index.html
  </acceptance_criteria>
  <done>Forest scene has SVG background with woodland scenery, 4x3 CSS grid overlay, and fade-in+scale-up animation keyframes for new elements.</done>
</task>

<task type="auto">
  <name>Task 2: Render forest elements from IndexedDB</name>
  <files>src/forest/scene.ts</files>
  <read_first>src/forest/scene.ts, src/storage/db.ts, src/types.ts, index.html</read_first>
  <action>
    Extend `src/forest/scene.ts` (per D-07, D-08, FRST-03):

    Add a new exported function `renderForestScene(db: IDBDatabase, newElementId?: number): Promise<void>`:

    1. Get `#forest-grid` element
    2. Call `getForestElements(db)` to load all persisted elements
    3. Clear grid contents
    4. For each element (up to 12 — the grid has 12 slots):
       - Create a `<div class="forest__element">`
       - Create an `<img>` with `src` = `URL.createObjectURL(element.imageBlob)`
       - Set `alt` = `element.description`
       - Apply random offset per D-07: `style="transform: translate(${randomOffset}px, ${randomOffset}px)"` where offset is +-10-20px random
       - If `element.id === newElementId`, add class `forest__element--new` for animation
       - Append to grid

    5. Store created Object URLs in a module-level array so they can be revoked on next call (prevent memory leak):
       ```typescript
       let activeObjectUrls: string[] = [];
       ```
       At start of function, revoke all old URLs via `URL.revokeObjectURL()`, then clear array.

    Update `initForestScreen` to call `renderForestScene(db)` alongside existing `renderLevelMap`.

    The `newElementId` parameter will be used by Plan 04 (reward flow) to trigger animation on the just-added element.
  </action>
  <verify>
    <automated>npx tsc --noEmit && npx vitest run</automated>
  </verify>
  <acceptance_criteria>
    - grep "renderForestScene" src/forest/scene.ts returns export
    - grep "getForestElements" src/forest/scene.ts returns match (loads from IDB)
    - grep "createObjectURL" src/forest/scene.ts returns match (blob->URL conversion)
    - grep "revokeObjectURL" src/forest/scene.ts returns match (memory cleanup)
    - grep "forest__element--new" src/forest/scene.ts returns match (animation class)
    - grep "renderForestScene" src/forest/scene.ts shows it's called from initForestScreen
    - npx tsc --noEmit exits 0
  </acceptance_criteria>
  <done>Forest scene renders all stored elements from IndexedDB as images in the 4x3 grid. New elements get animation class. Object URLs cleaned up on re-render. Level map preserved alongside scene.</done>
</task>

</tasks>

<verification>
- `npx tsc --noEmit` — no type errors
- `npx vitest run` — all tests pass
- Forest screen has SVG background visible with grid overlay
- Elements from IndexedDB render as images with random offset
</verification>

<success_criteria>
1. Forest screen shows SVG woodland background (sky, hills, trees, clouds)
2. 4x3 CSS grid overlays the SVG for element placement
3. All stored ForestElements load from IndexedDB and display as images
4. New elements animate with fade-in + scale-up (1.2s ease-out)
5. Object URLs properly managed (revoked on re-render)
6. Level map still visible below/alongside the scene
</success_criteria>

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