f22c5ebbfe
- PHP/MySQL Backend (XAMPP + Produktionsserver) - Front-Controller, API-Endpunkte, Session-Management - Flussmanagement-Simulation (Echtzeit, Punkt-basierter Fluss) - Stadt & Raumplanung (Prototyp, Top-Down Kachelsystem) - Klimawaechter 3D: Deiche kleiner, Baeume kippen, Budget angepasst - persistence.ts: Dualer Speicher (localStorage + Server-API) - 6 Unit-Test-Dateien fuer bestehende Simulationen Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
240 lines
12 KiB
HTML
240 lines
12 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>GeoGraSim — Energiewende-Planer*in</title>
|
|
<link rel="icon" type="image/png" href="assets/img/logo.png">
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
|
|
<style>
|
|
*, *::before, *::after { margin:0; padding:0; box-sizing:border-box; }
|
|
:root {
|
|
--bg: #f7f6f3; --bg2: #efeee9; --white: #fff;
|
|
--text: #1a1a1a; --text2: #4a4a4a; --text3: #8a8a8a;
|
|
--fjord: #4a7c8a; --fjord-l: #dae8ec; --fjord-d: #3a6470;
|
|
--moss: #5a8a5e; --moss-l: #dceadd;
|
|
--sand: #c4a35a; --sand-l: #f2eacc;
|
|
--coral: #c07a6b; --coral-l: #f2ddd8;
|
|
--danger: #b04a3a;
|
|
}
|
|
body { font-family:'Inter',system-ui,sans-serif; background:var(--bg); color:var(--text); line-height:1.5; }
|
|
|
|
.nav { padding:.6rem 1.2rem; display:flex; justify-content:space-between; align-items:center; border-bottom:1px solid rgba(0,0,0,.05); background:var(--white); }
|
|
.nav-brand { display:flex; align-items:center; gap:.5rem; font-weight:800; font-size:1rem; color:var(--fjord); text-decoration:none; }
|
|
.nav-brand img { width:28px; height:28px; border-radius:6px; }
|
|
.nav-actions { display:flex; gap:.4rem; }
|
|
.nav-btn { padding:.4rem .8rem; border:1px solid rgba(0,0,0,.08); border-radius:8px; background:var(--white); color:var(--text2); font-size:.78rem; font-weight:600; cursor:pointer; text-decoration:none; }
|
|
.nav-btn:hover { background:var(--fjord-l); border-color:var(--fjord); color:var(--fjord); }
|
|
|
|
.game-layout { display:grid; grid-template-columns:280px 1fr 300px; gap:.8rem; padding:.8rem; max-width:1400px; margin:0 auto; align-items:start; }
|
|
|
|
.panel { background:var(--white); border-radius:12px; border:1px solid rgba(0,0,0,.05); padding:.9rem; box-shadow:0 2px 8px rgba(0,0,0,.03); }
|
|
.panel-h { font-size:.66rem; font-weight:700; color:var(--fjord); text-transform:uppercase; letter-spacing:.06em; margin-bottom:.5rem; }
|
|
|
|
.left { display:flex; flex-direction:column; gap:.8rem; }
|
|
.resource { display:flex; align-items:center; gap:.6rem; padding:.4rem .2rem; border-bottom:1px solid rgba(0,0,0,.04); }
|
|
.resource:last-child { border-bottom:none; }
|
|
.resource-icon { font-size:1.4rem; width:28px; text-align:center; }
|
|
.resource-info { flex:1; min-width:0; }
|
|
.resource-name { font-size:.7rem; color:var(--text3); font-weight:500; }
|
|
.resource-val { font-size:.95rem; font-weight:700; color:var(--text); }
|
|
|
|
.goals .goal { padding:.4rem 0; border-bottom:1px solid rgba(0,0,0,.04); }
|
|
.goals .goal:last-child { border:0; }
|
|
.goal-row { display:flex; align-items:center; gap:.4rem; font-size:.78rem; }
|
|
.goal-check { width:14px; height:14px; border:1.5px solid var(--text3); border-radius:3px; display:flex; align-items:center; justify-content:center; font-size:.6rem; flex-shrink:0; }
|
|
.goal-check.done { background:var(--moss); border-color:var(--moss); color:#fff; }
|
|
.goal-bar { height:3px; background:#e8e5dc; border-radius:2px; margin-top:.3rem; overflow:hidden; }
|
|
.goal-bar-fill { height:100%; background:var(--moss); transition:width .3s; }
|
|
|
|
.center { display:flex; flex-direction:column; gap:.6rem; }
|
|
.canvas-wrap { background:var(--white); border-radius:14px; border:1px solid rgba(0,0,0,.05); overflow:hidden; box-shadow:0 4px 16px rgba(0,0,0,.05); position:relative; min-height:440px; }
|
|
|
|
.tutorial-overlay { position:absolute; inset:0; background:rgba(15,30,40,.85); backdrop-filter:blur(8px); display:flex; align-items:center; justify-content:center; z-index:10; padding:1.5rem; }
|
|
.tutorial-card { background:var(--white); border-radius:16px; padding:1.5rem 1.8rem; max-width:500px; box-shadow:0 20px 60px rgba(0,0,0,.3); }
|
|
.tutorial-card h3 { font-size:1.2rem; font-weight:800; margin-bottom:.6rem; color:var(--fjord); }
|
|
.tutorial-card p { font-size:.88rem; color:var(--text2); white-space:pre-line; margin-bottom:1rem; }
|
|
.tutorial-card .progress-dots { display:flex; gap:.3rem; justify-content:center; margin:.8rem 0; }
|
|
.dot { width:8px; height:8px; border-radius:50%; background:#dadad0; }
|
|
.dot.active { background:var(--fjord); }
|
|
.tut-btn { width:100%; padding:.7rem; background:var(--fjord); color:#fff; border:none; border-radius:10px; font-weight:700; font-size:.9rem; cursor:pointer; }
|
|
.tut-btn:hover { background:var(--fjord-d); }
|
|
|
|
.speed-bar { display:flex; gap:.3rem; padding:.5rem; background:var(--white); border-radius:10px; border:1px solid rgba(0,0,0,.05); }
|
|
.speed-btn { flex:1; padding:.5rem; border:none; background:transparent; color:var(--text2); font-size:.8rem; font-weight:600; cursor:pointer; border-radius:6px; }
|
|
.speed-btn:hover { background:#f0eeea; }
|
|
.speed-btn.active { background:var(--fjord); color:#fff; }
|
|
|
|
.graphs { display:grid; grid-template-columns:1fr 1fr; gap:.6rem; }
|
|
.graph-card { background:var(--white); border-radius:10px; border:1px solid rgba(0,0,0,.05); padding:.6rem; }
|
|
.graph-title { font-size:.65rem; color:var(--text3); font-weight:600; text-transform:uppercase; letter-spacing:.06em; margin-bottom:.2rem; }
|
|
.graph-svg { width:100%; height:50px; }
|
|
|
|
.right { display:flex; flex-direction:column; gap:.8rem; }
|
|
.measure-card { display:flex; gap:.6rem; padding:.6rem; border-radius:10px; border:1px solid rgba(0,0,0,.06); margin-bottom:.4rem; cursor:pointer; transition:all .2s; background:var(--white); }
|
|
.measure-card:hover { border-color:var(--fjord); background:var(--fjord-l); }
|
|
.measure-card.disabled { opacity:.5; cursor:not-allowed; }
|
|
.measure-card.disabled:hover { border-color:rgba(0,0,0,.06); background:var(--white); }
|
|
.measure-icon { font-size:1.5rem; width:32px; text-align:center; }
|
|
.measure-info { flex:1; min-width:0; }
|
|
.measure-name { font-size:.8rem; font-weight:700; }
|
|
.measure-desc { font-size:.66rem; color:var(--text3); line-height:1.3; }
|
|
.measure-stats { display:flex; gap:.3rem; flex-wrap:wrap; margin-top:.2rem; }
|
|
.measure-stat { font-size:.6rem; padding:1px 5px; border-radius:4px; background:#f0eeea; color:var(--text3); font-weight:600; }
|
|
.measure-cost { background:var(--sand-l); color:var(--sand); }
|
|
.measure-reduction { background:var(--moss-l); color:var(--moss); }
|
|
.measure-protection { background:var(--fjord-l); color:var(--fjord); }
|
|
.measure-capacity { background:#e8e3f2; color:#7a5ea8; }
|
|
.measure-quality { background:var(--coral-l); color:var(--danger); }
|
|
.measure-neutral { background:#f0eeea; color:var(--text3); }
|
|
|
|
.events-list { max-height:180px; overflow-y:auto; }
|
|
.event { padding:.4rem .5rem; border-radius:8px; margin-bottom:.3rem; font-size:.74rem; line-height:1.3; }
|
|
.event-info { background:#f0eeea; color:var(--text2); }
|
|
.event-success { background:var(--moss-l); color:var(--moss); }
|
|
.event-warning { background:var(--sand-l); color:var(--sand); }
|
|
.event-danger { background:var(--coral-l); color:var(--danger); }
|
|
|
|
.end-screen { position:fixed; inset:0; background:rgba(15,30,40,.9); backdrop-filter:blur(10px); display:flex; align-items:center; justify-content:center; z-index:100; padding:2rem; }
|
|
.end-card { background:var(--white); border-radius:20px; padding:2rem 2.5rem; max-width:500px; text-align:center; box-shadow:0 30px 80px rgba(0,0,0,.4); }
|
|
.end-icon { font-size:4rem; margin-bottom:.5rem; }
|
|
.end-card h2 { font-size:1.8rem; font-weight:900; letter-spacing:-.02em; margin-bottom:.3rem; }
|
|
.end-card .end-summary { color:var(--text2); margin-bottom:1rem; }
|
|
.end-stats { display:grid; grid-template-columns:1fr 1fr; gap:.8rem; margin:1rem 0; }
|
|
.end-stat { background:#f7f6f3; border-radius:10px; padding:.8rem; }
|
|
.end-stat-val { font-size:1.4rem; font-weight:800; color:var(--fjord); }
|
|
.end-stat-lbl { font-size:.7rem; color:var(--text3); text-transform:uppercase; }
|
|
|
|
@media (max-width: 1100px) {
|
|
.game-layout { grid-template-columns:1fr; }
|
|
.left, .right { order:2; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<nav class="nav">
|
|
<a href="index.html" class="nav-brand"><img src="assets/img/logo.png" alt="">GeoGraSim · Energiewende</a>
|
|
<div class="nav-actions">
|
|
<button class="nav-btn" id="btn-save">💾 Speichern</button>
|
|
<button class="nav-btn" id="btn-load">📂 Laden</button>
|
|
<button class="nav-btn" id="btn-finish">⏹ Beenden</button>
|
|
<button class="nav-btn" id="btn-reset" title="Stand löschen und neu starten">🔄 Reset</button>
|
|
<a href="index.html" class="nav-btn">← Zurück</a>
|
|
</div>
|
|
</nav>
|
|
|
|
<div class="game-layout">
|
|
|
|
<aside class="left">
|
|
<div class="panel">
|
|
<div class="panel-h">📊 Status</div>
|
|
<div id="resources"></div>
|
|
</div>
|
|
<div class="panel goals">
|
|
<div class="panel-h">🎯 Ziele</div>
|
|
<div id="goals"></div>
|
|
</div>
|
|
</aside>
|
|
|
|
<main class="center">
|
|
<div class="canvas-wrap" id="canvas-wrap">
|
|
<div class="tutorial-overlay" id="tutorial">
|
|
<div class="tutorial-card">
|
|
<h3 id="tut-title">…</h3>
|
|
<p id="tut-text">…</p>
|
|
<div class="progress-dots" id="tut-dots"></div>
|
|
<button class="tut-btn" id="tut-next">Weiter →</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="speed-bar">
|
|
<button class="speed-btn" data-speed="0">⏸</button>
|
|
<button class="speed-btn active" data-speed="1">▶ Normal</button>
|
|
<button class="speed-btn" data-speed="2">⏩ Schnell</button>
|
|
<button class="speed-btn" data-speed="4">⏭ Sehr schnell</button>
|
|
</div>
|
|
|
|
<div class="graphs">
|
|
<div class="graph-card">
|
|
<div class="graph-title">🌱 Erneuerbar-Anteil</div>
|
|
<svg class="graph-svg" id="graph-renewable"></svg>
|
|
</div>
|
|
<div class="graph-card">
|
|
<div class="graph-title">🌫 CO₂-Ausstoß</div>
|
|
<svg class="graph-svg" id="graph-co2"></svg>
|
|
</div>
|
|
<div class="graph-card">
|
|
<div class="graph-title">⚡ Erzeugung vs. Bedarf</div>
|
|
<svg class="graph-svg" id="graph-supply"></svg>
|
|
</div>
|
|
<div class="graph-card">
|
|
<div class="graph-title">💰 Budget</div>
|
|
<svg class="graph-svg" id="graph-budget"></svg>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<aside class="right">
|
|
<div class="panel">
|
|
<div class="panel-h">⚡ Kraftwerke bauen</div>
|
|
<div id="measures"></div>
|
|
</div>
|
|
<div class="panel">
|
|
<div class="panel-h">📰 Ereignisse</div>
|
|
<div class="events-list" id="events"></div>
|
|
</div>
|
|
</aside>
|
|
|
|
</div>
|
|
|
|
<div id="end-screen-host"></div>
|
|
|
|
<script type="module">
|
|
import { EnergiemixGame, PLANT_TYPES } from './src/sims/sim-08-energiemix/game.ts'
|
|
import { EnergiemixRenderer } from './src/sims/sim-08-energiemix/game-renderer.ts'
|
|
import { GameUI } from './src/ui/game-ui.ts'
|
|
|
|
const game = new EnergiemixGame()
|
|
|
|
const shopItems = PLANT_TYPES.map(p => {
|
|
const badges = []
|
|
if (p.capacityMW > 0) {
|
|
badges.push({ label: `⚡ ${p.capacityMW} MW`, type: 'capacity' })
|
|
badges.push({ label: `CF ${(p.capacityFactor * 100).toFixed(0)}%`, type: 'neutral' })
|
|
}
|
|
if (p.renewable) badges.push({ label: '🌱 erneuerbar', type: 'reduction' })
|
|
if (p.co2PerKWh > 300) badges.push({ label: `${p.co2PerKWh} g CO₂/kWh`, type: 'quality' })
|
|
else if (p.co2PerKWh > 0 && p.capacityMW > 0) badges.push({ label: `${p.co2PerKWh} g CO₂/kWh`, type: 'neutral' })
|
|
if (p.flexible && p.capacityMW > 0) badges.push({ label: '🔄 flexibel', type: 'protection' })
|
|
if (p.storage) badges.push({ label: `🔋 ${p.storage} MW Speicher`, type: 'protection' })
|
|
return {
|
|
id: p.id,
|
|
name: p.name,
|
|
emoji: p.emoji,
|
|
description: p.description,
|
|
cost: p.cost,
|
|
upkeep: p.upkeep,
|
|
badges,
|
|
}
|
|
})
|
|
|
|
new GameUI({
|
|
game,
|
|
Renderer: EnergiemixRenderer,
|
|
shopItems,
|
|
onBuy: (id) => game.buyPlant(id),
|
|
graphs: [
|
|
{ id: 'graph-renewable', title: 'Erneuerbar', field: 'renewable', yMin: 0, yMax: 100, color: '#5a8a5e', format: v => `${v.toFixed(0)}%` },
|
|
{ id: 'graph-co2', title: 'CO₂', field: 'co2', yMin: 0, yMax: 5000, color: '#8a7868' },
|
|
{ id: 'graph-supply', title: 'Erzeugung', field: 'supply', yMin: 0, yMax: 3000, color: '#4a7c8a' },
|
|
{ id: 'graph-budget', title: 'Budget', field: 'budget', yMin: -300, yMax: 2000, color: '#c4a35a' },
|
|
],
|
|
saveKey: 'energiemix-save',
|
|
yearOffset: 2025,
|
|
})
|
|
</script>
|
|
</body>
|
|
</html>
|