342 lines
13 KiB
Markdown
342 lines
13 KiB
Markdown
---
|
|||
|
|
phase: 01-grundger-st-tippmechanik
|
||
|
|
plan: 02
|
||
|
|
type: execute
|
||
|
|
wave: 2
|
||
|
|
depends_on: ["01-01"]
|
||
|
|
files_modified:
|
||
|
|
- src/app.ts
|
||
|
|
- src/ui/screens.ts
|
||
|
|
- src/companion/characters.ts
|
||
|
|
- index.html
|
||
|
|
autonomous: true
|
||
|
|
requirements:
|
||
|
|
- FNDN-05
|
||
|
|
- ONBD-01
|
||
|
|
- ONBD-02
|
||
|
|
- ONBD-03
|
||
|
|
- ONBD-04
|
||
|
|
|
||
|
|
must_haves:
|
||
|
|
truths:
|
||
|
|
- "App startet und zeigt den Willkommens-Screen"
|
||
|
|
- "4 Begleitfigur-Karten sind klickbar und zeigen Emoji + Name"
|
||
|
|
- "DE/CH Layout-Toggle ist waehlbar"
|
||
|
|
- "Nach Auswahl + Los gehts wechselt die App zur Wald-Uebersicht"
|
||
|
|
- "Nach Browser-Reload bleibt die Auswahl erhalten und Willkommens-Screen wird uebersprungen"
|
||
|
|
artifacts:
|
||
|
|
- path: "src/app.ts"
|
||
|
|
provides: "Screen management and routing"
|
||
|
|
exports: ["showScreen", "initApp"]
|
||
|
|
- path: "src/ui/screens.ts"
|
||
|
|
provides: "Welcome screen logic and companion selection"
|
||
|
|
exports: ["initWelcomeScreen"]
|
||
|
|
- path: "src/companion/characters.ts"
|
||
|
|
provides: "Companion definitions (name, emoji, personality)"
|
||
|
|
exports: ["companions", "CompanionDefinition"]
|
||
|
|
key_links:
|
||
|
|
- from: "src/app.ts"
|
||
|
|
to: "src/storage/db.ts"
|
||
|
|
via: "checks progress on init to skip welcome"
|
||
|
|
pattern: "getProgress"
|
||
|
|
- from: "src/ui/screens.ts"
|
||
|
|
to: "src/storage/db.ts"
|
||
|
|
via: "saves companion + layout choice"
|
||
|
|
pattern: "saveProgress"
|
||
|
|
- from: "src/main.ts"
|
||
|
|
to: "src/app.ts"
|
||
|
|
via: "calls initApp() on DOMContentLoaded"
|
||
|
|
pattern: "initApp"
|
||
|
|
---
|
||
|
|
|
||
|
|
<objective>
|
||
|
|
Implement screen management (CSS fade transitions between 5 screens) and the complete onboarding flow: companion selection, layout selection, persistence, and navigation to forest.
|
||
|
|
|
||
|
|
Purpose: The user must be able to start the app, choose a companion and layout, and reach the forest overview. This is the entry point for all subsequent gameplay.
|
||
|
|
Output: Working welcome-to-forest flow with IndexedDB persistence.
|
||
|
|
</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/01-grundger-st-tippmechanik/01-CONTEXT.md
|
||
|
|
@.planning/phases/01-grundger-st-tippmechanik/01-01-SUMMARY.md
|
||
|
|
@SPEC.md (sections 5.2, 7.1, 7.2)
|
||
|
|
|
||
|
|
<interfaces>
|
||
|
|
From src/types.ts:
|
||
|
|
```typescript
|
||
|
|
export type CompanionType = 'fee' | 'einhorn' | 'fuchs' | 'eule';
|
||
|
|
export type KeyboardLayout = 'de' | 'ch';
|
||
|
|
export type ScreenName = 'welcome' | 'forest' | 'lesson' | 'reward' | 'parent';
|
||
|
|
export interface Progress {
|
||
|
|
id: 1;
|
||
|
|
currentLevel: number;
|
||
|
|
completedLevels: number[];
|
||
|
|
totalSessions: number;
|
||
|
|
sessionDates: string[];
|
||
|
|
selectedCharacter: CompanionType;
|
||
|
|
selectedLayout: KeyboardLayout;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
From src/storage/db.ts:
|
||
|
|
```typescript
|
||
|
|
export function openDB(): Promise<IDBDatabase>;
|
||
|
|
export async function getProgress(db: IDBDatabase): Promise<Progress | null>;
|
||
|
|
export async function saveProgress(db: IDBDatabase, progress: Progress): Promise<void>;
|
||
|
|
```
|
||
|
|
</interfaces>
|
||
|
|
</context>
|
||
|
|
|
||
|
|
<tasks>
|
||
|
|
|
||
|
|
<task type="auto">
|
||
|
|
<name>Task 1: Screen management + companion definitions</name>
|
||
|
|
<files>
|
||
|
|
src/app.ts, src/companion/characters.ts
|
||
|
|
</files>
|
||
|
|
<read_first>
|
||
|
|
src/types.ts
|
||
|
|
src/storage/db.ts
|
||
|
|
src/styles/main.css
|
||
|
|
index.html
|
||
|
|
SPEC.md (section 5.2 for companion definitions, section 7.1 for screen list)
|
||
|
|
</read_first>
|
||
|
|
<action>
|
||
|
|
1. Create src/companion/characters.ts with companion definitions (per D-05, emoji placeholders):
|
||
|
|
```typescript
|
||
|
|
import type { CompanionType } from '../types';
|
||
|
|
|
||
|
|
export interface CompanionDefinition {
|
||
|
|
type: CompanionType;
|
||
|
|
name: string;
|
||
|
|
emoji: string;
|
||
|
|
personality: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
export const companions: CompanionDefinition[] = [
|
||
|
|
{ type: 'fee', name: 'Lila', emoji: '🧚', personality: 'Sanft, ermutigend, ein bisschen vertraeumt' },
|
||
|
|
{ type: 'einhorn', name: 'Stella', emoji: '🦄', personality: 'Froehlich, enthusiastisch, feiert jeden kleinen Erfolg' },
|
||
|
|
{ type: 'fuchs', name: 'Finn', emoji: '🦊', personality: 'Ruhig, weise, humorvoll' },
|
||
|
|
{ type: 'eule', name: 'Elsa', emoji: '🦉', personality: 'Geduldig, warm, grossmuetterlich' },
|
||
|
|
];
|
||
|
|
```
|
||
|
|
|
||
|
|
2. Create src/app.ts with screen management (per D-01):
|
||
|
|
```typescript
|
||
|
|
import type { ScreenName } from './types';
|
||
|
|
import { openDB, getProgress } from './storage/db';
|
||
|
|
import { initWelcomeScreen } from './ui/screens';
|
||
|
|
|
||
|
|
let db: IDBDatabase;
|
||
|
|
|
||
|
|
export function showScreen(name: ScreenName): void {
|
||
|
|
document.querySelectorAll('.screen').forEach(el => {
|
||
|
|
el.classList.remove('screen--active');
|
||
|
|
});
|
||
|
|
const target = document.getElementById(`screen-${name}`);
|
||
|
|
if (target) {
|
||
|
|
target.classList.add('screen--active');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export function getDB(): IDBDatabase {
|
||
|
|
return db;
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function initApp(): Promise<void> {
|
||
|
|
db = await openDB();
|
||
|
|
const progress = await getProgress(db);
|
||
|
|
if (progress && progress.selectedCharacter) {
|
||
|
|
// Returning user — skip welcome, go to forest
|
||
|
|
showScreen('forest');
|
||
|
|
} else {
|
||
|
|
// First time — show welcome
|
||
|
|
initWelcomeScreen(db, showScreen);
|
||
|
|
showScreen('welcome');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
3. Update src/main.ts to call initApp:
|
||
|
|
```typescript
|
||
|
|
import './styles/main.css';
|
||
|
|
import { initApp } from './app';
|
||
|
|
document.addEventListener('DOMContentLoaded', initApp);
|
||
|
|
```
|
||
|
|
</action>
|
||
|
|
<verify>
|
||
|
|
<automated>cd /home/dev/workspace/zauberwald && npx tsc --noEmit</automated>
|
||
|
|
</verify>
|
||
|
|
<acceptance_criteria>
|
||
|
|
- src/companion/characters.ts exports `companions` array with 4 entries (fee, einhorn, fuchs, eule)
|
||
|
|
- src/companion/characters.ts contains emoji strings: '🧚', '🦄', '🦊', '🦉'
|
||
|
|
- src/app.ts exports `showScreen` and `initApp`
|
||
|
|
- src/app.ts imports from `./storage/db` and `./ui/screens`
|
||
|
|
- src/app.ts contains logic: if progress exists -> showScreen('forest'), else showScreen('welcome')
|
||
|
|
- src/main.ts contains `import { initApp } from './app'` and `initApp`
|
||
|
|
- `npx tsc --noEmit` exits 0
|
||
|
|
</acceptance_criteria>
|
||
|
|
<done>Screen transitions work via CSS class toggling, companion data defined, app routes to welcome or forest based on saved progress</done>
|
||
|
|
</task>
|
||
|
|
|
||
|
|
<task type="auto">
|
||
|
|
<name>Task 2: Welcome screen UI + onboarding flow</name>
|
||
|
|
<files>
|
||
|
|
src/ui/screens.ts, index.html, src/styles/main.css
|
||
|
|
</files>
|
||
|
|
<read_first>
|
||
|
|
src/app.ts
|
||
|
|
src/companion/characters.ts
|
||
|
|
src/types.ts
|
||
|
|
src/storage/db.ts
|
||
|
|
src/styles/main.css
|
||
|
|
index.html
|
||
|
|
</read_first>
|
||
|
|
<action>
|
||
|
|
1. Update index.html — populate the welcome screen section with:
|
||
|
|
```html
|
||
|
|
<section id="screen-welcome" class="screen screen--active">
|
||
|
|
<div class="welcome">
|
||
|
|
<h1>Willkommen im Zauberwald!</h1>
|
||
|
|
<p class="welcome__subtitle">Waehle deine Begleitfigur:</p>
|
||
|
|
<div class="welcome__companions" id="companion-grid">
|
||
|
|
<!-- Filled dynamically by screens.ts -->
|
||
|
|
</div>
|
||
|
|
<p class="welcome__layout-label">Tastatur-Layout:</p>
|
||
|
|
<div class="welcome__layout-toggle" id="layout-toggle">
|
||
|
|
<button class="layout-btn layout-btn--active" data-layout="de">DE</button>
|
||
|
|
<button class="layout-btn" data-layout="ch">CH</button>
|
||
|
|
</div>
|
||
|
|
<button class="welcome__start-btn" id="start-btn" disabled>Los geht's!</button>
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
```
|
||
|
|
Keep the other 4 screen sections empty (just the section tag).
|
||
|
|
|
||
|
|
2. Create src/ui/screens.ts:
|
||
|
|
```typescript
|
||
|
|
import type { CompanionType, KeyboardLayout, ScreenName, Progress } from '../types';
|
||
|
|
import { companions } from '../companion/characters';
|
||
|
|
import { saveProgress } from '../storage/db';
|
||
|
|
|
||
|
|
export function initWelcomeScreen(
|
||
|
|
db: IDBDatabase,
|
||
|
|
navigateTo: (screen: ScreenName) => void
|
||
|
|
): void {
|
||
|
|
let selectedCompanion: CompanionType | null = null;
|
||
|
|
let selectedLayout: KeyboardLayout = 'de';
|
||
|
|
|
||
|
|
const grid = document.getElementById('companion-grid')!;
|
||
|
|
const startBtn = document.getElementById('start-btn') as HTMLButtonElement;
|
||
|
|
const layoutToggle = document.getElementById('layout-toggle')!;
|
||
|
|
|
||
|
|
// Render companion cards
|
||
|
|
companions.forEach(c => {
|
||
|
|
const card = document.createElement('button');
|
||
|
|
card.className = 'companion-card';
|
||
|
|
card.dataset.companion = c.type;
|
||
|
|
card.innerHTML = `
|
||
|
|
<span class="companion-card__emoji">${c.emoji}</span>
|
||
|
|
<span class="companion-card__name">${c.name}</span>
|
||
|
|
`;
|
||
|
|
card.addEventListener('click', () => {
|
||
|
|
grid.querySelectorAll('.companion-card').forEach(el =>
|
||
|
|
el.classList.remove('companion-card--selected')
|
||
|
|
);
|
||
|
|
card.classList.add('companion-card--selected');
|
||
|
|
selectedCompanion = c.type;
|
||
|
|
startBtn.disabled = false;
|
||
|
|
});
|
||
|
|
grid.appendChild(card);
|
||
|
|
});
|
||
|
|
|
||
|
|
// Layout toggle
|
||
|
|
layoutToggle.addEventListener('click', (e) => {
|
||
|
|
const btn = (e.target as HTMLElement).closest('.layout-btn') as HTMLButtonElement | null;
|
||
|
|
if (!btn) return;
|
||
|
|
layoutToggle.querySelectorAll('.layout-btn').forEach(el =>
|
||
|
|
el.classList.remove('layout-btn--active')
|
||
|
|
);
|
||
|
|
btn.classList.add('layout-btn--active');
|
||
|
|
selectedLayout = btn.dataset.layout as KeyboardLayout;
|
||
|
|
});
|
||
|
|
|
||
|
|
// Start button
|
||
|
|
startBtn.addEventListener('click', async () => {
|
||
|
|
if (!selectedCompanion) return;
|
||
|
|
const progress: Progress = {
|
||
|
|
id: 1,
|
||
|
|
currentLevel: 1,
|
||
|
|
completedLevels: [],
|
||
|
|
totalSessions: 0,
|
||
|
|
sessionDates: [],
|
||
|
|
selectedCharacter: selectedCompanion,
|
||
|
|
selectedLayout: selectedLayout,
|
||
|
|
};
|
||
|
|
await saveProgress(db, progress);
|
||
|
|
navigateTo('forest');
|
||
|
|
});
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
3. Add CSS for welcome screen in src/styles/main.css:
|
||
|
|
- `.welcome` — centered flex column, padding 2rem, max-width 600px, margin auto
|
||
|
|
- `.welcome h1` — font-family Quicksand, font-size 2rem, color var(--text-dark)
|
||
|
|
- `.welcome__companions` / `#companion-grid` — display grid, grid-template-columns repeat(2, 1fr), gap 1rem
|
||
|
|
- `.companion-card` — background white, border 3px solid transparent, border-radius 16px, padding 1.5rem, cursor pointer, transition border-color 0.2s, text-align center, display flex, flex-direction column, align-items center, gap 0.5rem
|
||
|
|
- `.companion-card:hover` — border-color var(--accent-gold)
|
||
|
|
- `.companion-card--selected` — border-color var(--accent-green), background var(--bg-forest)
|
||
|
|
- `.companion-card__emoji` — font-size 3rem
|
||
|
|
- `.companion-card__name` — font-family Quicksand, font-weight 600, font-size 1.2rem, color var(--text-dark)
|
||
|
|
- `.layout-btn` — padding 0.5rem 1.5rem, border-radius 8px, border 2px solid var(--text-light), background white, cursor pointer, font-family Nunito, font-size 1rem
|
||
|
|
- `.layout-btn--active` — background var(--accent-green), color white, border-color var(--accent-green)
|
||
|
|
- `.welcome__start-btn` — margin-top 1.5rem, padding 1rem 3rem, border-radius 12px, background var(--accent-green), color white, border none, font-family Quicksand, font-weight 700, font-size 1.3rem, cursor pointer, opacity 1, transition opacity 0.2s
|
||
|
|
- `.welcome__start-btn:disabled` — opacity 0.4, cursor not-allowed
|
||
|
|
- `.welcome__layout-toggle` — display flex, gap 0.5rem, margin-top 0.5rem
|
||
|
|
</action>
|
||
|
|
<verify>
|
||
|
|
<automated>cd /home/dev/workspace/zauberwald && npx tsc --noEmit && npm run dev -- --strictPort 2>&1 & sleep 3 && curl -s http://localhost:5173 | grep -q "companion-grid" && kill %1 && echo "PASS"</automated>
|
||
|
|
</verify>
|
||
|
|
<acceptance_criteria>
|
||
|
|
- index.html contains `id="companion-grid"` and `id="start-btn"` and `id="layout-toggle"`
|
||
|
|
- index.html contains `data-layout="de"` and `data-layout="ch"`
|
||
|
|
- src/ui/screens.ts exports `initWelcomeScreen`
|
||
|
|
- src/ui/screens.ts contains `saveProgress(db, progress)` call
|
||
|
|
- src/ui/screens.ts contains `navigateTo('forest')` after save
|
||
|
|
- src/ui/screens.ts creates 4 companion cards with `.companion-card` class
|
||
|
|
- src/styles/main.css contains `.companion-card`, `.companion-card--selected`, `.welcome__start-btn`
|
||
|
|
- src/styles/main.css contains `.layout-btn--active`
|
||
|
|
- `npx tsc --noEmit` exits 0
|
||
|
|
- Welcome screen renders 4 companion cards and DE/CH toggle in browser
|
||
|
|
</acceptance_criteria>
|
||
|
|
<done>Welcome screen shows 4 emoji companion cards, DE/CH layout toggle, and "Los geht's" button. Selecting companion enables start. Clicking start saves to IndexedDB and navigates to forest screen. On reload, app skips welcome and shows forest directly.</done>
|
||
|
|
</task>
|
||
|
|
|
||
|
|
</tasks>
|
||
|
|
|
||
|
|
<verification>
|
||
|
|
- App loads and shows welcome screen with 4 companion cards
|
||
|
|
- Clicking a companion highlights it and enables "Los geht's"
|
||
|
|
- DE/CH toggle switches active state
|
||
|
|
- Clicking "Los geht's" transitions to forest screen (empty for now)
|
||
|
|
- Refreshing the page skips welcome and shows forest directly
|
||
|
|
- Clearing IndexedDB and refreshing shows welcome again
|
||
|
|
</verification>
|
||
|
|
|
||
|
|
<success_criteria>
|
||
|
|
- Complete onboarding flow: select companion + layout, start, persist, skip on return
|
||
|
|
- Screen transitions use CSS fade (opacity transition)
|
||
|
|
- All companion data matches spec section 5.2 names
|
||
|
|
</success_criteria>
|
||
|
|
|
||
|
|
<output>
|
||
|
|
After completion, create `.planning/phases/01-grundger-st-tippmechanik/01-02-SUMMARY.md`
|
||
|
|
</output>
|