Files

484 lines
16 KiB
Markdown
Raw Permalink Normal View History

---
phase: 03-komplettes-spielerlebnis
plan: 05
type: execute
wave: 2
depends_on: [03-01]
files_modified:
- src/ui/parent.ts
- index.html
- src/styles/main.css
- src/app.ts
autonomous: true
requirements: [PRNT-01, PRNT-02, PRNT-03, PRNT-04]
must_haves:
truths:
- "Ctrl+Shift+E opens a code input prompt"
- "Entering '1234' navigates to parent area screen"
- "Parent area shows current level, completed sessions, practice days as calendar dots"
- "Parent area shows average accuracy and most frequent error keys"
- "Settings allow changing layout, toggling audio, changing companion, entering API key"
artifacts:
- path: "src/ui/parent.ts"
provides: "initParentScreen() with stats display and settings panel"
exports: ["initParentScreen", "showParentCodePrompt"]
- path: "index.html"
provides: "Parent screen HTML with stats overview and settings form"
contains: "parent-stats"
key_links:
- from: "src/ui/parent.ts"
to: "src/storage/db.ts"
via: "getProgress, saveProgress, getSettings, saveSettings"
pattern: "getProgress"
- from: "src/app.ts"
to: "src/ui/parent.ts"
via: "showParentCodePrompt on Ctrl+Shift+E"
pattern: "showParentCodePrompt"
---
<objective>
Build the parent area: gated access via Ctrl+Shift+E + code "1234", stats overview (level, sessions, practice days, accuracy, error keys), and settings (layout, audio, companion, API key).
Purpose: Parents can see progress and adjust settings without the child being exposed to performance metrics.
Output: Parent screen with stats and settings, access gate wired into app.ts.
</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
@src/app.ts
@src/storage/db.ts
@src/types.ts
@src/companion/characters.ts
@index.html
@src/styles/main.css
<interfaces>
<!-- From src/types.ts (after Plan 01): -->
export interface Progress {
id: 1;
currentLevel: number;
completedLevels: number[];
totalSessions: number;
sessionDates: string[]; // ISO date strings "YYYY-MM-DD"
selectedCharacter: CompanionType;
selectedLayout: KeyboardLayout;
totalCorrect: number;
totalErrors: number;
errorKeyCounts: Record<string, number>;
lastPlayedLevels: number[];
}
export interface Settings {
id: 1;
audioEnabled: boolean;
apiKey: string;
apiCallsToday: { text: number; image: number };
lastApiCallDate: string;
}
<!-- From src/storage/db.ts (existing): -->
export function getProgress(db: IDBDatabase): Promise<Progress | null>;
export function saveProgress(db: IDBDatabase, progress: Progress): Promise<void>;
export function getSettings(db: IDBDatabase): Promise<Settings | null>;
export function saveSettings(db: IDBDatabase, settings: Settings): Promise<void>;
<!-- From src/companion/characters.ts (existing): -->
export interface CompanionDefinition { type: CompanionType; name: string; emoji: string; personality: string; avatarUrl: string; }
export const companions: CompanionDefinition[];
<!-- From src/app.ts (existing): -->
export function showScreen(name: ScreenName): void;
export function getDB(): IDBDatabase;
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Parent area HTML, CSS, and access gate</name>
<files>index.html, src/styles/main.css, src/ui/parent.ts</files>
<read_first>index.html, src/styles/main.css, src/types.ts, src/storage/db.ts, src/companion/characters.ts</read_first>
<action>
**Update index.html** — replace empty `<section id="screen-parent">` (per D-14, D-17, D-18):
```html
<section id="screen-parent" class="screen">
<div class="parent">
<button class="parent__back-btn" id="parent-back-btn">&#x2190; Zurueck</button>
<h1 class="parent__title">Elternbereich</h1>
<!-- Code gate overlay -->
<div class="parent__gate" id="parent-gate">
<p>Bitte Code eingeben:</p>
<input type="password" id="parent-code-input" class="parent__code-input" maxlength="4" placeholder="____" inputmode="numeric">
<p class="parent__gate-error" id="parent-gate-error" style="display:none">Falscher Code</p>
</div>
<!-- Content (hidden until code entered) -->
<div class="parent__content" id="parent-content" style="display:none">
<section class="parent__section">
<h2>Uebersicht</h2>
<div class="parent__stats" id="parent-stats">
<div class="parent__stat">
<span class="parent__stat-label">Aktuelle Stufe</span>
<span class="parent__stat-value" id="stat-level">-</span>
</div>
<div class="parent__stat">
<span class="parent__stat-label">Einheiten</span>
<span class="parent__stat-value" id="stat-sessions">-</span>
</div>
<div class="parent__stat">
<span class="parent__stat-label">Genauigkeit</span>
<span class="parent__stat-value" id="stat-accuracy">-</span>
</div>
</div>
<div class="parent__calendar" id="parent-calendar">
<h3>Uebungstage</h3>
<div class="parent__calendar-dots" id="calendar-dots"></div>
</div>
<div class="parent__errors" id="parent-errors">
<h3>Haeufigste Fehlertasten</h3>
<div id="error-keys-list"></div>
</div>
</section>
<section class="parent__section">
<h2>Einstellungen</h2>
<div class="parent__settings">
<div class="parent__setting">
<label>Tastatur-Layout</label>
<select id="setting-layout">
<option value="de">DE (Deutschland)</option>
<option value="ch">CH (Schweiz)</option>
</select>
</div>
<div class="parent__setting">
<label>Audio</label>
<label class="parent__toggle">
<input type="checkbox" id="setting-audio">
<span>An/Aus</span>
</label>
</div>
<div class="parent__setting">
<label>Begleitfigur</label>
<select id="setting-companion"></select>
</div>
<div class="parent__setting">
<label>API-Key</label>
<input type="password" id="setting-apikey" class="parent__input" placeholder="Gemini API Key">
<button class="parent__save-btn" id="save-apikey-btn">Speichern</button>
</div>
</div>
</section>
</div>
</div>
</section>
```
**Add CSS to `src/styles/main.css`:**
```css
/* Parent Area */
.parent {
max-width: 600px;
margin: 0 auto;
padding: 1.5rem;
}
.parent__back-btn {
background: none;
border: none;
font-family: var(--font-heading);
font-size: 1rem;
color: var(--text-warm);
cursor: pointer;
margin-bottom: 1rem;
}
.parent__title {
font-family: var(--font-heading);
color: var(--text-dark);
margin-bottom: 1.5rem;
}
.parent__gate {
text-align: center;
padding: 2rem;
font-family: var(--font-body);
}
.parent__code-input {
font-size: 2rem;
text-align: center;
width: 120px;
padding: 0.5rem;
border: 2px solid var(--accent-purple);
border-radius: 8px;
font-family: var(--font-heading);
letter-spacing: 0.5rem;
margin-top: 0.5rem;
}
.parent__gate-error {
color: var(--accent-pink);
margin-top: 0.5rem;
font-size: 0.9rem;
}
.parent__section {
margin-bottom: 2rem;
}
.parent__section h2 {
font-family: var(--font-heading);
color: var(--text-dark);
border-bottom: 2px solid var(--bg-forest);
padding-bottom: 0.5rem;
margin-bottom: 1rem;
}
.parent__stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
margin-bottom: 1.5rem;
}
.parent__stat {
background: var(--bg-forest);
border-radius: 12px;
padding: 1rem;
text-align: center;
}
.parent__stat-label {
display: block;
font-family: var(--font-body);
font-size: 0.85rem;
color: var(--text-warm);
margin-bottom: 0.25rem;
}
.parent__stat-value {
display: block;
font-family: var(--font-heading);
font-size: 1.5rem;
font-weight: 700;
color: var(--text-dark);
}
.parent__calendar-dots {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 0.5rem;
}
.parent__calendar-dot {
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--accent-green);
}
.parent__calendar-dot--empty {
background: var(--bg-cream);
border: 1px solid var(--text-light);
}
.parent__errors h3,
.parent__calendar h3 {
font-family: var(--font-heading);
font-size: 1rem;
color: var(--text-warm);
margin-bottom: 0.5rem;
}
.parent__error-key {
display: inline-block;
background: var(--accent-pink);
color: white;
padding: 0.25rem 0.75rem;
border-radius: 8px;
margin: 0.25rem;
font-family: var(--font-heading);
font-size: 0.9rem;
}
.parent__settings {
display: flex;
flex-direction: column;
gap: 1rem;
}
.parent__setting {
display: flex;
align-items: center;
gap: 1rem;
}
.parent__setting label:first-child {
min-width: 120px;
font-family: var(--font-body);
color: var(--text-warm);
}
.parent__setting select,
.parent__input {
font-family: var(--font-body);
padding: 0.5rem;
border: 1px solid var(--text-light);
border-radius: 8px;
font-size: 0.95rem;
}
.parent__save-btn {
font-family: var(--font-heading);
padding: 0.5rem 1rem;
background: var(--accent-green);
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
}
```
**Create `src/ui/parent.ts`:**
```typescript
import { getProgress, saveProgress, getSettings, saveSettings } from '../storage/db';
import { companions } from '../companion/characters';
import type { CompanionType, KeyboardLayout } from '../types';
import { showScreen, getDB } from '../app';
```
**`showParentCodePrompt()`** (per D-14, PRNT-01):
- Show parent screen: `showScreen('parent')`
- Show gate, hide content
- Focus code input
- On input change, if value === '1234': hide gate, show content, call `loadParentData()`
- If value.length === 4 but wrong: show error, clear after 1.5s
**`initParentScreen(db: IDBDatabase, onBack: () => void)`:**
Wire back button to `onBack` callback.
**`loadParentData()`** (per D-17, PRNT-02, PRNT-03):
1. Get progress from IndexedDB
2. Set `#stat-level` to `progress.currentLevel`
3. Set `#stat-sessions` to `progress.totalSessions`
4. Calculate accuracy: `progress.totalCorrect + progress.totalErrors > 0 ? Math.round(progress.totalCorrect / (progress.totalCorrect + progress.totalErrors) * 100) : 0`
5. Set `#stat-accuracy` to `${accuracy}%`
6. Render calendar dots in `#calendar-dots`:
- Get last 30 days as date strings
- For each day: if in `progress.sessionDates`, render green dot, else empty dot
7. Render error keys in `#error-keys-list`:
- Sort `progress.errorKeyCounts` by value descending
- Show top 5 keys as colored badges: `<span class="parent__error-key">E (12x)</span>`
- If no errors: show "Noch keine Fehler erfasst"
**Settings (per D-18, PRNT-04):**
1. Populate companion select from `companions` array
2. Set current values from progress (layout, companion) and settings (audio, apiKey)
3. On layout change: `saveProgress(db, { ...progress, selectedLayout: value })`
4. On companion change: `saveProgress(db, { ...progress, selectedCharacter: value })`
5. On audio toggle: `saveSettings(db, { ...settings, audioEnabled: checked })`
6. On save API key: `saveSettings(db, { ...settings, apiKey: value })`
7. Each save shows brief "Gespeichert!" feedback
</action>
<verify>
<automated>npx tsc --noEmit && grep -c "showParentCodePrompt" src/ui/parent.ts && grep -c "parent-gate" index.html && grep -c "parent__calendar-dot" src/styles/main.css</automated>
</verify>
<acceptance_criteria>
- grep "showParentCodePrompt" src/ui/parent.ts returns export
- grep "1234" src/ui/parent.ts returns match (access code)
- grep "parent-gate" index.html returns match (gate overlay)
- grep "stat-level" index.html returns match (stats display)
- grep "stat-accuracy" index.html returns match
- grep "calendar-dots" index.html returns match
- grep "error-keys-list" index.html returns match
- grep "setting-layout" index.html returns match (layout setting)
- grep "setting-companion" index.html returns match (companion setting)
- grep "setting-apikey" index.html returns match (API key setting)
- grep "parent__calendar-dot" src/styles/main.css returns match
- grep "parent__error-key" src/styles/main.css returns match
- npx tsc --noEmit exits 0
</acceptance_criteria>
<done>Parent area gate with code "1234", stats overview with level/sessions/accuracy/calendar-dots/error-keys, settings for layout/audio/companion/API-key. All styled and functional.</done>
</task>
<task type="auto">
<name>Task 2: Wire parent area into app.ts</name>
<files>src/app.ts</files>
<read_first>src/app.ts, src/ui/parent.ts</read_first>
<action>
**Wire parent area access in `src/app.ts`** (per D-14, PRNT-01):
1. Import: `import { showParentCodePrompt, initParentScreen } from './ui/parent';`
2. In `initApp()`, after DB is opened, call:
```typescript
initParentScreen(db, async () => {
// Back from parent -> return to forest
await initForestScreen(db, startLessonFromForest);
showScreen('forest');
});
```
3. Replace the stub `showParentCodePrompt` reference (if Plan 04 added a stub) or add the global keydown listener:
```typescript
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.shiftKey && e.key === 'E') {
e.preventDefault();
showParentCodePrompt();
}
});
```
Add this in `initApp()` after DB setup. Ensure it's only added once.
4. Make sure `showParentCodePrompt` properly uses the DB reference. Since `parent.ts` imports `getDB` from `app.ts`, it can access the DB.
</action>
<verify>
<automated>npx tsc --noEmit && grep -c "showParentCodePrompt" src/app.ts && grep -c "initParentScreen" src/app.ts</automated>
</verify>
<acceptance_criteria>
- grep "import.*showParentCodePrompt" src/app.ts returns match
- grep "import.*initParentScreen" src/app.ts returns match
- grep "ctrlKey.*shiftKey" src/app.ts returns match (keyboard shortcut)
- grep "initParentScreen" src/app.ts shows it's called in initApp
- npx tsc --noEmit exits 0
</acceptance_criteria>
<done>Parent area fully wired: Ctrl+Shift+E triggers code prompt, "1234" opens parent screen, back button returns to forest. Global listener registered once in initApp.</done>
</task>
</tasks>
<verification>
- `npx tsc --noEmit` — no type errors
- `npx vitest run` — all tests pass
- Parent area accessible via Ctrl+Shift+E + "1234"
- Stats show current data, settings persist changes
</verification>
<success_criteria>
1. Ctrl+Shift+E opens code input overlay on parent screen
2. Code "1234" reveals parent content, wrong code shows error
3. Overview shows: current level, total sessions, accuracy percentage
4. Calendar dots show last 30 days with green dots for practice days
5. Error keys shown as badges, sorted by frequency, top 5
6. Settings: layout change, audio toggle, companion change, API key save — all persist to IndexedDB
7. Back button returns to forest screen
</success_criteria>
<output>
After completion, create `.planning/phases/03-komplettes-spielerlebnis/03-05-SUMMARY.md`
</output>