docs(03): create phase plan — komplettes spielerlebnis
5 plans across 3 waves covering all 17 requirements: - Plan 01 (W1): Word lists, Progress extension, rate limits - Plan 02 (W2): Lesson 3-phase state machine, review/repeat - Plan 03 (W1): Forest visual scene with SVG + grid - Plan 04 (W3): Reward screen, pre-generation, full flow wiring - Plan 05 (W2): Parent area with stats and settings Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,267 @@
|
||||
---
|
||||
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>
|
||||
Reference in New Issue
Block a user