Files
geograsim/App/sims/klima/scripts/preview-sounds.html
T
Adminator dfb9e80664 Klima: Engine extrahiert + 2D refactored + Drama-Track + Bugfixes
- engine.js als headless Single Source of Truth (KlimaEngine)
- game-2d.html nutzt engine (-515 Zeilen Duplikation)
- rising-pressure.mp3 als Drama-Slot, Auto-Switch bei kritischem State
- state.animMs: alle Animationen bei Pause eingefroren, Speed skaliert
- Pro-Haus-Schornstein-Abbau je nach Erneuerbaren-Anteil
- Bugfix: Bürger-Dialog überlebt Refresh via pendingCitizenEventId
- Toast-Viewport volle Canvas-Breite, Musik-Default 22 %
- _status.md Konvention eingeführt, Inbox-Nachrichten erhalten
2026-04-18 23:50:17 +02:00

157 lines
6.0 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Klimawächter · Sound-Preview</title>
<link rel="stylesheet" href="../../../assets/fonts/inter.css">
<link rel="stylesheet" href="../../../assets/css/design-system.css">
<style>
body { overflow: auto; height: auto; min-height: 100vh; background: var(--ggs-bg); padding: 32px; }
h1 { color: var(--ggs-fjord-dark); font-size: 28px; margin-bottom: 8px; }
.lead { color: var(--ggs-text-muted); margin-bottom: 24px; max-width: 700px; line-height: 1.5; }
.group { margin-bottom: 28px; }
.group h2 {
color: var(--ggs-fjord-dark); font-size: 16px; text-transform: uppercase;
letter-spacing: 0.06em; margin-bottom: 12px; border-bottom: 2px solid var(--ggs-border);
padding-bottom: 4px;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 12px;
}
.card {
background: var(--ggs-white); border: 1px solid var(--ggs-border);
border-radius: 10px; padding: 12px 14px;
display: flex; flex-direction: column; gap: 6px;
}
.card h3 { font-size: 13px; font-weight: 800; color: var(--ggs-fjord-dark); margin: 0; }
.card .meta { font-size: 11px; color: var(--ggs-text-muted); }
.card .prompt { font-size: 11px; color: var(--ggs-text); line-height: 1.4; font-style: italic; }
.card audio { width: 100%; margin-top: 4px; }
.card.playing { border-color: var(--ggs-moss); box-shadow: 0 2px 8px rgba(90,138,94,0.3); }
.toolbar {
position: sticky; top: 0; background: var(--ggs-bg);
padding: 10px 0; border-bottom: 1px solid var(--ggs-border);
margin-bottom: 24px; z-index: 10;
display: flex; gap: 12px; align-items: center; flex-wrap: wrap;
}
.toolbar label { font-size: 13px; color: var(--ggs-text); display: flex; align-items: center; gap: 6px; }
.toolbar input[type=range] { width: 140px; }
.toolbar .count { font-size: 12px; color: var(--ggs-text-muted); margin-left: auto; }
.btn {
padding: 6px 14px; border: 1.5px solid var(--ggs-fjord); background: var(--ggs-white);
color: var(--ggs-fjord-dark); border-radius: 6px; font-weight: 600; cursor: pointer;
font-family: inherit; font-size: 13px;
}
.btn:hover { background: var(--ggs-fjord-light); }
.btn.primary { background: var(--ggs-fjord); color: #fff; }
.btn.primary:hover { background: var(--ggs-fjord-dark); }
</style>
</head>
<body>
<h1>🔊 Klimawächter — Sound-Preview</h1>
<p class="lead">
Alle 32 SFX aus <code>assets/sounds/</code>. Klick auf einen Play-Button, um zu hören.
Falls ein Sound nicht passt: Dateinamen merken, ich kann den Prompt feintunen und
nur diesen einen neu generieren (<code>python generate-sounds.py &lt;name&gt; --force</code>).
</p>
<div class="toolbar">
<button class="btn primary" id="btn-play-all">▶ Alle nacheinander</button>
<button class="btn" id="btn-stop">⏹ Stop</button>
<label>Lautstärke <input type="range" id="vol" min="0" max="1" step="0.05" value="0.7"></label>
<span class="count" id="count">Lädt …</span>
</div>
<div id="root"></div>
<script>
const GROUPS = {
'UI + Meta': ['ui-click', 'ui-error', 'ui-confirm', 'achievement', 'warning', 'praise', 'level-won', 'level-lost', 'game-start', 'demolish'],
'Bau (Klima-positiv)': ['build-forest', 'build-solar', 'build-wind', 'build-green-roof', 'build-bikes', 'build-mangrove'],
'Bau (Küstenschutz)': ['build-sand-fill', 'build-dike', 'build-sea-wall'],
'Bau (Klima-negativ / Schein)': ['build-coal', 'build-airport', 'build-cloud-seed', 'build-generic'],
'Ereignisse': ['event-temperature', 'event-flood', 'event-vegetation', 'event-water', 'event-glacier', 'event-blackout', 'event-power-back', 'event-tourism', 'event-co2'],
'Dramatik (wenn es kippt)': ['drama-paris-missed', 'drama-disaster', 'drama-game-over'],
};
let DATA = { sounds: [] };
let currentAudio = null;
async function load() {
const res = await fetch('sounds-list.json');
DATA = await res.json();
render();
}
function findMeta(file) {
return DATA.sounds.find(s => s.file === file + '.mp3');
}
function render() {
const root = document.getElementById('root');
let html = '';
let total = 0;
for (const [groupName, files] of Object.entries(GROUPS)) {
html += '<div class="group"><h2>' + groupName + ' (' + files.length + ')</h2><div class="grid">';
for (const file of files) {
const meta = findMeta(file);
const prompt = meta ? meta.prompt : '';
const dur = meta ? meta.duration : '?';
html += `
<div class="card" data-file="${file}">
<h3>${file}.mp3</h3>
<div class="meta">Dauer: ${dur} s</div>
<div class="prompt">${prompt}</div>
<audio controls preload="none" src="../assets/sounds/${file}.mp3"></audio>
</div>`;
total++;
}
html += '</div></div>';
}
root.innerHTML = html;
document.getElementById('count').textContent = total + ' Sounds';
// Volumen global setzen
const vol = document.getElementById('vol');
document.querySelectorAll('audio').forEach(a => {
a.volume = parseFloat(vol.value);
a.addEventListener('play', () => {
document.querySelectorAll('audio').forEach(o => { if (o !== a) { o.pause(); o.currentTime = 0; } });
a.closest('.card').classList.add('playing');
currentAudio = a;
});
a.addEventListener('pause', () => a.closest('.card').classList.remove('playing'));
a.addEventListener('ended', () => a.closest('.card').classList.remove('playing'));
});
vol.addEventListener('input', () => {
document.querySelectorAll('audio').forEach(a => a.volume = parseFloat(vol.value));
});
}
document.getElementById('btn-play-all').addEventListener('click', () => {
const all = Array.from(document.querySelectorAll('audio'));
let i = 0;
const next = () => {
if (i >= all.length) return;
const a = all[i++];
a.currentTime = 0;
a.play();
a.addEventListener('ended', next, { once: true });
};
next();
});
document.getElementById('btn-stop').addEventListener('click', () => {
document.querySelectorAll('audio').forEach(a => { a.pause(); a.currentTime = 0; });
});
load();
</script>
</body>
</html>