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
+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'