- Uebersicht
+ Übersicht
Aktuelle Stufe
@@ -174,7 +174,7 @@
-
+
diff --git a/public/forest-birds.mp3 b/public/forest-birds.mp3
new file mode 100644
index 0000000..492a584
Binary files /dev/null and b/public/forest-birds.mp3 differ
diff --git a/src/audio/sounds.ts b/src/audio/sounds.ts
index 18b62fa..95c3412 100644
--- a/src/audio/sounds.ts
+++ b/src/audio/sounds.ts
@@ -2,6 +2,8 @@ import { getDB } from "../app";
import { getSettings } from "../storage/db";
let audioCtx: AudioContext | null = null;
+let ambientAudio: HTMLAudioElement | null = null;
+let ambientStarted = false;
/**
* Creates or resumes AudioContext on first user interaction.
@@ -21,6 +23,39 @@ export function initAudioOnInteraction(): void {
}
}
+/**
+ * Start ambient forest sounds (loops quietly in background).
+ * Called once on first user interaction alongside AudioContext init.
+ */
+export async function startAmbientSound(): Promise
{
+ if (ambientStarted) return;
+ ambientStarted = true;
+ try {
+ if (!(await isAudioEnabled())) return;
+ ambientAudio = new Audio("/forest-birds.mp3");
+ ambientAudio.loop = true;
+ ambientAudio.volume = 0.08;
+ ambientAudio.play().catch(() => {
+ // Autoplay blocked — will retry on next interaction
+ ambientStarted = false;
+ });
+ } catch {
+ ambientStarted = false;
+ }
+}
+
+/**
+ * Update ambient sound state based on mute toggle.
+ */
+export function setAmbientMuted(muted: boolean): void {
+ if (!ambientAudio) return;
+ if (muted) {
+ ambientAudio.pause();
+ } else {
+ ambientAudio.play().catch(() => {});
+ }
+}
+
async function isAudioEnabled(): Promise {
try {
const db = getDB();
diff --git a/src/companion/companion.test.ts b/src/companion/companion.test.ts
index 9b90e5e..f64388a 100644
--- a/src/companion/companion.test.ts
+++ b/src/companion/companion.test.ts
@@ -102,7 +102,7 @@ describe("getGreeting", () => {
"Sanft, ermutigend, ein bisschen vertraeumt",
);
expect(systemPrompt).toMatch(/morgen|mittag|abend/);
- expect(systemPrompt).toContain("Max. 2 kurze Saetze");
+ expect(systemPrompt).toContain("Max. 2 kurze Sätze");
});
it("calls incrementApiCall after successful API call", async () => {
@@ -174,7 +174,7 @@ describe("getLetterIntro", () => {
db,
);
- expect(result).toBe("Druecke die leuchtende Taste!");
+ expect(result).toBe("Drücke die leuchtende Taste!");
});
});
diff --git a/src/companion/companion.ts b/src/companion/companion.ts
index c41324a..2e859f2 100644
--- a/src/companion/companion.ts
+++ b/src/companion/companion.ts
@@ -41,18 +41,18 @@ export async function getGreeting(
const companion = getCompanion(characterType);
const systemPrompt = `Du bist ${companion.name}, ein(e) ${companion.personality} im Zauberwald.
-Du sprichst mit einem 7-jaehrigen Kind, das Tippen lernt.
+Du sprichst mit einem 7-jährigen Kind, das Tippen lernt.
Regeln:
-- Max. 2 kurze Saetze (insgesamt max. 25 Woerter)
-- Einfache Sprache, kurze Woerter
+- Max. 2 kurze Sätze (insgesamt max. 25 Wörter)
+- Einfache Sprache, kurze Wörter
- Freundlich und warm, nie belehrend
- Keine Bewertung von Leistung
- Kein Englisch
- Beziehe dich auf die Tageszeit: ${timeOfDay}`;
const result = await generateText(
- "Schreib eine Begruessung.",
+ "Schreib eine Begrüssung.",
systemPrompt,
config,
);
@@ -72,7 +72,7 @@ export async function getLetterIntro(
if (!config) {
return (
getFallbackLetterIntro(letter)?.text ??
- "Druecke die leuchtende Taste!"
+ "Drücke die leuchtende Taste!"
);
}
@@ -80,18 +80,18 @@ export async function getLetterIntro(
if (!canCall) {
return (
getFallbackLetterIntro(letter)?.text ??
- "Druecke die leuchtende Taste!"
+ "Drücke die leuchtende Taste!"
);
}
const companion = getCompanion(characterType);
const systemPrompt = `Du bist ${companion.name}, ein(e) ${companion.personality} im Zauberwald.
-Du stellst einem 7-jaehrigen Kind den Buchstaben ${letter} vor.
+Du stellst einem 7-jährigen Kind den Buchstaben ${letter} vor.
Regeln:
-- Max. 2 Saetze (max. 25 Woerter)
-- Erklaere, welcher Finger den Buchstaben drueckt: ${fingerDescription}
-- Verwende eine bildhafte Eselsbruecke, die zum Wald/Natur/Tiere-Thema passt
+- Max. 2 Sätze (max. 25 Wörter)
+- Erkläre, welcher Finger den Buchstaben drückt: ${fingerDescription}
+- Verwende eine bildhafte Eselsbrücke, die zum Wald/Natur/Tiere-Thema passt
- Einfache Sprache
- Kein Englisch`;
@@ -103,7 +103,7 @@ Regeln:
if (!result) {
return (
getFallbackLetterIntro(letter)?.text ??
- "Druecke die leuchtende Taste!"
+ "Drücke die leuchtende Taste!"
);
}
@@ -124,13 +124,13 @@ export async function getForestComment(
const companion = getCompanion(characterType);
const systemPrompt = `Du bist ${companion.name}, ein(e) ${companion.personality} im Zauberwald.
-Ein Kind hat gerade eine Tippuebung abgeschlossen. Im Wald ist ein neues Wesen erschienen: ${elementDescription}.
+Ein Kind hat gerade eine Tippübung abgeschlossen. Im Wald ist ein neues Wesen erschienen: ${elementDescription}.
Regeln:
-- Max. 2 kurze Saetze (insgesamt max. 25 Woerter)
-- Druecke Staunen und Freude aus
-- Einfache Sprache fuer 7-Jaehrige
-- Kein Lob fuer Leistung, sondern Begeisterung ueber das neue Waldelement
+- Max. 2 kurze Sätze (insgesamt max. 25 Wörter)
+- Drücke Staunen und Freude aus
+- Einfache Sprache für 7-Jährige
+- Kein Lob für Leistung, sondern Begeisterung über das neue Waldelement
- Kein Englisch`;
const result = await generateText(
diff --git a/src/companion/fallbacks.ts b/src/companion/fallbacks.ts
index c652ea9..6bf1267 100644
--- a/src/companion/fallbacks.ts
+++ b/src/companion/fallbacks.ts
@@ -39,7 +39,7 @@ export const fallbackGreetings: FallbackGreeting[] = [
timeOfDay: "any",
},
{
- text: "Willkommen zurueck! Lass uns den Wald ein bisschen wachsen lassen.",
+ text: "Willkommen zurück! Lass uns den Wald ein bisschen wachsen lassen.",
timeOfDay: "any",
},
{
@@ -60,7 +60,7 @@ export const fallbackGreetings: FallbackGreeting[] = [
timeOfDay: "abend",
},
{
- text: "Am Abend wird der Wald ganz still. Lass uns leise ueben.",
+ text: "Am Abend wird der Wald ganz still. Lass uns leise üben.",
timeOfDay: "abend",
},
];
@@ -71,13 +71,13 @@ export const fallbackForestComments: string[] = [
"Oh schau mal! Ein neuer Bewohner ist in unseren Wald gezogen!",
"Der Wald waechst und waechst! Was wird wohl als naechstes erscheinen?",
"Siehst du das? Etwas Neues hat sich zwischen den Baeumen versteckt!",
- "Unser Wald wird immer bunter. Was fuer eine Ueberraschung!",
+ "Unser Wald wird immer bunter. Was für eine Überraschung!",
"Ein neues Stueck Wald ist dazugekommen. Wie schoen das aussieht!",
"Hast du das gesehen? Der Wald hat sich veraendert!",
"Es raschelt im Unterholz. Etwas Neues ist da!",
"Die Waldlichtung hat Zuwachs bekommen. Kannst du es entdecken?",
"Jedes Mal passiert etwas Neues im Wald. Schau genau hin!",
- "Der Zauberwald hat ein Geschenk fuer dich bereit. Schau mal!",
+ "Der Zauberwald hat ein Geschenk für dich bereit. Schau mal!",
];
// --- Fallback Letter Introductions (13 entries, levels 1-6) ---
@@ -108,7 +108,7 @@ export const fallbackLetterIntros: LetterIntro[] = [
{
letter: "k",
finger: "rechter Mittelfinger",
- text: "Das K ist der Nachbar vom J. Dein rechter Mittelfinger huepft einfach hinueber.",
+ text: "Das K ist der Nachbar vom J. Dein rechter Mittelfinger hüpft einfach hinüber.",
},
// Level 3: S, L
{
@@ -141,18 +141,18 @@ export const fallbackLetterIntros: LetterIntro[] = [
{
letter: "h",
finger: "rechter Zeigefinger",
- text: "Das H ist der Nachbar vom J zur Mitte hin. Ein kurzer Sprung fuer deinen Zeigefinger!",
+ text: "Das H ist der Nachbar vom J zur Mitte hin. Ein kurzer Sprung für deinen Zeigefinger!",
},
// Level 6: E, I
{
letter: "e",
finger: "linker Mittelfinger",
- text: "Das E wohnt eine Reihe hoeher ueber dem D. Dein Mittelfinger klettert wie ein Eichhoernchen hoch.",
+ text: "Das E wohnt eine Reihe höher über dem D. Dein Mittelfinger klettert wie ein Eichhörnchen hoch.",
},
{
letter: "i",
finger: "rechter Mittelfinger",
- text: "Das I sitzt ueber dem K. Dein rechter Mittelfinger springt hoch wie ein Igel huepft.",
+ text: "Das I sitzt über dem K. Dein rechter Mittelfinger springt hoch wie ein Igel hüpft.",
},
];
diff --git a/src/forest/reward.ts b/src/forest/reward.ts
index 7abbd12..e24b9c6 100644
--- a/src/forest/reward.ts
+++ b/src/forest/reward.ts
@@ -31,7 +31,7 @@ const ELEMENT_TYPES = [
"Schmetterling",
"Vogel",
"Reh",
- "Eichhoernchen",
+ "Eichhörnchen",
"Hase",
"Frosch",
"Igel",
@@ -117,7 +117,7 @@ async function generateForestElement(db: IDBDatabase): Promise<{
ELEMENT_TYPES[Math.floor(Math.random() * ELEMENT_TYPES.length)]!;
// Build prompt
- const prompt = `Kinderbuch-Illustration, Aquarell-Stil, ${elementType}, magischer Wald, warm, einladend, fuer Kinder, Pastellfarben, kein Text, einfarbig heller gruener Hintergrund`;
+ const prompt = `Kinderbuch-Illustration, Aquarell-Stil, ${elementType}, magischer Wald, warm, einladend, für Kinder, Pastellfarben, kein Text, einfarbig heller grüner Hintergrund`;
// Generate image
const blob = await generateImage(prompt, referenceImages, config);
diff --git a/src/forest/scene.ts b/src/forest/scene.ts
index b13f257..f04d162 100644
--- a/src/forest/scene.ts
+++ b/src/forest/scene.ts
@@ -124,7 +124,7 @@ export async function initForestScreen(
continueBtn.disabled = true;
continueBtn.classList.add("forest__continue-btn--done");
} else {
- continueBtn.textContent = "Weiter ueben";
+ continueBtn.textContent = "Weiter üben";
continueBtn.disabled = false;
continueBtn.classList.remove("forest__continue-btn--done");
continueBtn.onclick = () =>
diff --git a/src/main.ts b/src/main.ts
index 4f11eea..f7da16c 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,6 +1,6 @@
import "./styles/main.css";
import { getDB, initApp } from "./app";
-import { initAudioOnInteraction } from "./audio/sounds";
+import { initAudioOnInteraction, startAmbientSound, setAmbientMuted } from "./audio/sounds";
import { getSettings, saveSettings } from "./storage/db";
window.addEventListener("error", (event) => {
@@ -51,12 +51,14 @@ document.addEventListener("DOMContentLoaded", async () => {
muteIcon.textContent = nowEnabled ? "\u{1F50A}" : "\u{1F507}";
muteBtn.classList.toggle("mute-btn--muted", !nowEnabled);
+ setAmbientMuted(!nowEnabled);
};
}
- // Initialize AudioContext on first user interaction (autoplay policy D-02)
+ // Initialize AudioContext + ambient sound on first user interaction (autoplay policy D-02)
const initAudioOnce = () => {
initAudioOnInteraction();
+ startAmbientSound();
document.removeEventListener("click", initAudioOnce);
document.removeEventListener("keydown", initAudioOnce);
};
diff --git a/src/ui/parent.ts b/src/ui/parent.ts
index 95d9132..1d061ca 100644
--- a/src/ui/parent.ts
+++ b/src/ui/parent.ts
@@ -286,7 +286,7 @@ async function importData(db: IDBDatabase, file: File): Promise {
return;
}
- if (!confirm("Fortschritt ueberschreiben? Aktuelle Daten gehen verloren.")) {
+ if (!confirm("Fortschritt überschreiben? Aktuelle Daten gehen verloren.")) {
return;
}
@@ -316,8 +316,8 @@ function clearStore(db: IDBDatabase, storeName: string): Promise {
* Reset all data with double confirmation, then reload.
*/
async function resetAllData(db: IDBDatabase): Promise {
- if (!confirm("Wirklich alles loeschen?")) return;
- if (!confirm("Alle Fortschritte und Einstellungen werden geloescht. Bist du sicher?")) return;
+ if (!confirm("Wirklich alles löschen?")) return;
+ if (!confirm("Alle Fortschritte und Einstellungen werden gelöscht. Bist du sicher?")) return;
await Promise.all(DB_STORES.map((store) => clearStore(db, store)));
location.reload();
diff --git a/src/ui/screens.ts b/src/ui/screens.ts
index 59b32af..05396e3 100644
--- a/src/ui/screens.ts
+++ b/src/ui/screens.ts
@@ -108,7 +108,7 @@ const fingerDescriptions: Record = {
// Encouragement messages for repetition protection
const encouragementMessages = [
"Magst du sehen, was als Naechstes kommt? Im Wald gibt es noch so viel zu entdecken!",
- "Der Wald wartet auf dich! Jedes Mal, wenn du uebst, waechst etwas Neues.",
+ "Der Wald wartet auf dich! Jedes Mal, wenn du übst, wächst etwas Neues.",
"Schau mal, was sich im Wald versteckt! Probier es einfach noch einmal.",
"Dein Wald wird immer schoener. Komm, wir entdecken zusammen weiter!",
];
@@ -159,8 +159,8 @@ export function initLessonScreen(
function setPhase(phase: LessonPhase): void {
const labels: Record = {
discover: "Entdecken",
- practice: "Ueben",
- words: "Woerter",
+ practice: "Üben",
+ words: "Wörter",
};
phaseIndicator.textContent = labels[phase];
}