Files
Adminator 1e51ef7def Nachtrag: alle bisher untracked Ordner + hängende Änderungen mit-committen
- Konzept/, didaktik_geografie/, didaktik_simulation/, v2-modules/, v2-platform/
- 12 code-workspace-Files
- STATUS-*.md
- viele M/D/R-Änderungen an bereits getrackten Files
- .gitignore verstärkt: **/.humaninput/, **/secret_keys.txt

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-08 02:27:02 +02:00

224 lines
12 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Heli — Audio-Matrix nach Bundesland</title>
<style>
:root{
--cream:#f5efe0; --forest:#2d5434; --sage:#7a9b76;
--orange:#c97a3f; --red:#c85c4a; --slate:#3d5165; --ink:#1f2a24;
--border:rgba(45,84,52,.18);
}
body{font-family:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;background:var(--cream);color:var(--ink);margin:0;line-height:1.5}
header{background:var(--forest);color:var(--cream);padding:14px 20px;position:sticky;top:0;z-index:50;display:flex;justify-content:space-between;align-items:baseline;flex-wrap:wrap;gap:8px}
header h1{margin:0;font-size:18px}
header nav{display:flex;gap:6px;flex-wrap:wrap;font-size:11px}
header nav a{color:var(--cream);text-decoration:none;background:rgba(255,255,255,.1);padding:3px 8px;border-radius:4px;font-weight:600}
header nav a:hover{background:rgba(255,255,255,.25)}
.container{max-width:1280px;margin:0 auto;padding:18px 22px}
.land{margin-bottom:24px;border-radius:14px;background:#fff;border:1px solid var(--border);overflow:hidden}
.land h2{margin:0;padding:14px 22px;background:linear-gradient(90deg,var(--forest),#3d6a44);color:var(--cream);font-size:16px;letter-spacing:.02em;display:flex;align-items:center;gap:8px}
.land h2 .badge{background:rgba(255,255,255,.18);padding:1px 8px;border-radius:99px;font-size:11px;font-weight:600}
.station{padding:14px 22px;border-top:1px solid var(--border)}
.station:first-of-type{border-top:none}
.station h3{margin:0 0 4px;color:var(--forest);font-size:14px}
.station .meta{color:var(--slate);font-size:12px;margin-bottom:8px}
table{width:100%;border-collapse:collapse;margin-top:6px;font-size:12px;background:#fafafa;border-radius:6px;overflow:hidden}
th{background:rgba(45,84,52,.08);color:var(--forest);text-align:left;padding:6px 8px;font-weight:700;font-size:11px;letter-spacing:.02em;text-transform:uppercase}
td{padding:5px 8px;border-top:1px solid rgba(45,84,52,.06);vertical-align:top}
td.tag{font-family:"SF Mono","Consolas",monospace;color:var(--orange);font-size:10px;background:rgba(201,122,63,.07);width:160px}
td.phase{font-weight:600;color:var(--slate);width:120px;font-size:11px}
td.audio{width:230px}
td.audio audio{width:100%;height:26px}
td.text{font-style:italic;color:var(--ink)}
td.text .missing{color:var(--red);font-style:normal;font-size:11px}
.mission-block{margin-top:10px;background:rgba(201,122,63,.06);border-left:3px solid var(--orange);padding:8px 12px;border-radius:0 6px 6px 0}
.mission-block h4{margin:0 0 4px;font-size:13px;color:var(--ink)}
.mission-block .desc{font-size:11px;color:var(--slate)}
.scen-tag{display:inline-block;font-size:10px;font-weight:700;padding:1px 6px;border-radius:3px;margin-left:4px;text-transform:uppercase}
.scen-berg{background:#dceadd;color:#3a6b3e}
.scen-wasser{background:#dae8ec;color:#1f4e5a}
.scen-autobahn{background:#f2eacc;color:#8a7020}
.scen-tal{background:#f2ddd8;color:#8a3a2a}
</style>
</head>
<body>
<header>
<h1>🚁 Heli · Audio-Matrix nach Bundesland</h1>
<nav id="nav"></nav>
</header>
<div class="container" id="content"><p style="color:var(--slate)">Lade …</p></div>
<script>
// === 9 Bundeslaender mit Stuetzpunkten ===
const LAENDER = [
{ name: 'Vorarlberg', base: 'vbg', stations: ['hohenems','nenzing'] },
{ name: 'Tirol', base: 'tir', stations: ['innsbruck','zams_c5','kitzbuehel','lienz'] },
{ name: 'Salzburg', base: 'sbg', stations: ['salzburg_c6'] },
{ name: 'Oberösterreich', base: 'ooe', stations: ['linz_c10'] },
{ name: 'Niederösterreich', base: 'noe', stations: ['krems_c2','wr_neustadt_c3'] },
{ name: 'Wien', base: 'w', stations: ['wien_c9'] },
{ name: 'Burgenland', base: 'bgl', stations: ['oberwart_c16'] },
{ name: 'Steiermark', base: 'stm', stations: ['graz_c12','niederoeblarn_c14'] },
{ name: 'Kärnten', base: 'ktn', stations: ['klagenfurt_c11'] },
];
// Stuetzpunkt-Definitionen (gespiegelt aus game.html BASE_CONFIG)
const STATIONS = {
nenzing: {heliKey:'c8', heliName:'Nenzing OE-XEN', ort:'Nenzing (Vorarlberg)', tower:'Bregenz Tower'},
hohenems: {heliKey:'ara', heliName:'ARA Flugrettung RK-1', ort:'Hohenems (Vorarlberg)', tower:'Bregenz Tower'},
innsbruck: {heliKey:'c1', heliName:'Innsbruck OE-XVB', ort:'Innsbruck-Flughafen (Tirol)', tower:'Innsbruck Tower'},
zams_c5: {heliKey:'c5', heliName:'Zams OE-XEZ', ort:'Zams (Tirol)', tower:'Innsbruck Tower'},
kitzbuehel: {heliKey:'c4', heliName:'Kitzbühel OE-XEY', ort:'Reith bei Kitzbühel (Tirol)', tower:'Innsbruck Tower'},
lienz: {heliKey:'c7', heliName:'Lienz OE-XER', ort:'Lienz (Osttirol)', tower:'Klagenfurt Tower'},
krems_c2: {heliKey:'c2', heliName:'Krems OE-XVI', ort:'Krems-Gneixendorf (NÖ)', tower:'Wien Tower'},
wr_neustadt_c3: {heliKey:'c3', heliName:'Wr. Neustadt OE-XEL', ort:'Wiener Neustadt Ost (NÖ)', tower:'Wien Tower'},
salzburg_c6: {heliKey:'c6', heliName:'Salzburg OE-XVL', ort:'Flughafen Salzburg', tower:'Salzburg Tower'},
wien_c9: {heliKey:'c9', heliName:'Wien-Baumgasse OE-XVO', ort:'Wien-Baumgasse', tower:'Wien Tower'},
linz_c10: {heliKey:'c10', heliName:'Linz OE-XVP', ort:'Flughafen Linz-Hörsching', tower:'Linz Tower'},
klagenfurt_c11: {heliKey:'c11', heliName:'Klagenfurt OE-XEK', ort:'Flughafen Klagenfurt', tower:'Klagenfurt Tower'},
graz_c12: {heliKey:'c12', heliName:'Graz OE-XEM', ort:'Flughafen Graz-Thalerhof', tower:'Graz Tower'},
niederoeblarn_c14:{heliKey:'c14', heliName:'Niederöblarn OE-XEW', ort:'Flugplatz Niederöblarn (Stmk)', tower:'Graz Tower'},
oberwart_c16: {heliKey:'c16', heliName:'Pinkafeld OE-XEU', ort:'Flugplatz Pinkafeld (Bgld)', tower:'Wien Tower'},
};
// Alle Missionen (gespiegelt aus game.html)
const MISSIONS = [
{id:'m1', base:'nenzing', title:'Skiunfall Damüls', scen:'berg-schnee', diff:'medium', region:'Vbg'},
{id:'m2', base:'hohenems', title:'Verkehrsunfall A14', scen:'autobahn', diff:'easy', region:'Vbg'},
{id:'m3', base:'nenzing', title:'Bergrettung Brand', scen:'berg-fels', diff:'medium', region:'Vbg'},
{id:'m4', base:'innsbruck', title:'Skiunfall Sölden', scen:'berg-schnee', diff:'medium', region:'Tirol'},
{id:'m5', base:'hohenems', title:'Seerettung Bregenz', scen:'wasser', diff:'easy', region:'Vbg'},
{id:'m6', base:'hohenems', title:'Canyoning Mellau', scen:'schlucht', diff:'medium', region:'Vbg'},
{id:'m7', base:'innsbruck', title:'Wanderunfall Kufstein', scen:'berg-fels', diff:'easy', region:'Tirol'},
{id:'m8', base:'nenzing', title:'Notfall Gaschurn', scen:'tal-dorf', diff:'hard', region:'Vbg'},
{id:'m9', base:'innsbruck', title:'Lawinen Mayrhofen', scen:'berg-schnee', diff:'hard', region:'Tirol'},
{id:'m10', base:'wien_c9', title:'A2 Baden', scen:'autobahn', diff:'easy', region:'Wien'},
{id:'m11', base:'wien_c9', title:'Donau Wien', scen:'wasser', diff:'medium', region:'Wien'},
{id:'m12', base:'wr_neustadt_c3', title:'Schneeberg', scen:'berg-fels', diff:'hard', region:'NÖ'},
{id:'m13', base:'wr_neustadt_c3', title:'Rax', scen:'berg-fels', diff:'medium', region:'NÖ'},
{id:'m14', base:'oberwart_c16', title:'Surfer Neusiedler See', scen:'wasser', diff:'easy', region:'Bgld'},
{id:'m15', base:'linz_c10', title:'A1 Sattledt', scen:'autobahn', diff:'medium', region:'OÖ'},
{id:'m16', base:'linz_c10', title:'Boot Traunsee', scen:'wasser', diff:'easy', region:'OÖ'},
{id:'m17', base:'salzburg_c6', title:'A10 Tauern', scen:'autobahn', diff:'medium', region:'Sbg'},
{id:'m18', base:'salzburg_c6', title:'Wolfgangsee', scen:'wasser', diff:'easy', region:'Sbg'},
{id:'m19', base:'salzburg_c6', title:'Großglockner Nord', scen:'berg-schnee', diff:'hard', region:'Sbg'},
{id:'m20', base:'niederoeblarn_c14',title:'Dachstein', scen:'berg-schnee', diff:'hard', region:'Stmk'},
{id:'m21', base:'graz_c12', title:'Hochschwab', scen:'berg-fels', diff:'medium', region:'Stmk'},
{id:'m22', base:'graz_c12', title:'A9 Bosruck', scen:'autobahn', diff:'medium', region:'Stmk'},
{id:'m23', base:'klagenfurt_c11', title:'Wörthersee', scen:'wasser', diff:'easy', region:'Ktn'},
{id:'m24', base:'klagenfurt_c11', title:'Glockner Süd', scen:'berg-schnee', diff:'hard', region:'Ktn'},
{id:'m25', base:'klagenfurt_c11', title:'A2 Klagenfurt', scen:'autobahn', diff:'easy', region:'Ktn'},
];
const AUDIO_PATH = '../../sims/heli/sounds/radio/';
let lib = {};
async function loadLibrary() {
const files = [
'../../sims/heli/scripts/tower-lines.json',
'../../sims/heli/scripts/tower-geo-lines.json',
'../../sims/heli/scripts/tower-start-lines.json',
'../../sims/heli/scripts/tower-sea-lines.json',
'../../sims/heli/scripts/tower-heliintro-lines.json',
'../../sims/heli/scripts/nav-flight-lines.json',
'../../sims/heli/scripts/pilot-situations-lines.json',
];
for (const f of files) {
try {
const r = await fetch(f);
if (!r.ok) continue;
const d = await r.json();
for (const line of (d.lines || [])) lib[line.id] = line.text;
} catch(e) {}
}
}
function row(phase, id, label) {
const file = AUDIO_PATH + id + '.mp3';
const text = lib[id] ? `<span>${lib[id]}</span>` : `<span class="missing">⚠ kein Text in JSON</span>`;
return `<tr>
<td class="phase">${phase}</td>
<td class="tag">${id}</td>
<td class="audio"><audio controls preload="none" src="${file}"></audio></td>
<td class="text">${text}</td>
</tr>`;
}
function audiosForStation(key, hasWasser, hasAutobahn, missions) {
const k = key;
let html = `<table>
<tr><th>Phase</th><th>Audio-ID</th><th>Anhören</th><th>Sollte sagen</th></tr>`;
// Universelle Heli-Audios
html += row('Start', `r_tower_start_${k}`, '');
html += row('Anflug', `r_pilot_call_${k}`, '');
html += row('Anflug Standard', `r_tower_approach_${k}`, '');
html += row('Extract Standard',`r_tower_extract_${k}`, '');
html += row('Strike', `r_tower_strike_${k}`, '');
html += row('Pilot übernimmt', `r_tower_handover_${k}`, '');
html += row('Flug-Intro', `r_nav_heli_intro_${k}`, '');
if (hasAutobahn) {
html += row('Anflug Autobahn', `r_tower_hw_approach_${k}`, '');
html += row('Extract Autobahn', `r_tower_hw_extract_${k}`, '');
}
if (hasWasser) {
html += row('Anflug Wasser', `r_tower_sea_approach_${k}`, '');
html += row('Extract Wasser', `r_tower_sea_extract_${k}`, '');
}
html += `</table>`;
// Mission-Block: pro Mission das Geo-Audio
if (missions.length) {
html += `<div style="margin-top:14px"><b style="color:var(--forest);font-size:13px">Missionen ab diesem Stützpunkt:</b></div>`;
missions.forEach(m => {
const cls = m.scen === 'wasser' ? 'scen-wasser' : m.scen === 'autobahn' ? 'scen-autobahn' : m.scen === 'tal-dorf' ? 'scen-tal' : 'scen-berg';
html += `<div class="mission-block">
<h4>${m.id} · ${m.title}<span class="scen-tag ${cls}">${m.scen}</span></h4>
<div class="desc">Region ${m.region} · Schwierigkeit ${m.diff}</div>
<table>
<tr><th>Phase</th><th>Audio-ID</th><th>Anhören</th><th>Sollte sagen</th></tr>
${row('Tower-Geo (Anflug)', `r_tower_geo_${m.id}`, '')}
</table>
</div>`;
});
}
return html;
}
async function render() {
await loadLibrary();
const nav = document.getElementById('nav');
nav.innerHTML = LAENDER.map(l => `<a href="#${l.base}">${l.name}</a>`).join('');
const c = document.getElementById('content');
c.innerHTML = LAENDER.map(land => {
const stationsHtml = land.stations.map(stKey => {
const st = STATIONS[stKey];
if (!st) return '';
const stMissions = MISSIONS.filter(m => m.base === stKey);
const hasWasser = stMissions.some(m => m.scen === 'wasser');
const hasAutobahn = stMissions.some(m => m.scen === 'autobahn');
return `<div class="station">
<h3>${st.heliName} · ${st.ort}</h3>
<div class="meta">heliKey: <code>${st.heliKey}</code> · zuständig: ${st.tower} · ${stMissions.length} Mission(en)</div>
${audiosForStation(st.heliKey, hasWasser, hasAutobahn, stMissions)}
</div>`;
}).join('');
return `<section class="land" id="${land.base}">
<h2>${land.name} <span class="badge">${land.stations.length} Stützpunkt(e)</span></h2>
${stationsHtml}
</section>`;
}).join('');
// Auto-Pause anderer Audios beim Abspielen
document.querySelectorAll('audio').forEach(a => {
a.addEventListener('play', () => {
document.querySelectorAll('audio').forEach(b => { if (b !== a) b.pause(); });
});
});
}
render();
</script>
</body>
</html>