feat(04-04): wire audio and speech into lesson, reward, forest, and app
- playCorrectSound() on correct keypress in letter and word exercises - playRewardSound() when reward image appears - speakText() for companion comment on reward screen - playForestElementSound() when new forest element renders - speakText() for companion greeting on forest screen Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+9
-7
@@ -1,11 +1,12 @@
|
|||||||
import type { ScreenName } from "./types";
|
import { speakText } from "./audio/speech";
|
||||||
import { openDB, getProgress, saveProgress } from "./storage/db";
|
|
||||||
import { initWelcomeScreen, initLessonScreen } from "./ui/screens";
|
|
||||||
import { initForestScreen, renderForestScene } from "./forest/scene";
|
|
||||||
import { startPreGeneration, initRewardScreen } from "./forest/reward";
|
|
||||||
import { getGreeting } from "./companion/companion";
|
|
||||||
import { companions } from "./companion/characters";
|
import { companions } from "./companion/characters";
|
||||||
import { showParentCodePrompt, initParentScreen } from "./ui/parent";
|
import { getGreeting } from "./companion/companion";
|
||||||
|
import { initRewardScreen, startPreGeneration } from "./forest/reward";
|
||||||
|
import { initForestScreen, renderForestScene } from "./forest/scene";
|
||||||
|
import { getProgress, openDB, saveProgress } from "./storage/db";
|
||||||
|
import type { ScreenName } from "./types";
|
||||||
|
import { initParentScreen, showParentCodePrompt } from "./ui/parent";
|
||||||
|
import { initLessonScreen, initWelcomeScreen } from "./ui/screens";
|
||||||
|
|
||||||
let db: IDBDatabase;
|
let db: IDBDatabase;
|
||||||
let lessonCleanup: (() => void) | null = null;
|
let lessonCleanup: (() => void) | null = null;
|
||||||
@@ -215,6 +216,7 @@ export async function initApp(): Promise<void> {
|
|||||||
// Fetch greeting async (shows immediately with avatar, text fills in)
|
// Fetch greeting async (shows immediately with avatar, text fills in)
|
||||||
getGreeting(progress.selectedCharacter, db).then((text) => {
|
getGreeting(progress.selectedCharacter, db).then((text) => {
|
||||||
textEl.textContent = text;
|
textEl.textContent = text;
|
||||||
|
speakText(text);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import {
|
|||||||
import { getForestComment } from "../companion/companion";
|
import { getForestComment } from "../companion/companion";
|
||||||
import { getRandomFallbackForestComment } from "../companion/fallbacks";
|
import { getRandomFallbackForestComment } from "../companion/fallbacks";
|
||||||
import type { CompanionType, ForestElement } from "../types";
|
import type { CompanionType, ForestElement } from "../types";
|
||||||
|
import { playRewardSound } from "../audio/sounds";
|
||||||
|
import { speakText } from "../audio/speech";
|
||||||
|
|
||||||
// --- Element types for random selection ---
|
// --- Element types for random selection ---
|
||||||
|
|
||||||
@@ -192,16 +194,18 @@ export async function initRewardScreen(
|
|||||||
};
|
};
|
||||||
const elementId = await saveForestElement(db, element);
|
const elementId = await saveForestElement(db, element);
|
||||||
|
|
||||||
// 6. Replace placeholder with actual image
|
// 6. Replace placeholder with actual image + play reward sound
|
||||||
const objectUrl = URL.createObjectURL(result.blob);
|
const objectUrl = URL.createObjectURL(result.blob);
|
||||||
imageArea.innerHTML = "";
|
imageArea.innerHTML = "";
|
||||||
const img = document.createElement("img");
|
const img = document.createElement("img");
|
||||||
img.src = objectUrl;
|
img.src = objectUrl;
|
||||||
img.alt = result.description;
|
img.alt = result.description;
|
||||||
imageArea.appendChild(img);
|
imageArea.appendChild(img);
|
||||||
|
playRewardSound();
|
||||||
|
|
||||||
// 7. Show companion comment
|
// 7. Show companion comment + speak it aloud
|
||||||
companionTextEl.textContent = comment;
|
companionTextEl.textContent = comment;
|
||||||
|
speakText(comment);
|
||||||
|
|
||||||
// 8. Wire buttons
|
// 8. Wire buttons
|
||||||
continueBtn.onclick = () => {
|
continueBtn.onclick = () => {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { Progress, LevelStatus } from "../types";
|
import type { Progress, LevelStatus } from "../types";
|
||||||
import { levels } from "../game/levels";
|
import { levels } from "../game/levels";
|
||||||
import { getProgress, getForestElements } from "../storage/db";
|
import { getProgress, getForestElements } from "../storage/db";
|
||||||
|
import { playForestElementSound } from "../audio/sounds";
|
||||||
|
|
||||||
let activeObjectUrls: string[] = [];
|
let activeObjectUrls: string[] = [];
|
||||||
|
|
||||||
@@ -44,6 +45,7 @@ export async function renderForestScene(
|
|||||||
// Animate newly added element
|
// Animate newly added element
|
||||||
if (element.id === newElementId) {
|
if (element.id === newElementId) {
|
||||||
slot.classList.add("forest__element--new");
|
slot.classList.add("forest__element--new");
|
||||||
|
playForestElementSound();
|
||||||
}
|
}
|
||||||
|
|
||||||
slot.appendChild(img);
|
slot.appendChild(img);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { renderKeyboard, highlightKey, pressKey } from "../game/keyboard";
|
|||||||
import { getLevelByNumber } from "../game/levels";
|
import { getLevelByNumber } from "../game/levels";
|
||||||
import { getLetterIntro } from "../companion/companion";
|
import { getLetterIntro } from "../companion/companion";
|
||||||
import { getWordsForLevel } from "../game/words";
|
import { getWordsForLevel } from "../game/words";
|
||||||
|
import { playCorrectSound } from "../audio/sounds";
|
||||||
|
|
||||||
export function initWelcomeScreen(
|
export function initWelcomeScreen(
|
||||||
db: IDBDatabase,
|
db: IDBDatabase,
|
||||||
@@ -213,6 +214,7 @@ export function initLessonScreen(
|
|||||||
|
|
||||||
if (result.correct) {
|
if (result.correct) {
|
||||||
cumulativeCorrect++;
|
cumulativeCorrect++;
|
||||||
|
playCorrectSound();
|
||||||
const currentEl = letterArea.querySelector(".falling-letter");
|
const currentEl = letterArea.querySelector(".falling-letter");
|
||||||
if (currentEl) {
|
if (currentEl) {
|
||||||
currentEl.classList.add("falling-letter--dissolve");
|
currentEl.classList.add("falling-letter--dissolve");
|
||||||
@@ -288,6 +290,7 @@ export function initLessonScreen(
|
|||||||
|
|
||||||
if (result.correct) {
|
if (result.correct) {
|
||||||
cumulativeCorrect++;
|
cumulativeCorrect++;
|
||||||
|
playCorrectSound();
|
||||||
|
|
||||||
if (result.exerciseComplete) {
|
if (result.exerciseComplete) {
|
||||||
document.removeEventListener("keydown", onKeyDown);
|
document.removeEventListener("keydown", onKeyDown);
|
||||||
|
|||||||
Reference in New Issue
Block a user