Files
geograsim/App/sims/staustufen/game.html
T
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

860 lines
36 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>Staustufen — Wasserkraft in der Kaskade</title>
<link rel="icon" type="image/png" href="../../favicon-96x96.png" sizes="96x96">
<link rel="stylesheet" href="../../assets/fonts/inter.css">
<link rel="stylesheet" href="../../assets/css/design-system.css">
<style>
/* ===== Modul-spezifische Styles =====
Plattform-Klassen aus design-system.css. Hier nur Staustufen-eigenes. */
html, body { height: 100%; margin: 0; padding: 0;
font-family: 'Inter', system-ui, sans-serif;
background: var(--ggs-bg); color: var(--ggs-text);
touch-action: manipulation; }
.st-layout { display: grid; grid-template-rows: auto 1fr auto;
height: 100dvh; }
.st-main {
display: grid;
grid-template-columns: 240px 1fr;
gap: 12px; padding: 12px;
min-height: 0;
}
@media (max-width: 1199px) and (min-width: 900px) {
.st-main { grid-template-columns: 215px 1fr; }
}
@media (max-width: 899px) {
.st-main { grid-template-columns: 1fr;
grid-template-rows: auto 1fr; }
}
.st-panel { background: var(--ggs-white, #fff);
border-radius: var(--ggs-radius-md);
padding: 14px;
box-shadow: var(--ggs-shadow-sm);
overflow-y: auto;
}
.st-panel h3 { margin: 0 0 10px;
font-size: 0.95rem; color: var(--ggs-fjord-dark); }
/* === Status-Karten (links) === */
.st-kpi { margin-bottom: 12px; }
.st-kpi-label { font-size: 0.74rem; color: var(--ggs-text-muted);
text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 2px; }
.st-kpi-value { font-size: 1.3rem; font-weight: 700;
color: var(--ggs-fjord-dark); font-variant-numeric: tabular-nums; }
.st-kpi-sub { font-size: 0.78rem; color: var(--ggs-text-muted); }
.st-day-pill { display: inline-block; padding: 4px 12px;
border-radius: var(--ggs-radius-pill, 16px);
background: var(--ggs-fjord-light); color: var(--ggs-fjord-dark);
font-weight: 700; font-size: 0.85rem; margin-bottom: 10px; }
.st-progressbar { height: 8px; background: #e8e4d8;
border-radius: 4px; overflow: hidden; margin-top: 4px; }
.st-progressbar-fill { height: 100%; background: var(--ggs-moss);
transition: width 0.3s ease; }
.st-progressbar-fill.warn { background: #d6a04e; }
.st-progressbar-fill.danger { background: var(--ggs-coral); }
/* Korridor-Status-Farben (Atlas-Update Netzbalance) */
.st-balance-green { color: var(--ggs-moss-dark); }
.st-balance-yellow { color: #b07a1f; }
.st-balance-red { color: var(--ggs-coral); }
/* === Bipolar-Live-Meter === Plus/Minus von der Mitte weg.
Skala: 50 % (Lücke ganz links) → 0 (Korridor-Mitte) → +50 % (Überschuss).
Werte über ±50 % werden visuell gecappt. */
.st-livemeter {
position: relative; height: 56px; margin: 6px 0 4px;
}
.st-livemeter-track {
position: absolute; left: 0; right: 0; top: 4px; height: 18px;
border-radius: 9px;
background: linear-gradient(90deg,
#c85c4a 0%, /* rote Lücke */
#c85c4a 20%,
#d6a04e 20%, #d6a04e 35%, /* gelbe Lücke */
#5a8a5e 35%, #5a8a5e 50%, /* grüner Korridor links */
#5a8a5e 50%, #5a8a5e 65%, /* grüner Korridor rechts */
#d6a04e 65%, #d6a04e 80%, /* gelber Überschuss */
#c85c4a 80%, #c85c4a 100%); /* roter Überschuss */
box-shadow: inset 0 1px 2px rgba(0,0,0,.15);
}
/* Mittellinie für 0 — kontrastierender Strich */
.st-livemeter-track::after {
content: ''; position: absolute;
left: 50%; top: -2px; width: 2px; height: 22px;
background: #fff; box-shadow: 0 0 0 1px #455a64;
transform: translateX(-50%); border-radius: 1px;
}
/* Indikator-Nadel — wird per JS positioniert */
.st-livemeter-needle {
position: absolute; top: 0; left: 50%;
width: 4px; height: 26px;
background: #1a1a1a; border: 1.5px solid #fff;
border-radius: 2px;
transform: translateX(-50%);
box-shadow: 0 1px 3px rgba(0,0,0,.4);
transition: left 0.4s ease;
pointer-events: none;
}
.st-livemeter-needle::after {
content: ''; position: absolute;
top: -4px; left: 50%; transform: translateX(-50%);
width: 0; height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-bottom: 5px solid #1a1a1a;
}
.st-livemeter-axis {
position: absolute; left: 0; right: 0; top: 28px;
display: flex; justify-content: space-between;
font-size: 0.62rem; color: var(--ggs-text-muted);
line-height: 1.15; padding: 0 2px;
}
.st-livemeter-axis span { text-align: center; flex: 0 0 auto; }
.st-livemeter-axis span:first-child { text-align: left; }
.st-livemeter-axis span:last-child { text-align: right; }
.st-livemeter-axis b { font-weight: 700; color: var(--ggs-text); }
/* === Querschnitt-Canvas (Mitte) === */
.st-canvas { background: var(--ggs-white, #fff);
border-radius: var(--ggs-radius-md);
box-shadow: var(--ggs-shadow-sm);
display: grid;
grid-template-rows: 1fr auto;
min-height: 0; overflow: hidden;
position: relative;
}
.st-cross-section {
width: 100%; height: 100%;
display: block;
background: linear-gradient(180deg, #d6e7ee 0%, #e8eef0 65%, #f0ebde 100%);
}
/* Bergkörper */
.st-mountain { fill: #4a7c4e; }
.st-mountain-shade{ fill: #2f5a32; opacity: 0.55; }
.st-mountain-rock { fill: #6a6a6a; opacity: 0.18; }
.st-snow { fill: #fafaf6; }
.st-tal-floor { fill: #d8d2c0; }
.st-tal-grass { fill: #b8c89b; }
/* Wasser */
.st-water-top, .st-water-bot {
fill: url(#stWaterGrad);
transition: y 0.6s ease, height 0.6s ease;
}
.st-water-top.danger, .st-water-bot.danger { fill: #c85c4a; opacity: 0.85; }
/* Druckrohre — graue Schale (Vorlage-Stil) */
.st-pipe-shell {
stroke: #9E9E9E; stroke-width: 18;
stroke-linejoin: round; stroke-linecap: round;
fill: none;
}
/* Wasser-Animation im Rohr — state-driven via Klassen.
Pfad-Richtung: Pfad A geht Oberbecken→Maschinenhaus,
Pfad B Maschinenhaus→Unterbecken. Im Turbinenbetrieb wandern Striche
in Pfad-Richtung (forward), im Pumpbetrieb gegen die Richtung. */
.st-pipe-flow {
stroke-width: 3; fill: none;
stroke-linecap: round;
stroke-dasharray: 15 10;
stroke-dashoffset: 0;
opacity: 0;
transition: opacity 0.25s ease, stroke 0.25s ease;
}
.st-pipe-flow.active.turbine { stroke: #01579B; opacity: 1;
animation: st-flow-fwd var(--st-flow-dur, 1.5s) linear infinite; }
.st-pipe-flow.active.pump { stroke: #00E676; opacity: 1;
animation: st-flow-bwd var(--st-flow-dur, 1.5s) linear infinite; }
@keyframes st-flow-fwd { to { stroke-dashoffset: -25; } }
@keyframes st-flow-bwd { to { stroke-dashoffset: 25; } }
.st-pump-symbol, .st-turb-symbol {
transition: filter 0.2s ease, transform 0.2s ease;
transform-box: fill-box; transform-origin: center;
}
.st-pump-symbol.active { filter: drop-shadow(0 0 6px rgba(74,124,138,.6)); }
.st-turb-symbol.active { filter: drop-shadow(0 0 6px rgba(90,138,94,.6)); }
/* Beschriftungen auf dem Querschnitt — Lesbarkeits-Outline,
font-size kommt vom SVG-Attribut (Vorlage-Werte 13..20). */
.st-cs-label {
font-family: 'Segoe UI', 'Inter', Roboto, system-ui, sans-serif;
paint-order: stroke;
stroke: rgba(255,255,255,.85);
stroke-width: 4px;
stroke-linejoin: round;
}
.st-cs-pct {
font-family: 'Segoe UI', 'Inter', Roboto, system-ui, sans-serif;
font-variant-numeric: tabular-nums;
paint-order: stroke;
stroke: rgba(255,255,255,.9);
stroke-width: 4px;
stroke-linejoin: round;
}
/* === Bidirektionaler Schieberegler — Bedien-Footer unter dem Querschnitt ===
Als HTML-Element (NICHT foreignObject — auf iPad/iOS Safari unzuverlässig).
Mittelpunkt = aus, links Pumpe (grün), rechts Turbine (blau).
Werte stufenlos von 100 bis +100. */
.st-machine-control {
display: grid;
grid-template-columns: 1fr auto;
grid-template-rows: auto auto;
gap: 6px 10px;
align-items: center;
padding: 10px 16px 14px;
background: linear-gradient(180deg, #ECEFF1 0%, #CFD8DC 100%);
border-top: 2px solid #90A4AE;
box-sizing: border-box;
font-family: 'Inter', system-ui, sans-serif;
}
.st-machine-readout {
font-size: 13px; font-weight: 700; color: #263238;
text-align: center; grid-column: 1 / -1;
font-variant-numeric: tabular-nums;
text-transform: uppercase; letter-spacing: 0.04em;
padding: 2px 0;
}
.st-bipolar-wrap {
display: grid; grid-template-columns: auto 1fr auto;
align-items: center; gap: 8px;
}
.st-bipolar-end {
font-size: 11px; font-weight: 700; user-select: none;
white-space: nowrap;
}
.st-bipolar-end.pump-end { color: #2E7D32; }
.st-bipolar-end.turb-end { color: #01579B; }
.st-bipolar-zero {
appearance: none; background: #ECEFF1;
border: 1px solid #B0BEC5; border-radius: 50%;
width: 38px; height: 38px;
cursor: pointer; font-size: 14px; line-height: 1;
color: #607D8B; padding: 0;
font-family: inherit; font-weight: 700;
touch-action: manipulation;
}
.st-bipolar-zero:hover { background: #fff; color: #455A64; }
.st-bipolar-zero:active { background: #B0BEC5; color: #fff; }
.st-bipolar-slider {
-webkit-appearance: none; appearance: none;
width: 100%; height: 36px;
background: transparent; cursor: pointer;
touch-action: manipulation;
margin: 0; padding: 0;
}
.st-bipolar-slider:focus { outline: none; }
/* Track: links Pumpe-grün-Bereich, Mitte hell, rechts Turbine-blau-Bereich.
CSS-Variable --st-thumb-pct steuert Highlight ab Mitte zur Thumb-Position. */
.st-bipolar-slider::-webkit-slider-runnable-track {
height: 8px; border-radius: 4px;
background: linear-gradient(90deg,
rgba(46,125,50,.18) 0%, rgba(46,125,50,.18) 50%,
rgba(1,87,155,.18) 50%, rgba(1,87,155,.18) 100%);
}
.st-bipolar-slider.mode-turbine::-webkit-slider-runnable-track {
background: linear-gradient(90deg,
rgba(46,125,50,.18) 0%, rgba(46,125,50,.18) 50%,
#01579B 50%, #01579B var(--st-thumb-pct, 50%),
rgba(1,87,155,.18) var(--st-thumb-pct, 50%), rgba(1,87,155,.18) 100%);
}
.st-bipolar-slider.mode-pump::-webkit-slider-runnable-track {
background: linear-gradient(90deg,
rgba(46,125,50,.18) 0%, rgba(46,125,50,.18) var(--st-thumb-pct, 50%),
#00C853 var(--st-thumb-pct, 50%), #00C853 50%,
rgba(1,87,155,.18) 50%, rgba(1,87,155,.18) 100%);
}
/* Mittelmarkierung: zarter Strich bei 50 % */
.st-bipolar-wrap { position: relative; }
.st-bipolar-wrap::after {
content: ''; position: absolute;
left: calc(50% + 4px); transform: translateX(-50%);
top: 50%; margin-top: -7px;
width: 2px; height: 14px;
background: #607D8B; border-radius: 1px;
pointer-events: none; opacity: 0.7;
}
/* Thumb — iPad-Touch-Target ≥ 36 px */
.st-bipolar-slider::-webkit-slider-thumb {
-webkit-appearance: none; appearance: none;
width: 38px; height: 38px; border-radius: 19px;
background: #fff;
border: 3px solid #455A64;
margin-top: -15px;
box-shadow: 0 2px 6px rgba(0,0,0,.25);
cursor: grab;
}
.st-bipolar-slider.mode-pump::-webkit-slider-thumb { border-color: #2E7D32; }
.st-bipolar-slider.mode-turbine::-webkit-slider-thumb { border-color: #01579B; }
.st-bipolar-slider:active::-webkit-slider-thumb { cursor: grabbing; }
.st-bipolar-slider::-moz-range-track {
height: 8px; border-radius: 4px; background: #ECEFF1;
}
.st-bipolar-slider::-moz-range-thumb {
width: 36px; height: 36px; border-radius: 18px;
background: #fff;
border: 3px solid #455A64;
box-shadow: 0 2px 6px rgba(0,0,0,.25);
cursor: grab;
}
.st-bipolar-slider.mode-pump::-moz-range-thumb { border-color: #2E7D32; }
.st-bipolar-slider.mode-turbine::-moz-range-thumb { border-color: #01579B; }
/* Tick-Skala unter dem Slider — 21 Striche je 10 %, längere bei 0/±50/±100 */
.st-bipolar-slider-area { position: relative; padding-bottom: 24px; }
.st-tick-rail {
position: absolute; left: 18px; right: 18px; top: 32px;
display: flex; justify-content: space-between;
pointer-events: none;
}
.st-tick-rail .tick {
width: 1.5px; height: 7px; background: #90A4AE; border-radius: 1px;
}
.st-tick-rail .tick.t-mid { height: 11px; background: #607D8B; }
.st-tick-rail .tick.t-zero { height: 14px; width: 2.5px; background: #263238; }
.st-tick-rail .tick.t-100 { height: 11px; background: #607D8B; }
.st-tick-labels {
position: absolute; left: 14px; right: 14px; top: 50px;
display: flex; justify-content: space-between;
font-size: 10.5px; color: #455A64; font-weight: 700;
pointer-events: none; user-select: none;
font-variant-numeric: tabular-nums;
}
/* === Bedarfs-Graph (unten) === */
.st-graph-zone {
background: var(--ggs-white, #fff);
border-top: 1px solid var(--ggs-border, #e2ddd1);
padding: 12px 16px; height: 240px;
display: flex; gap: 16px; align-items: stretch;
}
.st-graph-meta { width: 200px; flex-shrink: 0;
display: flex; flex-direction: column; gap: 4px;
font-size: 0.78rem; color: var(--ggs-text-muted); }
.st-graph-meta b { color: var(--ggs-text);
font-variant-numeric: tabular-nums; font-weight: 700; }
.st-graph-meta .st-legend-row { display: flex; align-items: center; gap: 6px; }
.st-graph-swatch { display: inline-block; width: 14px; height: 4px;
border-radius: 2px; }
.st-graph-swatch.demand { background: #c85c4a; }
.st-graph-swatch.sun { background: #e8a82e; }
.st-graph-swatch.supply { background: #1f4e5a; }
.st-graph-swatch.cursor { background: var(--ggs-fjord); height: 14px; width: 2px; }
.st-graph-svg { flex: 1; min-width: 0; }
/* === Speed-Controls === */
.st-speed { display: flex; gap: 4px; }
.st-speed-btn { min-width: 36px; min-height: 36px;
padding: 4px 10px;
border: 1px solid var(--ggs-border, #e2ddd1);
background: var(--ggs-white, #fff); color: var(--ggs-text);
border-radius: 6px; cursor: pointer;
font-size: 0.85rem; font-weight: 600;
font-family: inherit;
}
.st-speed-btn:hover { background: var(--ggs-bg-dark, #e8e4d8); }
.st-speed-btn.active { background: var(--ggs-fjord);
color: #fff; border-color: var(--ggs-fjord-dark); }
/* === InfoCards (slide-in von rechts, nicht blockierend) ===
Pattern: Logistik-Infocards. Triggern bei didaktisch passenden Momenten,
verschwinden automatisch nach ~8 s oder per Klick. */
.st-infocards {
position: fixed; top: 76px; right: 16px; z-index: 1200;
display: flex; flex-direction: column; gap: 10px;
pointer-events: none;
max-width: calc(100% - 32px);
}
.st-infocard {
background: #fff; border-radius: 12px; overflow: hidden;
box-shadow: 0 8px 24px rgba(0,0,0,.18);
width: 320px; pointer-events: auto;
transform: translateX(380px); opacity: 0;
transition: transform .35s ease, opacity .35s ease;
border-left: 4px solid var(--ggs-fjord);
}
.st-infocard.visible { transform: translateX(0); opacity: 1; }
.st-infocard.dismissing { transform: translateX(380px); opacity: 0; }
.st-infocard.tone-pump { border-left-color: var(--ggs-fjord); }
.st-infocard.tone-turbine { border-left-color: var(--ggs-moss); }
.st-infocard.tone-warn { border-left-color: #d6a04e; }
.st-infocard.tone-spike { border-left-color: var(--ggs-coral); }
.st-infocard-img {
width: 100%; height: 130px;
background: var(--ggs-fjord-light);
background-size: cover; background-position: center;
}
.st-infocard-body { padding: 12px 14px 14px; }
.st-infocard-title {
font-size: 0.95rem; font-weight: 700;
color: var(--ggs-fjord-dark); margin: 0 0 5px;
display: flex; justify-content: space-between;
align-items: center; gap: 6px;
}
.st-infocard-title-text { flex: 1; min-width: 0; }
.st-infocard-text {
font-size: 0.85rem; line-height: 1.5;
color: var(--ggs-text); margin: 0;
}
.st-infocard-text b { color: var(--ggs-fjord-dark); }
.st-infocard-close {
background: none; border: 0; cursor: pointer;
font-size: 18px; color: var(--ggs-text-muted);
padding: 0; min-width: 28px; min-height: 28px;
line-height: 1; flex-shrink: 0;
border-radius: 4px; font-family: inherit;
}
.st-infocard-close:hover {
color: var(--ggs-text); background: var(--ggs-bg-dark, #e8e4d8);
}
/* === Mute-Toggle im Header === */
.ggs-btn-ghost.muted { opacity: 0.55; }
/* === Hint-Overlay === */
.st-hint-overlay {
position: fixed; inset: 0; background: rgba(20,40,50,.55);
backdrop-filter: blur(4px);
display: none; align-items: center; justify-content: center;
z-index: 9000; padding: 16px;
}
.st-hint-overlay.open { display: flex; }
.st-hint-modal { background: #fff; border-radius: 12px;
padding: 24px 28px; max-width: 480px; width: 100%;
box-shadow: 0 12px 40px rgba(0,0,0,.3);
}
.st-hint-modal h2 { margin: 0 0 8px; color: var(--ggs-fjord-dark);
font-size: 1.15rem; }
.st-hint-modal p { margin: 0 0 12px; color: var(--ggs-text);
font-size: 0.95rem; line-height: 1.55; }
.st-hint-modal-actions { text-align: right; margin-top: 14px; }
.st-hint-modal-actions .ggs-btn-primary {
min-height: 40px; padding: 8px 18px;
background: var(--ggs-fjord); color: #fff; border: none;
border-radius: 8px; cursor: pointer; font-weight: 600;
font-family: inherit; font-size: 0.9rem;
}
/* End-Screen-Body-Spezifika (nutzt .ggs-endscreen aus Design-System) */
.st-endscreen-grade {
font-size: 4rem; font-weight: 800; line-height: 1;
margin: 8px 0 4px;
}
.st-endscreen-grade.A { color: var(--ggs-moss-dark); }
.st-endscreen-grade.B { color: var(--ggs-moss); }
.st-endscreen-grade.C { color: #d6a04e; }
.st-endscreen-grade.D { color: #c47744; }
.st-endscreen-grade.E { color: var(--ggs-coral); }
.st-endscreen-detail { font-size: 0.88rem; line-height: 1.5;
margin-bottom: 10px; color: var(--ggs-text); }
</style>
</head>
<body>
<script>
/*__STAUSTUFEN_INJECTION__*/
/* Fallback fuer Direkt-Aufruf der Datei (ohne Wrapper) */
if (typeof window.STAUSTUFEN_BASE === 'undefined') {
window.STAUSTUFEN_BASE = '../../sims/staustufen/';
window.STAUSTUFEN_SESSION_MODE = 'free';
window.STAUSTUFEN_FORCED_LEVEL = null;
window.STAUSTUFEN_SESSION_ID = null;
window.STUDENT_EASY = false;
window.STAUSTUFEN_API_BASE = '/geograsim/App/api';
}
</script>
<div class="st-layout">
<!-- ============= Header (Plattform-Standard) ============= -->
<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">Staustufen — Wasserkraft in der Kaskade</span>
<span class="ggs-header-badge" id="levelBadge">Level 1</span>
<div class="ggs-header-spacer"></div>
<div class="ggs-header-actions">
<div class="st-speed" role="group" aria-label="Tempo">
<button class="st-speed-btn" id="speedPause" title="Pause" aria-label="Pause"></button>
<button class="st-speed-btn active" id="speed1x" title="Normales Tempo">1×</button>
<button class="st-speed-btn" id="speed4x" title="Schneller Vorlauf">4×</button>
</div>
<button class="ggs-btn-ghost muted" id="btnAudioToggle"
title="Sprachausgabe an/aus" aria-label="Sprachausgabe umschalten"
aria-pressed="false">🔇</button>
<a class="ggs-btn-ghost" href="../../schueler" title="Zurück zum Cockpit"
aria-label="Zurück zum Cockpit">🏠</a>
</div>
</header>
<!-- InfoCard-Stapel (slide-in von rechts) -->
<div class="st-infocards" id="stInfocards" aria-live="polite"></div>
<!-- ============= Hauptbereich ============= -->
<main class="st-main">
<!-- Status-Spalte links -->
<aside class="st-panel" id="statusPanel">
<span class="st-day-pill" id="dayPill">Tag 1 / 5</span>
<!-- Live-Netzbalance: Bipolar-Meter, Mitte = im Korridor,
links = Lücke (Verbrauch &gt; Versorgung), rechts = Überschuss -->
<div class="st-kpi">
<div class="st-kpi-label">Netzbalance jetzt</div>
<div class="st-livemeter">
<div class="st-livemeter-track" aria-hidden="true"></div>
<div class="st-livemeter-needle" id="livemeterNeedle"></div>
<div class="st-livemeter-axis">
<span>50 %<br><b>Lücke</b></span>
<span>0</span>
<span>+50 %<br><b>Überschuss</b></span>
</div>
</div>
<div class="st-kpi-value st-balance-green" id="kpiKorridor"
style="font-size:0.95rem; margin-top:6px;">🟢 im Korridor</div>
<div class="st-kpi-sub" id="kpiKorridorSub">±15 % um den Bedarf</div>
</div>
<div class="st-kpi">
<div class="st-kpi-label">Anteil im Korridor (bisher)</div>
<div class="st-kpi-value" id="kpiNetzbalance"
style="font-size:1.05rem;">100 %</div>
<div class="st-progressbar">
<div class="st-progressbar-fill" id="kpiNetzbalanceBar"
style="width:100%"></div>
</div>
<div class="st-kpi-sub" id="kpiNetzbalanceSub">Bisher im Gleichgewicht.</div>
</div>
<div class="st-kpi">
<div class="st-kpi-label">Verluste</div>
<div class="st-kpi-value" id="kpiVerluste"
style="font-size:1.05rem;">0 GWh</div>
<div class="st-kpi-sub">Pumpen + Turbinieren</div>
</div>
<div class="st-kpi">
<div class="st-kpi-label">Tageszeit</div>
<div class="st-kpi-value" id="kpiUhrzeit"
style="font-size:1.05rem;">00:00</div>
<div class="st-kpi-sub" id="kpiTickInfo">Tick 0 / 12</div>
</div>
</aside>
<!-- Sim-Zone Mitte: Querschnitt-SVG (Vorlage-Stil, state-driven) -->
<section class="st-canvas">
<svg class="st-cross-section" viewBox="0 0 1000 650"
preserveAspectRatio="xMidYMid meet"
aria-label="Querschnitt einer Pumpspeicher-Anlage">
<defs>
<linearGradient id="stSkyGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#4FC3F7"/>
<stop offset="100%" stop-color="#E1F5FE"/>
</linearGradient>
<linearGradient id="stEarthGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6D4C41"/>
<stop offset="100%" stop-color="#3E2723"/>
</linearGradient>
<linearGradient id="stWaterGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="rgba(3,169,244,0.6)"/>
<stop offset="100%" stop-color="rgba(2,119,189,0.85)"/>
</linearGradient>
<clipPath id="stUpperLakeClip">
<polygon points="660,140 910,140 850,200 680,200"/>
</clipPath>
<clipPath id="stLowerLakeClip">
<polygon points="130,420 383,420 300,480 150,480"/>
</clipPath>
<!-- Wiederverwendbare Symbole (aus Vorlage) -->
<g id="stTree">
<polygon points="0,-15 -8,0 8,0" fill="#2E7D32"/>
<polygon points="0,-25 -6,-8 6,-8" fill="#388E3C"/>
<rect x="-1.5" y="0" width="3" height="4" fill="#5D4037"/>
</g>
<g id="stBird">
<path d="M -10,-5 Q -5,-15 0,-5 Q 5,-15 10,-5 Q 5,-8 0,-2 Q -5,-8 -10,-5 Z"
fill="#424242"/>
</g>
<g id="stFish">
<ellipse cx="0" cy="0" rx="6" ry="3" fill="#FF9800"/>
<polygon points="-5,0 -10,-4 -10,4" fill="#FF9800"/>
<circle cx="3" cy="-1" r="0.8" fill="#212121"/>
</g>
</defs>
<!-- Himmel + Sonne -->
<rect width="1000" height="650" fill="url(#stSkyGrad)"/>
<circle cx="850" cy="80" r="40" fill="#FFF59D" opacity="0.8"/>
<!-- Bergsilhouetten (Tiefen-Layer) -->
<path d="M 0,400 L 150,220 L 350,380 L 600,150 L 800,280 L 1000,120 L 1000,500 L 0,500 Z"
fill="#CFD8DC" opacity="0.7"/>
<path d="M 0,450 L 250,280 L 500,420 L 800,200 L 1000,300 L 1000,500 L 0,500 Z"
fill="#B0BEC5" opacity="0.5"/>
<!-- Berg-/Erdkörper (Hauptpfad, Querschnitt) -->
<path d="M 0,650 L 1000,650 L 1000,50 L 850,200 L 680,200 L 620,250 L 300,480 L 150,480 L 100,500 L 0,500 Z"
fill="url(#stEarthGrad)"/>
<!-- Schnee auf rechtem Berg -->
<path d="M 1000,50 L 850,200 L 890,200 L 920,160 L 940,180 L 970,120 L 1000,150 Z"
fill="#FFFFFF" opacity="0.9"/>
<!-- Druckrohre (Schale) — zwei Pfade, beidseits des Maschinenhauses -->
<polyline class="st-pipe-shell" points="750,200 750,330 400,530 330,530"/>
<polyline class="st-pipe-shell" points="270,530 200,530 200,480"/>
<!-- Staumauern -->
<polygon points="650,120 670,120 680,200 620,250" fill="#90A4AE"
stroke="#607D8B" stroke-width="2"/>
<path d="M 650,120 L 670,120 L 680,200" fill="none" stroke="#ECEFF1" stroke-width="2"/>
<polygon points="120,400 140,400 150,480 100,500" fill="#90A4AE"
stroke="#607D8B" stroke-width="2"/>
<path d="M 120,400 L 140,400 L 150,480" fill="none" stroke="#ECEFF1" stroke-width="2"/>
<!-- Wasser im Oberbecken (state-driven, height/y) -->
<rect class="st-water-top" id="stWaterTop"
x="660" y="140" width="250" height="60"
clip-path="url(#stUpperLakeClip)"/>
<line x1="660" y1="140" x2="910" y2="140" stroke="#81D4FA" stroke-width="2"/>
<!-- Wasser im Unterbecken (state-driven) -->
<rect class="st-water-bot" id="stWaterBot"
x="130" y="420" width="253" height="60"
clip-path="url(#stLowerLakeClip)"/>
<line x1="130" y1="420" x2="383" y2="420" stroke="#81D4FA" stroke-width="2"/>
<!-- Wiesenränder neben Becken/Tal -->
<path d="M 0,500 L 100,500" fill="none" stroke="#689F38" stroke-width="8" stroke-linecap="round"/>
<path d="M 300,480 L 620,250" fill="none" stroke="#689F38" stroke-width="8" stroke-linecap="round"/>
<path d="M 850,200 L 900,150" fill="none" stroke="#689F38" stroke-width="8" stroke-linecap="round"/>
<!-- Bäume entlang Hängen + Tal -->
<use href="#stTree" x="20" y="496" transform="scale(1.2) translate(-5, 0)"/>
<use href="#stTree" x="40" y="496"/>
<use href="#stTree" x="65" y="496" transform="scale(1.5) translate(-22, 0)"/>
<use href="#stTree" x="480" y="350"/>
<use href="#stTree" x="510" y="325" transform="scale(1.3) translate(-120, -55)"/>
<use href="#stTree" x="550" y="300"/>
<!-- Vögel + Fische — Atmosphäre -->
<use href="#stBird" x="650" y="70" transform="scale(0.8)"/>
<use href="#stBird" x="680" y="50" transform="scale(0.6)"/>
<use href="#stFish" x="800" y="170"/>
<use href="#stFish" x="760" y="185" transform="scale(0.8) translate(50, 40)"/>
<use href="#stFish" x="220" y="450" transform="scale(1.2) translate(-35, -75)"/>
<!-- Wanderer auf dem Tal-Pfad -->
<g transform="translate(420, 395)">
<rect x="0" y="3" width="16" height="9" rx="3" fill="#795548"/>
<rect x="2" y="11" width="2" height="6" fill="#4E342E"/>
<rect x="12" y="11" width="2" height="6" fill="#4E342E"/>
<circle cx="17" cy="4" r="3.5" fill="#795548"/>
<polygon points="17,1 15,-2 19,-2" fill="#795548"/>
<circle cx="16" cy="9" r="1.5" fill="#FFC107"/>
</g>
<!-- Maschinenhaus (Kavernen-Optik) -->
<rect x="250" y="470" width="100" height="80" rx="10" fill="#BDBDBD"/>
<rect x="255" y="475" width="90" height="70" rx="6" fill="#F5F5F5"/>
<!-- Türen + Symbole — Pumpe links (grün), Turbine rechts (blau) -->
<rect x="290" y="500" width="20" height="35" rx="3" fill="#D32F2F"/>
<polygon class="st-turb-symbol" id="stTurbSymbol"
points="310,517 330,505 330,530" fill="#0288D1"/>
<polygon class="st-pump-symbol" id="stPumpSymbol"
points="290,517 270,505 270,530" fill="#388E3C"/>
<!-- Helle Innenröhre (Hintergrund-Layer für Animation) -->
<polyline points="750,200 750,330 400,530 330,530" fill="none"
stroke="#B3E5FC" stroke-width="10" stroke-linejoin="round"/>
<polyline points="270,530 200,530 200,480" fill="none"
stroke="#B3E5FC" stroke-width="10" stroke-linejoin="round"/>
<!-- Wasser-Animation: zwei Pfade, beide gleichzeitig je nach Modus -->
<polyline class="st-pipe-flow" id="stFlowA"
points="750,200 750,330 400,530 330,530"
fill="none" stroke-linejoin="round"/>
<polyline class="st-pipe-flow" id="stFlowB"
points="270,530 200,530 200,480"
fill="none" stroke-linejoin="round"/>
<!-- Strommast + Stromleitungen, links unten -->
<g stroke="#424242" stroke-width="2" fill="none">
<line x1="50" y1="500" x2="50" y2="430"/>
<polygon points="50,430 40,490 60,490"/>
<line x1="30" y1="445" x2="70" y2="445"/>
<line x1="20" y1="460" x2="80" y2="460"/>
</g>
<path d="M -10,435 Q 20,450 30,445 M 70,445 Q 120,460 250,490"
fill="none" stroke="#757575" stroke-width="1.5"/>
<path d="M -10,450 Q 15,465 20,460 M 80,460 Q 150,475 250,510"
fill="none" stroke="#757575" stroke-width="1.5"/>
<!-- Beschriftungen -->
<text x="780" y="125" font-size="18" fill="#01579B" font-weight="bold"
class="st-cs-label">Oberbecken</text>
<text x="780" y="105" font-size="20" fill="#01579B" font-weight="bold"
class="st-cs-pct" id="stTopPctLabel">30 %</text>
<text x="180" y="395" font-size="18" fill="#01579B" font-weight="bold"
class="st-cs-label">Unterbecken</text>
<text x="180" y="378" font-size="20" fill="#01579B" font-weight="bold"
class="st-cs-pct" id="stBotPctLabel">70 %</text>
<text x="680" y="92" font-size="13" fill="#424242" font-weight="bold"
class="st-cs-label">Staumauer</text>
<text x="100" y="395" font-size="13" fill="#424242" font-weight="bold"
class="st-cs-label">Staumauer</text>
<g transform="rotate(-34, 550, 420)">
<text x="550" y="420" font-size="15" fill="#212121" font-weight="bold"
class="st-cs-label">Fallleitung / Druckstollen</text>
</g>
<text x="300" y="461" font-size="14" fill="#3E2723" font-weight="bold"
text-anchor="middle" class="st-cs-label">Kavernenkraftwerk</text>
<text x="50" y="425" font-size="13" fill="#424242" font-weight="bold"
class="st-cs-label">Netzanbindung</text>
<!-- Pfeil hinunter zum Maschinenhaus, leitet zum Bedien-Footer -->
<text x="300" y="630" font-size="13" fill="#3E2723" font-weight="bold"
text-anchor="middle" class="st-cs-label">↓ Bedienpult Maschinenhaus ↓</text>
</svg>
<!-- Bedien-Footer (HTML, kein foreignObject — iPad-tauglich) -->
<div class="st-machine-control" id="machineControl">
<div class="st-machine-readout" id="readoutControl">aus</div>
<div class="st-bipolar-wrap">
<span class="st-bipolar-end pump-end">⬆ Pumpe</span>
<div class="st-bipolar-slider-area">
<input type="range" class="st-bipolar-slider mode-off"
id="sliderControl"
min="-100" max="100" step="any" value="0"
aria-label="Steuerung — Pumpe (links) bis Turbine (rechts), stufenlos">
<!-- Tick-Skala: 21 Marker je 10 % — längere Striche bei 0 / ±50 / ±100 -->
<div class="st-tick-rail" aria-hidden="true">
<span class="tick t-100"></span><span class="tick"></span>
<span class="tick"></span><span class="tick"></span>
<span class="tick"></span><span class="tick t-mid"></span>
<span class="tick"></span><span class="tick"></span>
<span class="tick"></span><span class="tick"></span>
<span class="tick t-zero"></span>
<span class="tick"></span><span class="tick"></span>
<span class="tick"></span><span class="tick"></span>
<span class="tick t-mid"></span><span class="tick"></span>
<span class="tick"></span><span class="tick"></span>
<span class="tick"></span><span class="tick t-100"></span>
</div>
<div class="st-tick-labels" aria-hidden="true">
<span>100</span><span>50</span><span>0</span><span>50</span><span>100</span>
</div>
</div>
<span class="st-bipolar-end turb-end">Turbine ⬇</span>
</div>
<button type="button" class="st-bipolar-zero" id="btnControlZero"
title="zurück auf 0" aria-label="zurück auf 0"></button>
</div>
</section>
</main>
<!-- Bedarfsgraph (Bottom) -->
<section class="st-graph-zone">
<div class="st-graph-meta">
<div><b id="graphDemandNow">0</b> aktueller Bedarf</div>
<div><b id="graphSupplyNow">0</b> Solar+Wind jetzt</div>
<div class="st-legend-row">
<span class="st-graph-swatch demand"></span> Bedarf
</div>
<div class="st-legend-row">
<span class="st-graph-swatch sun"></span> Solar/Wind (frei)
</div>
<div class="st-legend-row">
<span class="st-graph-swatch supply"></span> Versorgung netto
</div>
<div style="font-size:0.74rem; color: var(--ggs-text-muted); margin-top:6px;">
<b>Solar &gt; Bedarf</b> → Überschuss → pumpen.<br>
<b>Solar &lt; Bedarf</b> → Lücke → turbinieren.
</div>
</div>
<svg class="st-graph-svg" id="graphSvg" viewBox="0 0 1200 240" preserveAspectRatio="none"
aria-label="Strombedarf und Versorgung über 5 Tage"></svg>
</section>
</div>
<!-- Hint-Overlay -->
<div class="st-hint-overlay" id="hintOverlay" role="dialog" aria-modal="true">
<div class="st-hint-modal">
<h2 id="hintTitle">Hinweis</h2>
<p id="hintBody"></p>
<div class="st-hint-modal-actions">
<button class="ggs-btn-primary" id="hintClose">Verstanden</button>
</div>
</div>
</div>
<!-- End-Screen Overlay (nutzt Design-System .ggs-endscreen-overlay/.ggs-endscreen) -->
<div class="ggs-endscreen-overlay" id="endOverlay" role="dialog" aria-modal="true"
style="display:none;">
<div class="ggs-endscreen" id="endCard">
<div class="ggs-endscreen-icon" id="endIcon">🏞️</div>
<div class="ggs-endscreen-title" id="endTitle">Durchgang abgeschlossen</div>
<div class="ggs-endscreen-subtitle" id="endSubtitle">5 Tage Netzbalance bilanziert</div>
<div class="st-endscreen-grade" id="endGrade">B</div>
<div class="ggs-endscreen-body">
<div class="ggs-endscreen-kpi-grid">
<div class="ggs-endscreen-kpi"><div class="n" id="endNetzbalance">100 %</div>
<div class="l">Netzbalance</div></div>
<div class="ggs-endscreen-kpi"><div class="n" id="endVerluste">0 GWh</div>
<div class="l">Verluste</div></div>
<div class="ggs-endscreen-kpi"><div class="n" id="endDeviation">0 GWh</div>
<div class="l" id="endDeviationLabel">Abweichung</div></div>
</div>
<div class="st-endscreen-detail" id="endNarrative"></div>
</div>
<div class="ggs-endscreen-actions">
<button class="ggs-btn-primary" id="endRestart">Neu starten</button>
<a class="ggs-btn-ghost" href="../../schueler">Zum Cockpit</a>
</div>
</div>
</div>
<script>
/* Engine wird absolut geladen — STAUSTUFEN_BASE kommt vom Wrapper bzw.
vom Fallback im Head. So funktioniert die Datei sowohl unter
/geograsim/App/staustufen (Wrapper) als auch bei direktem Aufruf. */
(function () {
var s = document.createElement('script');
s.src = window.STAUSTUFEN_BASE + 'engine.js';
document.head.appendChild(s);
})();
</script>
</body>
</html>