From 57a929bc1a5d5a928b492d67bc675fc8c7ab4332 Mon Sep 17 00:00:00 2001 From: Markus Graf Date: Sun, 29 Mar 2026 12:15:45 +0200 Subject: [PATCH] feat(02-05): add companion greeting to forest and avatar to lesson screen - Add forest-greeting companion area in HTML with avatar + text - Add lesson-companion area in HTML above letter area - Wire getGreeting() call for returning users in app.ts - Show companion avatar in lesson screen via getProgress lookup - Fix pre-existing noUncheckedIndexedAccess errors in fallbacks.ts (Rule 3) Co-Authored-By: Claude Opus 4.6 (1M context) --- index.html | 8 ++++++++ src/app.ts | 23 +++++++++++++++++++++++ src/companion/fallbacks.ts | 4 ++-- src/ui/screens.ts | 18 +++++++++++++++++- 4 files changed, 50 insertions(+), 3 deletions(-) diff --git a/index.html b/index.html index 3d586c2..93e0c7c 100644 --- a/index.html +++ b/index.html @@ -30,6 +30,10 @@

Dein Zauberwald

+
@@ -39,6 +43,10 @@
+
diff --git a/src/app.ts b/src/app.ts index 6681f52..a648ac7 100644 --- a/src/app.ts +++ b/src/app.ts @@ -2,6 +2,8 @@ import type { ScreenName } from "./types"; import { openDB, getProgress, saveProgress } from "./storage/db"; import { initWelcomeScreen, initLessonScreen } from "./ui/screens"; import { initForestScreen } from "./forest/scene"; +import { getGreeting } from "./companion/companion"; +import { companions } from "./companion/characters"; let db: IDBDatabase; let lessonCleanup: (() => void) | null = null; @@ -89,6 +91,27 @@ export async function initApp(): Promise { // Returning user -- init forest and go there await initForestScreen(db, startLessonFromForest); showScreen("forest"); + + // Show companion greeting (per COMP-01, D-14) + const companion = companions.find( + (c) => c.type === progress.selectedCharacter, + ); + if (companion) { + const greetingEl = document.getElementById("forest-greeting")!; + const avatarEl = document.getElementById( + "forest-greeting-avatar", + ) as HTMLImageElement; + const textEl = document.getElementById("forest-greeting-text")!; + + avatarEl.src = companion.avatarUrl; + avatarEl.alt = companion.name; + greetingEl.style.display = "flex"; + + // Fetch greeting async (shows immediately with avatar, text fills in) + getGreeting(progress.selectedCharacter, db).then((text) => { + textEl.textContent = text; + }); + } } else { // First time -- show welcome, then forest after setup initWelcomeScreen(db, async (screen: ScreenName) => { diff --git a/src/companion/fallbacks.ts b/src/companion/fallbacks.ts index bbcff6c..c652ea9 100644 --- a/src/companion/fallbacks.ts +++ b/src/companion/fallbacks.ts @@ -163,12 +163,12 @@ export function getRandomFallbackGreeting(timeOfDay: TimeOfDay): string { (g) => g.timeOfDay === timeOfDay || g.timeOfDay === "any", ); const index = Math.floor(Math.random() * matching.length); - return matching[index].text; + return matching[index]?.text ?? "Willkommen im Zauberwald!"; } export function getRandomFallbackForestComment(): string { const index = Math.floor(Math.random() * fallbackForestComments.length); - return fallbackForestComments[index]; + return fallbackForestComments[index] ?? "Schau mal, was im Wald passiert!"; } export function getFallbackLetterIntro( diff --git a/src/ui/screens.ts b/src/ui/screens.ts index 1de9bf4..48664b9 100644 --- a/src/ui/screens.ts +++ b/src/ui/screens.ts @@ -5,7 +5,7 @@ import type { Progress, } from "../types"; import { companions } from "../companion/characters"; -import { saveProgress } from "../storage/db"; +import { saveProgress, getProgress } from "../storage/db"; import { createExerciseState, handleKeyPress, @@ -88,6 +88,22 @@ export function initLessonScreen( const backBtn = document.getElementById("lesson-back-btn") as HTMLButtonElement; const state = createExerciseState(level); + // Show companion avatar (per ASST-04) + getProgress(_db).then((progress) => { + if (!progress) return; + const companion = companions.find( + (c) => c.type === progress.selectedCharacter, + ); + if (!companion) return; + const area = document.getElementById("lesson-companion")!; + const avatar = document.getElementById( + "lesson-companion-avatar", + ) as HTMLImageElement; + avatar.src = companion.avatarUrl; + avatar.alt = companion.name; + area.style.display = "flex"; + }); + // Render keyboard renderKeyboard(keyboardContainer, layout, level);