15 KiB
15 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 02-gemini-integration-asset-pipeline | 05 | execute | 3 |
|
|
false |
|
|
Purpose: This is where users see the AI-generated assets for the first time. The welcome screen transforms from emoji placeholders to real character art, and the companion becomes a visible presence. Output: Updated characters.ts, screens.ts, app.ts with avatar images and greeting text.
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/02-gemini-integration-asset-pipeline/02-03-SUMMARY.md @.planning/phases/02-gemini-integration-asset-pipeline/02-04-SUMMARY.md@src/companion/characters.ts @src/ui/screens.ts @src/app.ts @src/styles/main.css
```typescript export interface CompanionDefinition { type: CompanionType; name: string; emoji: string; personality: string; } export const companions: CompanionDefinition[]; ```export async function getGreeting(characterType: CompanionType, db: IDBDatabase): Promise<string>;
export async function initApp(): Promise<void>;
export function showScreen(name: ScreenName): void;
Add static imports for each avatar PNG (Vite resolves these to hashed URLs):
```typescript
import feeLilaAvatar from "../assets/companions/fee-lila/avatar.png";
import einhornStellaAvatar from "../assets/companions/einhorn-stella/avatar.png";
import fuchsFinnAvatar from "../assets/companions/fuchs-finn/avatar.png";
import euleElsaAvatar from "../assets/companions/eule-elsa/avatar.png";
```
Extend `CompanionDefinition` interface:
```typescript
export interface CompanionDefinition {
type: CompanionType;
name: string;
emoji: string;
personality: string;
avatarUrl: string; // NEW — Vite-resolved URL to avatar PNG
}
```
Add `avatarUrl` to each companion entry:
```typescript
{ type: "fee", name: "Lila", emoji: "🧚", personality: "...", avatarUrl: feeLilaAvatar },
{ type: "einhorn", name: "Stella", emoji: "🦄", personality: "...", avatarUrl: einhornStellaAvatar },
{ type: "fuchs", name: "Finn", emoji: "🦊", personality: "...", avatarUrl: fuchsFinnAvatar },
{ type: "eule", name: "Elsa", emoji: "🦉", personality: "...", avatarUrl: euleElsaAvatar },
```
**Step 2: Update welcome screen in src/ui/screens.ts** (per D-05, D-06, ASST-03)
In `initWelcomeScreen`, change the card innerHTML from emoji to img with onerror fallback:
Replace:
```typescript
card.innerHTML = `
<span class="companion-card__emoji">${c.emoji}</span>
<span class="companion-card__name">${c.name}</span>
`;
```
With:
```typescript
card.innerHTML = `
<img class="companion-card__avatar" src="${c.avatarUrl}" alt="${c.name}"
onerror="this.style.display='none'; this.nextElementSibling.style.display='block';">
<span class="companion-card__emoji" style="display:none">${c.emoji}</span>
<span class="companion-card__name">${c.name}</span>
`;
```
Per D-06: If the img fails to load, the onerror handler hides the img and shows the emoji fallback.
**Step 3: Add CSS for avatar images in src/styles/main.css**
Add after existing `.companion-card__emoji` styles:
```css
.companion-card__avatar {
width: 80px;
height: 80px;
border-radius: 50%;
object-fit: cover;
margin-bottom: 0.5rem;
}
```
Also add a companion avatar class for the lesson screen:
```css
.companion-area {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
background: var(--bg-cream);
border-radius: 1rem;
margin-bottom: 1rem;
}
.companion-area__avatar {
width: 48px;
height: 48px;
border-radius: 50%;
object-fit: cover;
flex-shrink: 0;
}
.companion-area__text {
font-family: 'Nunito', sans-serif;
font-size: 1rem;
color: var(--text-warm);
line-height: 1.4;
}
```
In `index.html`, inside `screen-forest` div, add a greeting container before the level grid:
```html
<div id="forest-greeting" class="companion-area" style="display:none">
<img id="forest-greeting-avatar" class="companion-area__avatar" src="" alt="">
<p id="forest-greeting-text" class="companion-area__text"></p>
</div>
```
In `src/app.ts`, update `initApp()` for returning users:
```typescript
import { getGreeting } from "./companion/companion";
import { companions } from "./companion/characters";
```
After `initForestScreen(db, startLessonFromForest)` for returning users, add:
```typescript
// 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 fallback)
getGreeting(progress.selectedCharacter, db).then(text => {
textEl.textContent = text;
});
}
```
**Step 2: Add companion avatar to lesson screen** (per ASST-04)
In `index.html`, inside `screen-lesson` div, add a companion area above the letter-area:
```html
<div id="lesson-companion" class="companion-area" style="display:none">
<img id="lesson-companion-avatar" class="companion-area__avatar" src="" alt="">
<p id="lesson-companion-text" class="companion-area__text"></p>
</div>
```
In `src/ui/screens.ts` `initLessonScreen`, after getting the level parameter, look up the selected companion and show avatar:
```typescript
import { companions } from "../companion/characters";
import { getProgress } from "../storage/db";
```
Add near the beginning of `initLessonScreen`:
```typescript
// 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";
});
```
Note: Per D-14, companion text does NOT appear during typing exercise in Phase 2. Only the avatar is shown. The "Entdecken" phase with letter introduction text comes in Phase 3.
What was built:
1. Welcome screen now shows real generated avatar images (not emoji) for companion selection
2. Forest screen shows companion greeting text (AI-generated or fallback)
3. Lesson screen shows companion avatar
4. All 9 generated PNG assets (4 character sheets, 4 avatars, 1 style reference) are in the asset directories
5. Fallback SVG images exist for when API is unavailable
6. Full Gemini API client with retry, rate-limiting, and fallback strategy
How to verify:
1. Run `npm run dev` and open `http://{vps-ip}:5173` in browser
2. On welcome screen: Verify 4 companion cards show real avatar images (not emoji)
3. Select a companion and layout, click "Los geht's"
4. On forest screen: Verify a greeting text appears near the top with the companion avatar
5. Start a lesson: Verify the companion avatar appears in the lesson screen
6. Complete the lesson and return to forest: Greeting should still show
7. Refresh the page: Returning user flow should show greeting on forest screen
Fallback test (optional):
8. Rename public/config.json temporarily to config.json.bak
9. Refresh the app — greeting text should show a static fallback (no crash)
10. Rename it back
<success_criteria>
- Generated avatars replace emoji in welcome screen cards
- Emoji fallback works when image fails to load
- Companion greeting appears on forest screen (AI or fallback)
- Companion avatar visible during lessons
- No console errors during normal usage </success_criteria>