Tourismustal: Vorproduzierte Sprach-Audios statt Browser-TTS

52 MP3s (OpenAI gpt-4o-mini-tts, Stimme coral, warme deutsche
Erzaehlstimme) fuer alle Info-Karten: Erst-Bau-Erklaerungen, Meilensteine,
Events, Szenen-Intros, Auftraege, Megabauwerk. Texte automatisch aus
data.js gezogen (Generator im Scratchpad-Muster). Abspiel-Logik:
assets/audio-info/<id>.mp3, Browser-TTS nur Not-Fallback. Intro-IDs je
Szene eindeutig, Onboarding bleibt immer modal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-11 12:47:46 +02:00
parent 7e3158aede
commit 32793d0bca
55 changed files with 29 additions and 10 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+3 -3
View File
@@ -143,7 +143,7 @@ export const SCENARIOS = {
{ id: 'nature', label: 'Naturwert mind. 55 %', check: s => s.nature >= 55 },
],
intro: {
id: 'intro', icon: '👋', title: 'Willkommen im Tal!',
id: 'intro-pionierzeit', icon: '👋', title: 'Willkommen im Tal!',
text: 'Sommer 1975. Du übernimmst die Entwicklung eines kleinen, stillen '
+ 'Alpentals.\n\nFang klein an: Baue zuerst ein Gasthaus im Tal, in der '
+ 'Nähe des Dorfes. Dann kommen die ersten Wanderer und alles Weitere '
@@ -179,7 +179,7 @@ export const SCENARIOS = {
{ id: 'nature', label: 'Naturwert mind. 60 %', check: s => s.nature >= 60 },
],
intro: {
id: 'intro', icon: '📈', title: 'Der Boom ist da!',
id: 'intro-boom', icon: '📈', title: 'Der Boom ist da!',
text: 'Sommer 2000. Das Tal ist längst kein Geheimtipp mehr die Gäste '
+ 'strömen, und alle wollen ausbauen.\n\nAber die Natur trägt schon '
+ 'Spuren, und die Einheimischen murren. Schaffst du Wachstum, OHNE '
@@ -218,7 +218,7 @@ export const SCENARIOS = {
{ id: 'nature', label: 'Naturwert mind. 65 %', check: s => s.nature >= 65 },
],
intro: {
id: 'intro', icon: '🌡️', title: 'Die Schneegrenze steigt',
id: 'intro-klimawandel', icon: '🌡️', title: 'Die Schneegrenze steigt',
text: 'Sommer 2035. Die Winter werden wärmer, Schnee ist keine '
+ 'Selbstverständlichkeit mehr. Messdaten zeigen: In den Alpen '
+ 'schmilzt der Schnee 59 Tage pro Jahrzehnt früher seit 1970 ist '
+1 -1
View File
@@ -141,7 +141,7 @@ function onTileTap(c, r) {
const first = !state.firedEvents.has('mega-info');
state.firedEvents.add('mega-info');
ui.showCard({
id: `mega-${res.merged.c}-${res.merged.r}`, icon: '🏛️', sound: 'milestone',
id: `mega-${res.merged.c}-${res.merged.r}`, audio: 'mega', icon: '🏛️', sound: 'milestone',
title: `Megabauwerk: ${res.merged.name}!`,
text: `Vier gleiche Gebäude im Quadrat sind zu EINEM großen ${res.merged.name} verschmolzen.`
+ '\n\nDas ist ein Skaleneffekt: Das Megabauwerk leistet so viel wie '
+25 -6
View File
@@ -146,7 +146,7 @@ export class UI {
this.infoMode = this.infoMode === 'audio' ? 'read' : 'audio';
localStorage.setItem('tt_infomode', this.infoMode);
this._syncInfoModeBtn(e.currentTarget);
this._speak(null);
this._stopVoice();
this.toast(this.infoMode === 'audio'
? '🔊 Audio-Modus: Meldungen werden vorgelesen, das Spiel läuft weiter. Entscheidungen pausieren weiterhin.'
: '📖 Lese-Modus: Meldungen öffnen sich als Karte, das Spiel pausiert.', 5000);
@@ -486,12 +486,31 @@ export class UI {
el.classList.remove('hidden');
el.onclick = () => {
el.classList.add('hidden');
this._speak(null); // Vorlesen stoppen
this._stopVoice();
this.showCard({ ...card, transient: true, forceModal: true });
};
clearTimeout(this._bannerTimer);
this._bannerTimer = setTimeout(() => el.classList.add('hidden'), 9000);
this._speak(`${card.title}. ${card.text}`);
this._speakCard(card);
}
// Vorproduzierte Sprach-Audios (assets/audio-info/<id>.mp3, OpenAI-TTS).
// Browser-TTS nur als Not-Fallback, falls eine Datei fehlt.
_stopVoice() {
if (this._voice) { this._voice.pause(); this._voice = null; }
if ('speechSynthesis' in window) speechSynthesis.cancel();
}
_speakCard(card) {
this._stopVoice();
const id = card.audio || card.id;
if (!id) { this._speak(`${card.title}. ${card.text}`); return; }
const a = new Audio(`assets/audio-info/${id}.mp3`);
a.volume = 0.95;
this._voice = a;
const fallback = () => { if (this._voice === a) this._speak(`${card.title}. ${card.text}`); };
a.onerror = fallback;
a.play().catch(fallback);
}
// Sprachausgabe (Browser-TTS, deutsche Stimme). null = stoppen.
@@ -686,7 +705,7 @@ export class UI {
// ---------- Onboarding ----------
showOnboarding() {
this.showCard({
icon: '🏔️', transient: true,
icon: '🏔️', transient: true, forceModal: true,
title: 'Willkommen! (1/3) So spielst du',
text: 'Rechts ein Gebäude wählen, dann auf die Karte tippen fertig. '
+ 'Nach dem Bau legst du das Werkzeug automatisch ab.\n\n'
@@ -696,7 +715,7 @@ export class UI {
+ 'Schritt für Schritt frei.',
});
this.showCard({
icon: '🚶', transient: true,
icon: '🚶', transient: true, forceModal: true,
title: 'Die Gäste (2/3) dein Kreislauf',
text: 'Jede Figur steht für etwa 10 Gäste. Sie kommen mit dem Bus, '
+ 'wandern, kehren ein und reisen wieder ab.\n\n'
@@ -707,7 +726,7 @@ export class UI {
+ 'Wer konsumiert, bringt Geld wer nicht kommen kann, keins.',
});
this.showCard({
icon: '🔍', transient: true,
icon: '🔍', transient: true, forceModal: true,
title: 'Durchblick (3/3) warum läuft es (nicht)?',
text: 'Links stehen deine Kennzahlen. Kommen weniger Gäste als möglich, '
+ 'erscheint dort der GRUND (z.B. „Engpass: Anreise").\n\n'