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

298 lines
16 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Heli — Audio-Übersicht aller Missionen</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;padding:0;line-height:1.5}
header{background:var(--forest);color:var(--cream);padding:16px 24px;position:sticky;top:0;z-index:50;display:flex;align-items:center;gap:16px;flex-wrap:wrap}
header h1{margin:0;font-size:18px;font-weight:700}
header nav{display:flex;gap:4px;flex-wrap:wrap;margin-left:auto}
header nav a{color:var(--cream);text-decoration:none;background:rgba(255,255,255,.08);padding:3px 8px;border-radius:4px;font-size:11px;font-weight:600}
header nav a:hover{background:rgba(255,255,255,.2)}
.container{max-width:1240px;margin:0 auto;padding:24px}
.mission{background:#fff;border:1px solid var(--border);border-radius:12px;padding:18px 24px;margin-bottom:20px;box-shadow:0 2px 8px rgba(0,0,0,.04)}
.mission h2{margin:0 0 6px;color:var(--forest);font-size:18px}
.meta{color:var(--slate);font-size:13px;margin-bottom:8px}
.route{background:rgba(45,84,52,.05);border-left:3px solid var(--forest);padding:6px 10px;font-size:12px;color:var(--slate);margin-bottom:14px;border-radius:4px}
.route b{color:var(--forest)}
.audios{display:grid;gap:6px}
.phase-header{font-weight:700;color:var(--orange);font-size:12px;text-transform:uppercase;letter-spacing:.05em;margin:10px 0 4px;padding-bottom:4px;border-bottom:1px solid var(--border)}
.audio{display:grid;grid-template-columns:70px 180px 220px 1fr;gap:10px;align-items:center;padding:4px 8px;border-radius:4px;font-size:12px}
.audio:hover{background:rgba(45,84,52,.03)}
.audio .tag{font-family:"SF Mono","Consolas",monospace;font-weight:700;color:var(--orange);font-size:11px;background:rgba(201,122,63,.1);padding:2px 6px;border-radius:3px;text-align:center;letter-spacing:.02em;cursor:pointer;user-select:all}
.audio .tag:hover{background:rgba(201,122,63,.2)}
.audio .label{font-weight:600;color:var(--slate)}
.audio audio{width:100%;height:28px}
.audio .text{color:var(--ink);font-style:italic}
.audio .missing{color:var(--red);font-size:11px}
.audio.group-random{grid-template-columns:70px 180px 1fr}
.random-list{display:grid;gap:4px;margin-left:10px}
.random-list .audio{grid-template-columns:70px 220px 1fr}
@media (max-width:760px){
.audio{grid-template-columns:1fr;gap:4px}
.audio audio{width:100%}
}
</style>
</head>
<body>
<header>
<h1>🚁 Heli · Audio-Übersicht aller Missionen</h1>
<nav id="nav"></nav>
</header>
<div class="container" id="content">
<p style="color:var(--slate)">Lade Texte …</p>
</div>
<script>
// --- Alle 25 Missionen (aus game.html kopiert) ---
const BASE_CONFIG = {
nenzing: {heli:'oamtc', heliKey:'c8', heliName:'Nenzing OE-XEN'},
hohenems: {heli:'ara', heliKey:'ara', heliName:'ARA Flugrettung RK-1'},
innsbruck: {heli:'oamtc', heliKey:'c1', heliName:'Innsbruck OE-XVB'},
hall: {heli:'oamtc', heliKey:'c1', heliName:'Innsbruck OE-XVB'},
zams_c5: {heli:'oamtc', heliKey:'c5', heliName:'Zams OE-XEZ'},
kitzbuehel: {heli:'oamtc', heliKey:'c4', heliName:'Kitzbühel OE-XEY'},
lienz: {heli:'oamtc', heliKey:'c7', heliName:'Lienz OE-XER'},
krems_c2: {heli:'oamtc', heliKey:'c2', heliName:'Krems OE-XVI'},
wr_neustadt_c3: {heli:'oamtc', heliKey:'c3', heliName:'Wr. Neustadt OE-XEL'},
salzburg_c6: {heli:'oamtc', heliKey:'c6', heliName:'Salzburg OE-XVL'},
wien_c9: {heli:'oamtc', heliKey:'c9', heliName:'Wien-Baumgasse OE-XVO'},
linz_c10: {heli:'oamtc', heliKey:'c10', heliName:'Linz OE-XVP'},
klagenfurt_c11: {heli:'oamtc', heliKey:'c11', heliName:'Klagenfurt OE-XEK'},
graz_c12: {heli:'oamtc', heliKey:'c12', heliName:'Graz OE-XEM'},
niederoeblarn_c14:{heli:'oamtc', heliKey:'c14', heliName:'Niederöblarn OE-XEW'},
oberwart_c16: {heli:'oamtc', heliKey:'c16', heliName:'Pinkafeld OE-XEU'},
};
const ALL_MISSIONS = [
{id:'m1', title:'Skiunfall Damüls', difficulty:'medium', base:'nenzing', region:'Vorarlberg', scen:'berg-schnee',
waypoints:['nenzing','thueringen','zwischenwasser','laterns','damuels','bergrettung_muels']},
{id:'m2', title:'Verkehrsunfall A14', difficulty:'easy', base:'hohenems', region:'Vorarlberg', scen:'autobahn',
waypoints:['hohenems','goetzis','rankweil','verkehr_a14']},
{id:'m3', title:'Bergrettung Brand', difficulty:'medium', base:'nenzing', region:'Vorarlberg', scen:'berg-fels',
waypoints:['nenzing','buers','brand']},
{id:'m4', title:'Skiunfall Sölden', difficulty:'medium', base:'innsbruck', region:'Tirol', scen:'berg-schnee',
waypoints:['innsbruck','telfs','oetz','soelden','skiunfall_soelden']},
{id:'m5', title:'Seerettung Bregenz', difficulty:'easy', base:'hohenems', region:'Vorarlberg', scen:'wasser',
waypoints:['hohenems','dornbirn','bregenz']},
{id:'m6', title:'Canyoning Mellau', difficulty:'medium', base:'hohenems', region:'Vorarlberg', scen:'schlucht',
waypoints:['hohenems','dornbirn','egg','bezau','mellau']},
{id:'m7', title:'Wanderunfall Kufstein', difficulty:'easy', base:'innsbruck', region:'Tirol', scen:'berg-fels',
waypoints:['innsbruck','hall','schwaz','jenbach','woergl','kufstein']},
{id:'m8', title:'Notfall Gaschurn', difficulty:'hard', base:'nenzing', region:'Vorarlberg', scen:'tal-dorf',
waypoints:['nenzing','bludenz','schruns','stgallenk','gaschurn']},
{id:'m9', title:'Lawineneinsatz Mayrhofen', difficulty:'hard', base:'innsbruck', region:'Tirol', scen:'berg-schnee',
waypoints:['innsbruck','hall','schwaz','jenbach','zell_ziller','mayrhofen']},
{id:'m10', title:'Verkehrsunfall A2 Baden', difficulty:'easy', base:'wien_c9', region:'Wien', scen:'autobahn',
waypoints:['wien_c9','wien','moedling','baden','verkehr_a2_baden']},
{id:'m11', title:'Sog in der Neuen Donau', difficulty:'medium', base:'wien_c9', region:'Wien', scen:'wasser',
waypoints:['wien_c9','wien','wien_donau','see_donau_wien']},
{id:'m12', title:'Kletterunfall Schneeberg', difficulty:'hard', base:'wr_neustadt_c3', region:'Niederösterreich', scen:'berg-fels',
waypoints:['wr_neustadt_c3','wr_neustadt','puchberg','schneeberg','berg_schneeberg']},
{id:'m13', title:'Wanderunfall Rax', difficulty:'medium', base:'wr_neustadt_c3', region:'Niederösterreich', scen:'berg-fels',
waypoints:['wr_neustadt_c3','wr_neustadt','semmering','rax','berg_rax']},
{id:'m14', title:'Surfer Neusiedler See', difficulty:'easy', base:'oberwart_c16', region:'Burgenland', scen:'wasser',
waypoints:['oberwart_c16','mattersburg','eisenstadt','podersdorf','see_neusiedl']},
{id:'m15', title:'Verkehrsunfall A1 Sattledt', difficulty:'medium', base:'linz_c10', region:'Oberösterreich', scen:'autobahn',
waypoints:['linz_c10','linz','wels','sattledt','verkehr_a1_sattledt']},
{id:'m16', title:'Boot in Not Traunsee', difficulty:'easy', base:'linz_c10', region:'Oberösterreich', scen:'wasser',
waypoints:['linz_c10','wels','gmunden','traunsee','see_traun']},
{id:'m17', title:'LKW-Unfall A10 Tauern', difficulty:'medium', base:'salzburg_c6', region:'Salzburg', scen:'autobahn',
waypoints:['salzburg_c6','hallein','golling','flachau','verkehr_a10_tauern']},
{id:'m18', title:'Seerettung Wolfgangsee', difficulty:'easy', base:'salzburg_c6', region:'Salzburg', scen:'wasser',
waypoints:['salzburg_c6','hallein','st_wolfgang','see_wolfgang']},
{id:'m19', title:'Absturz Großglockner', difficulty:'hard', base:'salzburg_c6', region:'Salzburg', scen:'berg-schnee',
waypoints:['salzburg_c6','zell_see','kaprun','heiligenblut','grossglockner','berg_glockner']},
{id:'m20', title:'Dachstein Südwand', difficulty:'hard', base:'niederoeblarn_c14', region:'Steiermark', scen:'berg-schnee',
waypoints:['niederoeblarn_c14','bad_aussee','schladming','dachstein','berg_dachstein']},
{id:'m21', title:'Alpinunfall Hochschwab', difficulty:'medium', base:'graz_c12', region:'Steiermark', scen:'berg-fels',
waypoints:['graz_c12','bruck_mur','mariazell','hochschwab','berg_hochschwab']},
{id:'m22', title:'Unfall A9 Bosruck', difficulty:'medium', base:'graz_c12', region:'Steiermark', scen:'autobahn',
waypoints:['graz_c12','bruck_mur','leoben','verkehr_a9_pyhrn']},
{id:'m23', title:'Badegast Wörthersee', difficulty:'easy', base:'klagenfurt_c11', region:'Kärnten', scen:'wasser',
waypoints:['klagenfurt_c11','klagenfurt','poertschach','see_woerth']},
{id:'m24', title:'Glockner Südseite', difficulty:'hard', base:'klagenfurt_c11', region:'Kärnten', scen:'berg-schnee',
waypoints:['klagenfurt_c11','villach','spittal','heiligenblut','berg_glockner']},
{id:'m25', title:'Unfall A2 Klagenfurt', difficulty:'easy', base:'klagenfurt_c11', region:'Kärnten', scen:'autobahn',
waypoints:['klagenfurt_c11','klagenfurt','verkehr_a2_klagenfurt']},
];
const AUDIO_PATH = '../../sims/heli/sounds/radio/';
// --- Loader: Texte der JSON-Bücher ---
async function loadTextLibrary() {
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',
];
const lib = {};
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) { console.warn('skip', f, e); }
}
return lib;
}
// Pro Mission ein Counter, damit jede Zeile eine Nummer bekommt (M1-01 etc.)
let _rowCounter = 0;
let _currentMissionId = '';
function nextTag() {
_rowCounter++;
return `${_currentMissionId.toUpperCase()}-${String(_rowCounter).padStart(2,'0')}`;
}
// Audio-Zeile rendern
function audioRow(id, label, text) {
const tag = nextTag();
const file = AUDIO_PATH + id + '.mp3';
return `<div class="audio" data-tag="${tag}">
<span class="tag" title="Nummer zum Referenzieren: ${tag}">${tag}</span>
<span class="label">${label}</span>
<audio controls preload="none" src="${file}"></audio>
<span class="text">${text || '<span class="missing">(kein Text hinterlegt)</span>'}</span>
</div>`;
}
function audioGroupRandom(label, list, lib) {
const outerTag = nextTag();
return `<div class="audio group-random" data-tag="${outerTag}">
<span class="tag" title="Gruppe (zufaellig): ${outerTag}">${outerTag}</span>
<span class="label">${label}</span>
<div class="random-list">
${list.map(id => {
const t = nextTag();
return `<div class="audio" data-tag="${t}">
<span class="tag" title="${t}">${t}</span>
<audio controls preload="none" src="${AUDIO_PATH + id + '.mp3'}"></audio>
<span class="text">${lib[id] || '<span class="missing">(kein Text)</span>'}</span>
</div>`;
}).join('')}
</div>
</div>`;
}
// Rendert alle Missionen
async function render() {
const lib = await loadTextLibrary();
const nav = document.getElementById('nav');
nav.innerHTML = ALL_MISSIONS.map(m => `<a href="#${m.id}">${m.id}</a>`).join('');
const content = document.getElementById('content');
content.innerHTML = ALL_MISSIONS.map(m => {
// Reset Counter pro Mission — so startet jede bei 01
_rowCounter = 0;
_currentMissionId = m.id;
const cfg = BASE_CONFIG[m.base] || {};
const key = cfg.heliKey || 'c1';
const isHw = m.scen === 'autobahn';
const isSea = m.scen === 'wasser';
// Landing-Audio-Variante
let approachKey = 'tower_approach_' + key;
let extractKey = 'tower_extract_' + key;
if (isHw) { approachKey = 'tower_hw_approach_' + key; extractKey = 'tower_hw_extract_' + key; }
if (isSea) { approachKey = 'tower_sea_approach_' + key; extractKey = 'tower_sea_extract_' + key; }
// Himmelsrichtungen der Segmente — nur Start→Ziel grob, Demo-Zweck
const dirAudios = ['dir_nord','dir_nordost','dir_ost','dir_suedost','dir_sued','dir_suedwest','dir_west','dir_nordwest'];
return `<section class="mission" id="${m.id}">
<h2>${m.id} · ${m.title}</h2>
<div class="meta">
<b>Basis:</b> ${m.base} · ${cfg.heliName || '?'} ·
<b>Region:</b> ${m.region} · <b>Schwierigkeit:</b> ${m.difficulty} ·
<b>Landeszene:</b> ${m.scen}
</div>
<div class="route"><b>Route:</b> ${m.waypoints.join(' → ')}</div>
<div class="audios">
<div class="phase-header">Phase 2 · Plan (Feedback)</div>
${audioRow('r_nav_plan_start', 'Planung beginnt', lib['r_nav_plan_start'])}
${audioRow('r_nav_plan_correct', 'Wegpunkt richtig', lib['r_nav_plan_correct'])}
${audioRow('r_nav_plan_wrong_near', 'Fast — nahe', lib['r_nav_plan_wrong_near'])}
${audioRow('r_nav_plan_wrong_far', 'Weit daneben', lib['r_nav_plan_wrong_far'])}
${audioRow('r_nav_plan_done', 'Route vollständig', lib['r_nav_plan_done'])}
<div class="phase-header">Phase 3 · Start am Stützpunkt</div>
${audioRow('r_tower_start_' + key, 'Tower-Freigabe (${key})'.replace('${key}', key), lib['r_tower_start_' + key])}
<div class="phase-header">Phase 3→4 · Abflug</div>
${audioRow('r_nav_route_check', 'Route wird geprüft', lib['r_nav_route_check'])}
${audioRow('r_nav_departure_ok', 'Abflug freigegeben', lib['r_nav_departure_ok'])}
${audioRow('r_nav_departure_takeover','Chefpilot übernimmt',lib['r_nav_departure_takeover'])}
<div class="phase-header">Phase 4 · Kartenflug</div>
${audioRow('r_nav_heli_intro_' + key, 'Heli-Flug-Intro (einmalig)', lib['r_nav_heli_intro_' + key])}
${audioGroupRandom('Richtungs-Audios', dirAudios.map(d => d), {})}
<div class="audio"><span class="label">Himmelsrichtungs-Audios</span>
<span style="font-style:italic;color:#666">liegen in sounds/nav/ (dir_nord.mp3 etc.)</span>
<span></span>
</div>
${audioRow('r_nav_fuel_half', 'Treibst. halb', lib['r_nav_fuel_half'])}
${audioRow('r_nav_fuel_quarter', 'Treibst. viertel', lib['r_nav_fuel_quarter'])}
${audioRow('r_nav_altitude_mountain','Höhe Bergkette', lib['r_nav_altitude_mountain'])}
${audioRow('r_nav_altitude_valley','Höhe Tal', lib['r_nav_altitude_valley'])}
<div class="phase-header">Phase 5 · Zielanflug (ICAO-Protokoll)</div>
${audioRow('r_pilot_call_' + key, 'Pilot meldet sich', lib['r_pilot_call_' + key])}
${audioRow('r_tower_geo_' + m.id, 'Tower + Geografie', lib['r_tower_geo_' + m.id])}
${audioRow('r_' + approachKey, 'Tower-Anflug (' + (isHw?'Autobahn':isSea?'Wasser':'Standard') + ')', lib['r_' + approachKey])}
<div class="phase-header">Phase 5 · Landung</div>
${audioRow('r_land_winch_down', 'Seil läuft', lib['r_land_winch_down'])}
${audioRow('r_land_rescuer_down', 'Retter am Boden', lib['r_land_rescuer_down'])}
${audioRow('r_land_rescuer_ready', 'Patient bereit', lib['r_land_rescuer_ready'])}
${audioRow('r_land_hook_connected', 'Haken eingeklinkt', lib['r_land_hook_connected'])}
${audioRow('r_land_lift_success', 'Patient an Bord', lib['r_land_lift_success'])}
${audioRow('r_' + extractKey, 'Tower-Extract (' + (isHw?'Autobahn':isSea?'Wasser':'Standard') + ')', lib['r_' + extractKey])}
${audioRow('r_land_clinic_ahead', 'Klinik voraus', lib['r_land_clinic_ahead'])}
${audioRow('r_land_disembark', 'Saubere Landung', lib['r_land_disembark'])}
<div class="phase-header">Strike / Fehler</div>
${audioRow('r_tower_strike_' + key, 'Tower-Strike (' + key + ')', lib['r_tower_strike_' + key])}
${audioGroupRandom('Pilot-Reaktion (zufällig)', ['r_pilot_strike_1','r_pilot_strike_2','r_pilot_strike_3','r_pilot_strike_4','r_pilot_strike_5'], lib)}
${audioRow('r_pilot_three_strikes', 'Drei Berührungen', lib['r_pilot_three_strikes'])}
${audioRow('r_tower_handover_' + key, 'Chefpilot-Übernahme', lib['r_tower_handover_' + key])}
<div class="phase-header">Situations-Warnungen (wenn Situation eintritt)</div>
${audioRow('r_pilot_wind_turn', 'Wind dreht', lib['r_pilot_wind_turn'])}
${audioRow('r_pilot_bird_ahead', 'Vogelschwarm voraus', lib['r_pilot_bird_ahead'])}
${audioRow('r_pilot_plane_ahead', 'Verkehrsflugzeug', lib['r_pilot_plane_ahead'])}
${audioRow('r_pilot_sportplane_level', 'Sportflieger', lib['r_pilot_sportplane_level'])}
${audioRow('r_pilot_storm_detour', 'Gewitter', lib['r_pilot_storm_detour'])}
${audioRow('r_pilot_fog_thickens', 'Nebel dichter', lib['r_pilot_fog_thickens'])}
${audioRow('r_pilot_time_up', 'Zeit abgelaufen', lib['r_pilot_time_up'])}
${audioRow('r_pilot_too_high', 'Zu hoch/viel Sprit', lib['r_pilot_too_high'])}
</div>
</section>`;
}).join('');
// Stoppe andere Audio-Elemente, wenn eines abgespielt wird
document.querySelectorAll('audio').forEach(a => {
a.addEventListener('play', () => {
document.querySelectorAll('audio').forEach(b => { if (b !== a) b.pause(); });
});
});
}
render();
</script>
</body>
</html>