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

812 lines
34 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<title>GeoGraSim — [Modulname]</title>
<link rel="icon" type="image/svg+xml" href="../favicon.svg">
<link rel="stylesheet" href="../assets/fonts/inter.css">
<link rel="stylesheet" href="../assets/css/design-system.css">
<style>
/* ==========================================================
MODUL-SPEZIFISCHES CSS
Hier kommt NUR das, was dieses Modul zusätzlich braucht.
Alles Gemeinsame liegt in design-system.css.
========================================================== */
</style>
</head>
<body>
<!-- ============================================================
LOADING SCREEN (Logo dreht sich, verschwindet wenn geladen)
============================================================ -->
<div class="ggs-loading" id="loading-screen">
<img src="../assets/img/bildLogo.png" alt="" class="ggs-loading-logo">
<div class="ggs-loading-text">Wird geladen ...</div>
</div>
<!-- ============================================================
HEADER — überall identisch
============================================================ -->
<header class="ggs-header">
<a href="../" class="ggs-header-logo">
<img src="../assets/img/bildLogo.png" alt="" class="ggs-header-logo-icon">
<img src="../assets/img/textlogo_geograsim.svg" alt="GeoGraSim" class="ggs-header-logo-text">
</a>
<div class="ggs-header-separator"></div>
<span class="ggs-header-module">[Modulname]</span>
<span class="ggs-header-badge">Level 1</span>
<!-- Phasen-Anzeige (optional, z.B. Heli hat 6 Phasen) -->
<div class="ggs-phases">
<div class="ggs-phase-dot done"></div>
<div class="ggs-phase-dot current"></div>
<div class="ggs-phase-dot"></div>
<div class="ggs-phase-dot"></div>
</div>
<div class="ggs-header-spacer"></div>
<div class="ggs-header-actions">
<!-- Speed Control -->
<div class="ggs-speed">
<button class="ggs-speed-btn" data-speed="0" title="Pause (P)"></button>
<button class="ggs-speed-btn active" data-speed="1" title="Normal (1)">1x</button>
<button class="ggs-speed-btn" data-speed="2" title="Schnell (2)">2x</button>
<button class="ggs-speed-btn" data-speed="4" title="Turbo (3)">4x</button>
</div>
<!-- Music Player — Modul befüllt die Options per JS (ggsMusicSetup([{key,label,file},...])) -->
<div class="ggs-music" id="ggs-music" hidden>
<select class="ggs-music-sel" id="ggs-music-sel" title="Hintergrundmusik wählen"></select>
<button class="ggs-music-btn" id="ggs-music-toggle" title="Play / Pause"></button>
<input class="ggs-music-vol" id="ggs-music-vol" type="range" min="0" max="100" value="35" title="Lautstärke">
</div>
<!-- Lehrplan-Bezug: öffnet Modul-Detailseite beim Anker "lehrplan-bezug" in neuem Tab -->
<a class="ggs-btn-ghost" id="ggs-lehrplan-link" href="#" target="_blank" rel="noopener" title="Lehrplan-Bezug">📚</a>
<button class="ggs-btn-ghost" title="Speichern">💾</button>
<button class="ggs-btn-ghost" title="Laden">📂</button>
<a href="../" class="ggs-btn-ghost" title="Zurück">🏠</a>
</div>
</header>
<!-- ============================================================
SIMULATION LAYOUT — 3 Spalten + Graphen + Events
============================================================ -->
<main class="ggs-sim-layout">
<!-- ────────── LINKS: Status-Panel ────────── -->
<aside class="ggs-zone-status">
<!-- Panel: Ressourcen / Parameter -->
<div class="ggs-panel">
<div class="ggs-panel-title">Status</div>
<!-- Beispiel: Parameter mit Balken -->
<div class="ggs-param">
<span class="ggs-param-icon">💰</span>
<span class="ggs-param-label">Budget</span>
<span class="ggs-param-value">185 Mio</span>
<span class="ggs-trend up">+12</span>
</div>
<div class="ggs-param-bar"><div class="ggs-param-fill good" style="width:72%"></div></div>
<div class="ggs-param">
<span class="ggs-param-icon">🌡️</span>
<span class="ggs-param-label" data-glossar="temperatur">Temperatur</span>
<span class="ggs-param-value">+1.2 °C</span>
<span class="ggs-trend up">+0.1</span>
</div>
<div class="ggs-param-bar"><div class="ggs-param-fill warning" style="width:55%"></div></div>
<div class="ggs-param">
<span class="ggs-param-icon">🌊</span>
<span class="ggs-param-label" data-glossar="meeresspiegel">Meeresspiegel</span>
<span class="ggs-param-value">+18 cm</span>
</div>
<div class="ggs-param-bar"><div class="ggs-param-fill info" style="width:30%"></div></div>
<div class="ggs-param">
<span class="ggs-param-icon"></span>
<span class="ggs-param-label" data-glossar="co2">CO₂-Ausstoß</span>
<span class="ggs-param-value">840 Mt</span>
<span class="ggs-trend down">-45</span>
</div>
<div class="ggs-param-bar"><div class="ggs-param-fill danger" style="width:84%"></div></div>
</div>
<!-- Panel: Ziele -->
<div class="ggs-panel">
<div class="ggs-panel-title">Ziele</div>
<div class="ggs-param">
<span class="ggs-param-icon"></span>
<span class="ggs-param-label">Temp. unter 1.5 °C</span>
<span class="ggs-param-value">62%</span>
</div>
<div class="ggs-param-bar"><div class="ggs-param-fill good" style="width:62%"></div></div>
<div class="ggs-param">
<span class="ggs-param-icon"></span>
<span class="ggs-param-label">100% Erneuerbar</span>
<span class="ggs-param-value">34%</span>
</div>
<div class="ggs-param-bar"><div class="ggs-param-fill warning" style="width:34%"></div></div>
</div>
<!-- Panel: Achievements (gesammelt in diesem Level) -->
<div class="ggs-panel">
<div class="ggs-panel-title">Erfolge</div>
<div style="display:flex; gap:6px; flex-wrap:wrap;">
<div class="ggs-badge-earned" title="Erster Windpark">🌬️</div>
<div class="ggs-badge-earned" title="Deich gebaut">🛡️</div>
<!-- Leere Slots -->
<div class="ggs-badge-earned" style="background:var(--ggs-bg-dark);border-color:var(--ggs-border);opacity:.4">?</div>
<div class="ggs-badge-earned" style="background:var(--ggs-bg-dark);border-color:var(--ggs-border);opacity:.4">?</div>
</div>
</div>
</aside>
<!-- ────────── MITTE: Simulation ────────── -->
<section class="ggs-zone-sim">
<!-- Topbar innerhalb der Sim (Runde, Wetter, etc.) -->
<div class="ggs-topbar">
<div class="ggs-topbar-stat">
<span class="icon">📅</span>
<span class="val">Jahr 2038</span>
</div>
<div class="ggs-topbar-stat">
<span class="icon">☁️</span>
<span class="val">Bewölkt</span>
</div>
<div class="ggs-topbar-stat">
<span class="icon">💰</span>
<span class="val">+12 Mio/Jahr</span>
</div>
<div class="ggs-header-spacer"></div>
<div class="ggs-topbar-stat">
<span class="icon">🎯</span>
<span class="val">Runde 14 / 75</span>
</div>
</div>
<!-- Hier kommt der Canvas / die 3D-Szene / die Leaflet-Karte -->
<div id="sim-viewport" style="width:100%; height:100%; background:#d0dde0; display:flex; align-items:center; justify-content:center; color:var(--ggs-text-muted); font-size:18px;">
[ Simulation Canvas / 3D / Karte ]
</div>
<!-- In-Canvas Event Notifications (erscheinen gross, gleiten dann in den Stapel) -->
<div class="ggs-event-viewport" id="event-viewport"></div>
<!-- Event-Stapel (links unten, aufklappbar) -->
<div class="ggs-event-stack" id="event-stack">
<div class="ggs-event-stack-list" id="event-stack-list"></div>
<button class="ggs-event-stack-toggle" onclick="toggleEventStack()">
📋 Ereignisse <span class="ggs-event-stack-count" id="event-count">0</span>
</button>
</div>
</section>
<!-- ────────── RECHTS: Aktionen ────────── -->
<aside class="ggs-zone-actions">
<div class="ggs-panel">
<div class="ggs-panel-title">Massnahmen</div>
<!-- Beispiel: Action-Card -->
<div class="ggs-card">
<div class="ggs-card-header">
<span class="ggs-card-icon">🌬️</span>
<span class="ggs-card-title" data-glossar="windpark">Windpark</span>
</div>
<div class="ggs-card-desc">Erzeugt sauberen Strom, reduziert CO₂-Ausstoss.</div>
<div class="ggs-card-badges">
<span class="ggs-badge cost" data-glossar="budget">💰 25 Mio</span>
<span class="ggs-badge effect" data-glossar="megatonne">🍃 -80 Mt CO₂</span>
</div>
</div>
<div class="ggs-card">
<div class="ggs-card-header">
<span class="ggs-card-icon">☀️</span>
<span class="ggs-card-title" data-glossar="solaranlage">Solaranlage</span>
</div>
<div class="ggs-card-desc">Sonnenenergie für Gebäude und Industrie.</div>
<div class="ggs-card-badges">
<span class="ggs-badge cost">💰 18 Mio</span>
<span class="ggs-badge effect">🍃 -50 Mt CO₂</span>
</div>
</div>
<div class="ggs-card active">
<div class="ggs-card-header">
<span class="ggs-card-icon">🛡️</span>
<span class="ggs-card-title" data-glossar="kuestenschutz">Küstenschutz</span>
</div>
<div class="ggs-card-desc">Deiche schützen Siedlungen vor Überflutung.</div>
<div class="ggs-card-badges">
<span class="ggs-badge cost">💰 30 Mio</span>
<span class="ggs-badge protect">🛡️ +40% Schutz</span>
</div>
</div>
<div class="ggs-card disabled">
<div class="ggs-card-header">
<span class="ggs-card-icon">🏭</span>
<span class="ggs-card-title" data-glossar="co2_filter">CO₂-Filter</span>
</div>
<div class="ggs-card-desc">Teuer, aber entfernt CO₂ aus der Atmosphäre.</div>
<div class="ggs-card-badges">
<span class="ggs-badge cost">💰 85 Mio</span>
<span class="ggs-badge effect">🍃 -200 Mt CO₂</span>
</div>
</div>
</div>
</aside>
<!-- ────────── UNTEN: Graphen ────────── -->
<section class="ggs-zone-graphs">
<div class="ggs-graph-card">
<div class="ggs-graph-label">CO₂-Ausstoss</div>
<div class="ggs-graph-value" style="color:var(--ggs-coral);">840 Mt</div>
<svg class="ggs-graph-svg" viewBox="0 0 200 70" preserveAspectRatio="none">
<rect class="ggs-graph-zone-safe" x="0" y="0" width="200" height="25"/>
<rect class="ggs-graph-zone-warning" x="0" y="25" width="200" height="20"/>
<rect class="ggs-graph-zone-danger" x="0" y="45" width="200" height="25"/>
<line class="ggs-graph-ref" x1="0" y1="35" x2="200" y2="35" stroke="var(--ggs-moss)"/>
<polyline class="ggs-graph-line" stroke="var(--ggs-coral)" points="0,20 15,22 30,25 45,30 60,33 75,38 90,42 105,45 120,43 135,40 150,38"/>
<circle cx="150" cy="38" r="2" fill="var(--ggs-coral)"><animate attributeName="r" values="2;3.5;2" dur="2s" repeatCount="indefinite"/><animate attributeName="opacity" values="1;.5;1" dur="2s" repeatCount="indefinite"/></circle>
</svg>
</div>
<div class="ggs-graph-card">
<div class="ggs-graph-label">Temperatur</div>
<div class="ggs-graph-value" style="color:var(--ggs-orange);">+1.2 °C</div>
<svg class="ggs-graph-svg" viewBox="0 0 200 70" preserveAspectRatio="none">
<rect class="ggs-graph-zone-safe" x="0" y="0" width="200" height="30"/>
<rect class="ggs-graph-zone-warning" x="0" y="30" width="200" height="20"/>
<rect class="ggs-graph-zone-danger" x="0" y="50" width="200" height="20"/>
<line class="ggs-graph-ref" x1="0" y1="35" x2="200" y2="35" stroke="var(--ggs-coral)"/>
<polyline class="ggs-graph-line" stroke="var(--ggs-orange)" points="0,15 20,17 40,20 60,22 80,26 100,30 120,33 140,35 160,37"/>
<circle cx="160" cy="37" r="2" fill="var(--ggs-orange)"><animate attributeName="r" values="2;3.5;2" dur="2s" repeatCount="indefinite"/><animate attributeName="opacity" values="1;.5;1" dur="2s" repeatCount="indefinite"/></circle>
</svg>
</div>
<div class="ggs-graph-card">
<div class="ggs-graph-label">Budget</div>
<div class="ggs-graph-value" style="color:var(--ggs-moss);">185 Mio</div>
<svg class="ggs-graph-svg" viewBox="0 0 200 70" preserveAspectRatio="none">
<rect class="ggs-graph-zone-safe" x="0" y="0" width="200" height="45"/>
<rect class="ggs-graph-zone-danger" x="0" y="45" width="200" height="25"/>
<polyline class="ggs-graph-line" stroke="var(--ggs-moss)" points="0,10 20,15 40,18 60,25 80,20 100,28 120,32 140,30 160,25"/>
<circle cx="160" cy="25" r="2" fill="var(--ggs-moss)"><animate attributeName="r" values="2;3.5;2" dur="2s" repeatCount="indefinite"/><animate attributeName="opacity" values="1;.5;1" dur="2s" repeatCount="indefinite"/></circle>
</svg>
</div>
<div class="ggs-graph-card">
<div class="ggs-graph-label">Meeresspiegel</div>
<div class="ggs-graph-value" style="color:var(--ggs-fjord);">+18 cm</div>
<svg class="ggs-graph-svg" viewBox="0 0 200 70" preserveAspectRatio="none">
<rect class="ggs-graph-zone-safe" x="0" y="0" width="200" height="35"/>
<rect class="ggs-graph-zone-warning" x="0" y="35" width="200" height="20"/>
<rect class="ggs-graph-zone-danger" x="0" y="55" width="200" height="15"/>
<polyline class="ggs-graph-line" stroke="var(--ggs-fjord)" points="0,10 20,12 40,14 60,17 80,20 100,22 120,25 140,27 160,30"/>
<circle cx="160" cy="30" r="2" fill="var(--ggs-fjord)"><animate attributeName="r" values="2;3.5;2" dur="2s" repeatCount="indefinite"/><animate attributeName="opacity" values="1;.5;1" dur="2s" repeatCount="indefinite"/></circle>
</svg>
</div>
</section>
</main>
<!-- ============================================================
ACHIEVEMENT TOAST (fliegt oben rechts ein)
============================================================ -->
<div class="ggs-toast-container">
<!-- Wird dynamisch per JS eingefügt, Beispiel: -->
<div class="ggs-toast">
<span class="ggs-toast-icon"></span>
<div>
<div class="ggs-toast-text">Erster Windpark!</div>
<div class="ggs-toast-sub">Erneuerbare Energie entdeckt</div>
</div>
</div>
</div>
<!-- ============================================================
TUTORIAL OVERLAY (beim ersten Start)
============================================================ -->
<div class="ggs-overlay visible" id="tutorial-overlay" style="display:none;">
<div class="ggs-overlay-card ggs-tutorial">
<div class="ggs-tutorial-dots">
<div class="ggs-tutorial-dot active"></div>
<div class="ggs-tutorial-dot"></div>
<div class="ggs-tutorial-dot"></div>
<div class="ggs-tutorial-dot"></div>
</div>
<div class="ggs-tutorial-title">Willkommen!</div>
<div class="ggs-tutorial-text">
In diesem Modul lernst du, wie deine Entscheidungen die Umwelt beeinflussen.
Rechts findest du Massnahmen, links siehst du die Auswirkungen.
</div>
<button class="ggs-btn ggs-btn-primary" onclick="nextTutorialStep()">Weiter →</button>
</div>
</div>
<!-- ============================================================
BETWEEN-LEVEL SCREEN (nach Abschluss eines Levels)
============================================================ -->
<div class="ggs-overlay" id="level-overlay" style="display:none;">
<div class="ggs-overlay-card ggs-level-screen">
<div style="font-size:48px;">🎉</div>
<h2 style="font-size:20px; font-weight:800; color:var(--ggs-fjord-dark); margin:12px 0 4px;">Level 1 geschafft!</h2>
<div class="ggs-stars" style="margin:12px 0;">
<span class="ggs-star lit"></span>
<span class="ggs-star lit"></span>
<span class="ggs-star lit"></span>
<span class="ggs-star"></span>
<span class="ggs-star"></span>
</div>
<!-- Verdiente Badges -->
<div class="ggs-badges-earned">
<div class="ggs-badge-earned" title="Erster Windpark">🌬️</div>
<div class="ggs-badge-earned" title="Küstenschutz">🛡️</div>
</div>
<!-- Reflexionsfrage (optional, 1 Frage pro Level-Übergang) -->
<div class="ggs-reflection">
<div class="ggs-reflection-q">Was war deine wirksamste Entscheidung?</div>
<div class="ggs-reflection-option" onclick="selectReflection(this)">
<div class="ggs-reflection-radio"></div>
<span>Windpark gebaut</span>
</div>
<div class="ggs-reflection-option" onclick="selectReflection(this)">
<div class="ggs-reflection-radio"></div>
<span>Küstenschutz ausgebaut</span>
</div>
<div class="ggs-reflection-option" onclick="selectReflection(this)">
<div class="ggs-reflection-radio"></div>
<span>Verkehr reduziert</span>
</div>
<div class="ggs-reflection-option" onclick="selectReflection(this)">
<div class="ggs-reflection-radio"></div>
<span>Ich bin mir nicht sicher</span>
</div>
</div>
<button class="ggs-btn ggs-btn-primary ggs-mt-md" onclick="nextLevel()">Weiter zu Level 2 →</button>
</div>
</div>
<!-- ============================================================
ENDSCREEN (Spielende)
============================================================ -->
<div class="ggs-overlay" id="end-overlay" style="display:none;">
<div class="ggs-overlay-card ggs-results">
<div class="ggs-results-icon">🏆</div>
<div class="ggs-results-title">Simulation abgeschlossen!</div>
<div class="ggs-results-subtitle">Du hast das Klima stabilisiert und die Küste geschützt.</div>
<div class="ggs-stars" style="margin-bottom:24px;">
<span class="ggs-star lit"></span>
<span class="ggs-star lit"></span>
<span class="ggs-star lit"></span>
<span class="ggs-star lit"></span>
<span class="ggs-star"></span>
</div>
<div class="ggs-results-stats">
<div><div class="ggs-results-stat-value">62</div><div class="ggs-results-stat-label">Jahre überlebt</div></div>
<div><div class="ggs-results-stat-value">+1.3 °C</div><div class="ggs-results-stat-label">Endtemperatur</div></div>
<div><div class="ggs-results-stat-value">42 Mio</div><div class="ggs-results-stat-label">Restbudget</div></div>
<div><div class="ggs-results-stat-value">12%</div><div class="ggs-results-stat-label">Überflutung</div></div>
</div>
<div style="display:flex; gap:12px; justify-content:center;">
<button class="ggs-btn ggs-btn-primary" onclick="restartGame()">Nochmal spielen</button>
<button class="ggs-btn ggs-btn-secondary" onclick="location.href='../'">Zurück</button>
</div>
</div>
</div>
<!-- ============================================================
JAVASCRIPT — Minimaler Template-Code
Modul-spezifische Logik kommt separat.
============================================================ -->
<script>
/* --- Speed Control --- */
document.querySelectorAll('.ggs-speed-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
document.querySelectorAll('.ggs-speed-btn').forEach(function(b) { b.classList.remove('active'); });
btn.classList.add('active');
var speed = parseInt(btn.dataset.speed);
if (typeof onSpeedChange === 'function') onSpeedChange(speed);
});
});
/* --- Keyboard Shortcuts für Speed --- */
document.addEventListener('keydown', function(e) {
if (e.key === 'p' || e.key === 'P') document.querySelector('[data-speed="0"]').click();
if (e.key === '1') document.querySelector('[data-speed="1"]').click();
if (e.key === '2') document.querySelector('[data-speed="2"]').click();
if (e.key === '3') document.querySelector('[data-speed="4"]').click();
});
/* --- Reflection Question --- */
function selectReflection(el) {
el.parentElement.querySelectorAll('.ggs-reflection-option').forEach(function(o) { o.classList.remove('selected'); });
el.classList.add('selected');
}
/* --- Overlay Show/Hide --- */
function showOverlay(id) { document.getElementById(id).style.display = ''; document.getElementById(id).classList.add('visible'); }
function hideOverlay(id) { document.getElementById(id).classList.remove('visible'); setTimeout(function() { document.getElementById(id).style.display = 'none'; }, 300); }
/* --- Achievement Toast --- */
function showAchievement(icon, title, subtitle) {
var container = document.querySelector('.ggs-toast-container');
var toast = document.createElement('div');
toast.className = 'ggs-toast';
toast.innerHTML = '<span class="ggs-toast-icon">' + icon + '</span><div><div class="ggs-toast-text">' + title + '</div><div class="ggs-toast-sub">' + subtitle + '</div></div>';
container.appendChild(toast);
setTimeout(function() { toast.remove(); }, 4500);
}
/* --- Graph Update Helper --- */
function updateGraph(cardIndex, points, currentValue) {
var cards = document.querySelectorAll('.ggs-graph-card');
if (!cards[cardIndex]) return;
var svg = cards[cardIndex].querySelector('.ggs-graph-svg');
var line = svg.querySelector('.ggs-graph-line');
var dot = svg.querySelector('.ggs-graph-dot');
var valEl = cards[cardIndex].querySelector('.ggs-graph-value');
if (line) line.setAttribute('points', points);
if (dot && points) {
var last = points.trim().split(' ').pop().split(',');
dot.setAttribute('cx', last[0]);
dot.setAttribute('cy', last[1]);
}
if (valEl && currentValue !== undefined) valEl.textContent = currentValue;
}
/* --- Status Parameter Update Helper --- */
function updateParam(index, value, percent, trend) {
var params = document.querySelectorAll('.ggs-zone-status .ggs-param');
if (!params[index]) return;
var valEl = params[index].querySelector('.ggs-param-value');
var trendEl = params[index].querySelector('.ggs-trend');
var bar = params[index].nextElementSibling;
if (valEl) valEl.textContent = value;
if (bar && bar.classList.contains('ggs-param-bar')) {
var fill = bar.querySelector('.ggs-param-fill');
if (fill) {
fill.style.width = percent + '%';
fill.className = 'ggs-param-fill ' + (percent > 66 ? 'good' : percent > 33 ? 'warning' : 'danger');
}
}
if (trendEl && trend !== undefined) {
trendEl.textContent = (trend > 0 ? '+' : '') + trend;
trendEl.className = 'ggs-trend ' + (trend >= 0 ? 'up' : 'down');
}
}
/* --- In-Canvas Events --- */
var _eventList = [];
function showEvent(icon, text, type) {
type = type || 'neutral';
// 1. Grosse Notification im Canvas
var vp = document.getElementById('event-viewport');
var n = document.createElement('div');
n.className = 'ggs-event-notification ' + type;
n.innerHTML = '<span class="ev-icon">' + icon + '</span> ' + text;
vp.appendChild(n);
setTimeout(function() { n.remove(); }, 3800);
// 2. In den Stapel einfügen
_eventList.unshift({ icon: icon, text: text, type: type });
if (_eventList.length > 30) _eventList.pop();
renderEventStack();
}
function renderEventStack() {
var list = document.getElementById('event-stack-list');
var countEl = document.getElementById('event-count');
countEl.textContent = _eventList.length;
list.innerHTML = _eventList.map(function(e) {
return '<div class="ggs-event-stack-item ' + e.type + '">' + e.icon + ' ' + e.text + '</div>';
}).join('');
}
function toggleEventStack() {
document.getElementById('event-stack').classList.toggle('open');
}
/* --- Graph Zoom (Klick öffnet vergrössertes Overlay) --- */
document.querySelectorAll('.ggs-graph-card').forEach(function(card) {
card.addEventListener('click', function() {
var label = card.querySelector('.ggs-graph-label');
var value = card.querySelector('.ggs-graph-value');
var svg = card.querySelector('.ggs-graph-svg');
if (!svg) return;
var overlay = document.createElement('div');
overlay.className = 'ggs-graph-zoom-overlay';
overlay.onclick = function(e) { if (e.target === overlay) overlay.remove(); };
var zoomCard = document.createElement('div');
zoomCard.className = 'ggs-graph-zoom-card';
var header = document.createElement('div');
header.className = 'ggs-graph-zoom-header';
header.innerHTML = '<div class="ggs-graph-label">' + (label ? label.textContent : '') + '</div>'
+ '<div class="ggs-graph-value" style="' + (value ? value.getAttribute('style') || '' : '') + '">' + (value ? value.textContent : '') + '</div>';
var bigSvg = svg.cloneNode(true);
bigSvg.style.height = '100%';
bigSvg.style.flex = '1';
bigSvg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
zoomCard.appendChild(header);
zoomCard.appendChild(bigSvg);
overlay.appendChild(zoomCard);
document.body.appendChild(overlay);
document.addEventListener('keydown', function close(e) {
if (e.key === 'Escape') { overlay.remove(); document.removeEventListener('keydown', close); }
});
});
});
/* --- Glossar-System --- */
/* Jedes Modul erweitert dieses Objekt mit eigenen Einträgen */
var GLOSSAR = {
windpark: {
title: 'Windpark',
text: 'Eine Gruppe von Windkraftanlagen (Windrädern), die gemeinsam Strom erzeugen. Die Rotoren werden vom Wind angetrieben und wandeln Bewegungsenergie in elektrische Energie um. Windparks stehen oft auf Hügeln, an Küsten oder auf dem Meer (Offshore).',
unit: 'Leistung in MW (Megawatt)',
link: 'https://de.wikipedia.org/wiki/Windpark'
},
solaranlage: {
title: 'Solaranlage',
text: 'Wandelt Sonnenlicht direkt in Strom um (Photovoltaik) oder nutzt Sonnenwärme (Solarthermie). Eine typische Anlage auf einem Hausdach erzeugt etwa 5-10 kW, ein Solarpark im Feld mehrere MW.',
unit: 'Leistung in kW oder MW',
link: 'https://de.wikipedia.org/wiki/Photovoltaikanlage'
},
kuestenschutz: {
title: 'Küstenschutz',
text: 'Massnahmen zum Schutz der Küste vor Überflutung und Erosion. Dazu gehören Deiche, Wellenbrecher, Dünen und Mangroven. Besonders wichtig bei steigendem Meeresspiegel durch den Klimawandel.',
link: 'https://de.wikipedia.org/wiki/Küstenschutz'
},
co2_filter: {
title: 'CO₂-Filter (Carbon Capture)',
text: 'Technologie, die CO₂ direkt aus der Luft oder aus Abgasen filtert und unterirdisch speichert. Noch sehr teuer und energieintensiv, wird aber als Ergänzung zu erneuerbaren Energien erforscht.',
link: 'https://de.wikipedia.org/wiki/CO2-Abscheidung_und_-Speicherung'
},
co2: {
title: 'CO₂ (Kohlendioxid)',
text: 'Ein farbloses Gas, das bei der Verbrennung fossiler Brennstoffe (Kohle, Öl, Gas) entsteht. Es verstärkt den Treibhauseffekt und ist der wichtigste Treiber des Klimawandels. Die Konzentration in der Atmosphäre wird in ppm (parts per million) gemessen.',
unit: 'Ausstoss in Mt (Megatonnen) = Millionen Tonnen',
link: 'https://de.wikipedia.org/wiki/Kohlenstoffdioxid'
},
megatonne: {
title: 'Megatonne (Mt)',
text: 'Eine Million Tonnen. Wird verwendet, um sehr grosse Mengen zu beschreiben, zum Beispiel den jährlichen CO₂-Ausstoss eines Landes. Österreich stösst etwa 70 Mt CO₂ pro Jahr aus.',
unit: '1 Mt = 1.000.000 Tonnen'
},
temperatur: {
title: 'Globale Durchschnittstemperatur',
text: 'Die mittlere Temperatur der gesamten Erdoberfläche. Seit der Industrialisierung (ca. 1850) ist sie um etwa 1,2 °C gestiegen. Das Pariser Abkommen will den Anstieg auf 1,5 °C begrenzen.',
unit: 'Anstieg in °C gegenüber vorindustriellem Niveau',
link: 'https://de.wikipedia.org/wiki/Globale_Erwärmung'
},
meeresspiegel: {
title: 'Meeresspiegel',
text: 'Die durchschnittliche Höhe der Meeresoberfläche. Steigt durch schmelzendes Eis und die Ausdehnung von warmem Wasser. Seit 1900 ist er um ca. 20 cm gestiegen. Bei ungebremstem Klimawandel könnten es bis 2100 über 1 Meter sein.',
unit: 'Anstieg in cm',
link: 'https://de.wikipedia.org/wiki/Meeresspiegelanstieg'
},
budget: {
title: 'Budget',
text: 'Das verfügbare Geld für Massnahmen. In der Simulation erhältst du jedes Jahr Einnahmen (Steuern). Massnahmen kosten Geld bei der Errichtung und im laufenden Betrieb (Unterhalt).',
unit: 'in Mio € (Millionen Euro)'
}
};
var _glossarPopup = null;
document.addEventListener('click', function(e) {
// Schliesse offenes Popup
if (_glossarPopup && !_glossarPopup.contains(e.target)) {
_glossarPopup.remove();
_glossarPopup = null;
}
// Öffne neues Popup
var el = e.target.closest('[data-glossar]');
if (!el) return;
var key = el.dataset.glossar;
var entry = GLOSSAR[key];
if (!entry) return;
if (_glossarPopup) { _glossarPopup.remove(); _glossarPopup = null; }
var popup = document.createElement('div');
popup.className = 'ggs-glossar-popup';
var html = '<div class="ggs-glossar-title">' + entry.title + '</div>';
html += '<div class="ggs-glossar-text">' + entry.text + '</div>';
if (entry.unit) html += '<div class="ggs-glossar-unit">' + entry.unit + '</div>';
if (entry.link) html += '<br><a href="' + entry.link + '" target="_blank" class="ggs-glossar-link">Mehr erfahren →</a>';
popup.innerHTML = html;
document.body.appendChild(popup);
_glossarPopup = popup;
// Positionierung unter dem Element
var rect = el.getBoundingClientRect();
var pw = popup.offsetWidth;
var left = Math.max(8, Math.min(rect.left, window.innerWidth - pw - 8));
popup.style.left = left + 'px';
popup.style.top = (rect.bottom + 8) + 'px';
});
/* --- Placeholder Hooks (Module überschreiben diese) --- */
function onSpeedChange(speed) { console.log('Speed:', speed); }
function nextTutorialStep() { console.log('Next tutorial step'); }
function nextLevel() { hideOverlay('level-overlay'); console.log('Next level'); }
function restartGame() { location.reload(); }
/* --- Music Player (Standard aus Design-System) ---
Modul ruft einmalig ggsMusicSetup([{key,label,file},...]) auf.
key = interner Name (z.B. 'lounge')
label = Anzeige im Dropdown (z.B. 'Lounge')
file = Pfad zur MP3 (relativ zu aktueller Seite)
*/
window.ggsMusicSetup = function(tracks) {
var el = document.getElementById('ggs-music');
var sel = document.getElementById('ggs-music-sel');
var btn = document.getElementById('ggs-music-toggle');
var vol = document.getElementById('ggs-music-vol');
if (!el || !tracks || !tracks.length) return;
el.hidden = false;
var audio = new Audio();
audio.loop = true;
var simId = (window.__GGS__ && window.__GGS__.simId) || 'ggs';
var lsKey = 'ggs-music-' + simId;
var state = JSON.parse(localStorage.getItem(lsKey) || '{}');
sel.innerHTML = tracks.map(function(t) {
return '<option value="'+t.key+'" data-file="'+t.file+'">'+t.label+'</option>';
}).join('');
if (state.track) sel.value = state.track;
audio.src = sel.options[sel.selectedIndex].dataset.file;
audio.volume = (state.volume ?? 35) / 100;
vol.value = state.volume ?? 35;
function save() {
localStorage.setItem(lsKey, JSON.stringify({
track: sel.value, volume: +vol.value, playing: !audio.paused
}));
}
sel.addEventListener('change', function() {
var wasPlaying = !audio.paused;
audio.src = sel.options[sel.selectedIndex].dataset.file;
if (wasPlaying) audio.play().catch(function(){});
save();
});
btn.addEventListener('click', function() {
if (audio.paused) {
audio.play().then(function() {
btn.textContent = '⏸';
btn.classList.add('playing');
save();
}).catch(function(){ /* Browser blockiert Autoplay — User muss klicken */ });
} else {
audio.pause();
btn.textContent = '▶';
btn.classList.remove('playing');
save();
}
});
vol.addEventListener('input', function() {
audio.volume = vol.value / 100;
save();
});
// Auto-Resume wenn vorher gespielt (Browser blockt das oft — Fallback: erst nach erster User-Geste)
if (state.playing) {
var resume = function() {
audio.play().then(function() {
btn.textContent = '⏸';
btn.classList.add('playing');
}).catch(function(){});
document.removeEventListener('click', resume);
document.removeEventListener('keydown', resume);
};
document.addEventListener('click', resume, { once: true });
document.addEventListener('keydown', resume, { once: true });
}
};
/* --- Lehrplan-Link im Header dynamisch setzen --- */
(function() {
var link = document.getElementById('ggs-lehrplan-link');
if (!link) return;
var simId = (window.__GGS__ && window.__GGS__.simId) || 'klima';
var baseUrl = (window.__GGS__ && window.__GGS__.baseUrl) || '';
link.href = baseUrl + '/modul-' + simId + '#lehrplan-bezug';
})();
/* --- Loading Screen ausblenden --- */
window.addEventListener('load', function() {
var ls = document.getElementById('loading-screen');
if (ls) {
setTimeout(function() {
ls.classList.add('fade-out');
setTimeout(function() { ls.remove(); }, 600);
}, 400);
}
});
/* --- API: Fortschritt melden --- */
function reportProgress(data) {
if (!window.__GGS__ || !window.__GGS__.sessionId) return;
fetch((window.__GGS__.baseUrl || '') + '/php/api/progress.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
session_id: window.__GGS__.sessionId,
sim_id: window.__GGS__.simId || 'unknown',
action: 'update',
data: data
})
}).catch(function() {});
}
/* --- API: Assessment einreichen --- */
function submitAssessment(results) {
if (!window.__GGS__ || !window.__GGS__.sessionId) return;
return fetch((window.__GGS__.baseUrl || '') + '/php/api/progress.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
session_id: window.__GGS__.sessionId,
sim_id: window.__GGS__.simId || 'unknown',
action: 'submit_assessment',
data: results
})
}).catch(function() {});
}
/* --- API: Reflexionsantwort speichern --- */
function submitReflection(levelNum, question, answer) {
if (!window.__GGS__ || !window.__GGS__.sessionId) return;
fetch((window.__GGS__.baseUrl || '') + '/php/api/progress.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
session_id: window.__GGS__.sessionId,
sim_id: window.__GGS__.simId || 'unknown',
action: 'reflection',
data: { level: levelNum, question: question, answer: answer }
})
}).catch(function() {});
}
</script>
</body>
</html>