Files
geograsim/App/sim.html
T
Adminator f22c5ebbfe Stand 2026-04-13: PHP/MySQL Infrastruktur, Flussmanagement, Stadt-Prototyp
- 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>
2026-04-13 16:43:42 +02:00

289 lines
14 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 — Simulation</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; --white: #fff;
--text: #1a1a1a; --text2: #4a4a4a; --text3: #8a8a8a;
--fjord: #4a7c8a; --fjord-l: #dae8ec; --fjord-d: #3a6470;
--moss: #5a8a5e; --moss-l: #dceadd;
--sand: #c4a35a; --coral: #c07a6b; --coral-l: #f2ddd8;
}
body { font-family:'Inter',system-ui,sans-serif; background:var(--bg); color:var(--text); }
.nav { padding:.7rem 2rem; display:flex; justify-content:space-between; align-items:center; border-bottom:1px solid rgba(0,0,0,.04); 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-back { color:var(--text2); text-decoration:none; font-size:.82rem; font-weight:500; padding:.4rem .8rem; border-radius:8px; border:1px solid rgba(0,0,0,.08); }
.nav-back:hover { background:var(--fjord-l); }
.shell { max-width:1100px; margin:1.5rem auto; padding:0 1.5rem; }
.shell-header { margin-bottom:1rem; }
.shell-header h1 { font-size:1.6rem; font-weight:800; letter-spacing:-.02em; }
.shell-meta { display:flex; gap:.4rem; flex-wrap:wrap; margin-top:.3rem; }
.tag { font-size:.68rem; font-weight:600; padding:2px 9px; border-radius:6px; }
.tag-kl { background:var(--fjord-l); color:var(--fjord); }
.tag-time { background:#f0eeea; color:var(--text3); }
.tag-typ { background:var(--moss-l); color:var(--moss); }
.poe { display:flex; gap:.3rem; margin-bottom:1.2rem; }
.poe-step { flex:1; padding:.5rem .3rem; border-radius:8px; text-align:center; font-size:.7rem; font-weight:600; background:#f0eeea; color:var(--text3); transition:all .3s; }
.poe-step.active { background:var(--fjord); color:#fff; box-shadow:0 2px 8px rgba(74,124,138,.25); }
.poe-step.done { background:var(--moss-l); color:var(--moss); }
.sim-layout { display:grid; grid-template-columns:1fr 270px; gap:1rem; }
.sim-main { display:flex; flex-direction:column; gap:1rem; }
.sim-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); }
.sim-sidebar { display:flex; flex-direction:column; gap:.8rem; }
.panel { background:var(--white); border-radius:14px; border:1px solid rgba(0,0,0,.05); padding:1.1rem; box-shadow:0 2px 10px rgba(0,0,0,.03); }
.panel h4 { font-size:.72rem; font-weight:700; color:var(--fjord); text-transform:uppercase; letter-spacing:.06em; margin-bottom:.6rem; }
.panel p { font-size:.85rem; line-height:1.55; }
.ctrl { margin-bottom:.7rem; }
.ctrl label { display:flex; justify-content:space-between; font-size:.78rem; font-weight:500; margin-bottom:.15rem; }
.ctrl label .val { color:var(--fjord); font-weight:700; }
.ctrl input[type=range] { width:100%; -webkit-appearance:none; height:5px; border-radius:3px; background:#e8e8e4; outline:none; }
.ctrl input[type=range]::-webkit-slider-thumb { -webkit-appearance:none; width:18px; height:18px; border-radius:50%; background:var(--fjord); cursor:pointer; box-shadow:0 1px 5px rgba(0,0,0,.15); }
.lz h4 { color:var(--moss); }
.lz ul { font-size:.75rem; color:var(--text2); padding-left:1rem; }
.lz li { margin-bottom:.15rem; }
.predict-panel { background:var(--fjord-l); border:1px solid rgba(74,124,138,.15); }
.predict-panel h4 { color:var(--fjord-d); }
.predict-panel textarea, .reflect-panel textarea {
width:100%; border:1px solid rgba(0,0,0,.08); border-radius:8px;
padding:.5rem; font-family:inherit; font-size:.8rem; resize:vertical; min-height:60px;
margin-top:.3rem;
}
.phase-btn { width:100%; padding:.7rem; border:none; border-radius:10px; font-weight:700; font-size:.85rem; cursor:pointer; background:var(--fjord); color:#fff; transition:all .2s; }
.phase-btn:hover { background:var(--fjord-d); transform:translateY(-1px); }
.phase-btn:disabled { background:var(--moss); cursor:default; }
@media (max-width:780px) {
.sim-layout { grid-template-columns:1fr; }
}
</style>
</head>
<body>
<nav class="nav">
<a href="index.html" class="nav-brand"><img src="assets/img/logo.png" alt="">GeoGraSim</a>
<a href="index.html" class="nav-back">← Zurück</a>
</nav>
<div class="shell">
<div class="shell-header">
<h1 id="sim-title"></h1>
<div class="shell-meta">
<span class="tag tag-kl" id="sim-klasse"></span>
<span class="tag tag-time" id="sim-zeit"></span>
<span class="tag tag-typ" id="sim-typ"></span>
</div>
</div>
<div class="poe" id="poe">
<div class="poe-step active">📖 Intro</div>
<div class="poe-step">🤔 Vorhersage</div>
<div class="poe-step">🔬 Simulation</div>
<div class="poe-step">👁️ Beobachtung</div>
<div class="poe-step">💭 Reflexion</div>
</div>
<div class="sim-layout">
<div class="sim-main">
<div class="panel" id="intro-panel"></div>
<div class="panel predict-panel" id="predict-panel" style="display:none">
<h4>🤔 Deine Vorhersage</h4>
<p id="predict-question"></p>
<textarea id="predict-input" placeholder="Schreibe deine Vermutung hier..."></textarea>
</div>
<div class="sim-canvas-wrap" id="canvas-wrap" style="display:none"></div>
<div class="panel" id="reflect-panel" style="display:none">
<h4>💭 Reflexion</h4>
<p id="reflect-question"></p>
<textarea id="reflect-input" placeholder="Was hast du gelernt?"></textarea>
<p id="reflect-tip" style="font-size:.78rem;color:var(--text3);margin-top:.4rem"></p>
</div>
</div>
<div class="sim-sidebar">
<div class="panel" id="controls-panel">
<h4>⚙️ Parameter</h4>
<div id="controls-host"></div>
</div>
<div class="panel lz">
<h4>🎯 Lernziele</h4>
<ul id="lernziele-list"></ul>
</div>
<button class="phase-btn" id="phase-btn">Weiter → Vorhersage</button>
</div>
</div>
</div>
<script type="module">
import { TreibhausSimulation } from './src/sims/sim-05-treibhaus/logic.ts'
import { TreibhausRenderer } from './src/sims/sim-05-treibhaus/renderer.ts'
import { ErdbebenSimulation } from './src/sims/sim-07-erdbeben/logic.ts'
import { ErdbebenRenderer } from './src/sims/sim-07-erdbeben/renderer.ts'
import { EnergiemixSimulation } from './src/sims/sim-09-energiemix/logic.ts'
import { EnergiemixRenderer } from './src/sims/sim-09-energiemix/renderer.ts'
import { AUSTRIA, getLocalName } from './src/core/education-levels.ts'
// Sim registry
const SIMS = {
'05': {
SimClass: TreibhausSimulation,
RendererClass: TreibhausRenderer,
intro: `<h4>📖 Einführung</h4>
<p>Die Erde wird von der Sonne erwärmt. Ohne die Atmosphäre wäre es hier <strong>-18°C</strong> kalt — viel zu kalt für Leben.</p>
<p>Bestimmte Gase in der Atmosphäre — vor allem <strong>CO₂</strong> — fangen Wärmestrahlung ab und halten die Erde warm. Das nennt man den <strong>Treibhauseffekt</strong>.</p>
<p style="color:var(--fjord);font-weight:600">Aber was passiert, wenn sich die CO₂-Menge verändert? Finde es heraus!</p>`,
predictQ: 'Was denkst du: Wie warm wäre die Erde, wenn es <strong>doppelt so viel CO₂</strong> gäbe wie heute?',
reflectQ: 'Vergleiche das Ergebnis mit deiner Vorhersage. Was hat dich überrascht?',
reflectTip: '💡 Viele Menschen verwechseln den Treibhauseffekt mit dem Ozonloch — das sind aber zwei ganz verschiedene Dinge!',
},
'07': {
SimClass: ErdbebenSimulation,
RendererClass: ErdbebenRenderer,
intro: `<h4>📖 Einführung</h4>
<p>Die Erdkruste besteht aus <strong>tektonischen Platten</strong>, die sich langsam gegeneinander bewegen — nur wenige Zentimeter pro Jahr.</p>
<p>Wenn sie sich verhaken, baut sich <strong>Spannung</strong> auf. Wird die Spannung zu groß, ruckt es plötzlich los: ein <strong>Erdbeben</strong> entsteht.</p>
<p>Aber: Ein Beben gleicher Stärke kann sehr <strong>unterschiedliche Folgen</strong> haben — je nachdem, wie gut die Häuser gebaut sind.</p>
<p style="color:var(--fjord);font-weight:600">Beobachte zwei Städte: Stadt A ist wohlhabend, Stadt B nicht. Was passiert?</p>`,
predictQ: 'Was denkst du: Wenn ein Erdbeben der Stärke 7 in beiden Städten gleich stark ankommt — gibt es einen Unterschied bei den Schäden?',
reflectQ: 'Was bestimmt, wie schlimm ein Erdbeben für eine Region wird?',
reflectTip: '💡 Naturgefahren werden oft "natürlich" genannt — aber der Schaden ist meistens vom Menschen gemacht: durch Bauweise, Vorbereitung und Reichtum.',
},
'09': {
SimClass: EnergiemixSimulation,
RendererClass: EnergiemixRenderer,
intro: `<h4>📖 Einführung</h4>
<p>Strom muss irgendwo herkommen. Es gibt viele Möglichkeiten: Kohle, Gas, Atomkraft, Wind, Sonne, Wasser.</p>
<p>Jede Quelle hat <strong>Vor- und Nachteile</strong>: Manche sind billig, aber dreckig. Andere sauber, aber unzuverlässig oder teuer.</p>
<p style="color:var(--fjord);font-weight:600">Stelle deinen eigenen Energiemix zusammen. Schaffst du es, die drei Ziele in Einklang zu bringen?</p>`,
predictQ: 'Was denkst du: Welcher Mix wäre am besten — möglichst viel Erneuerbare? Oder doch ein Mix mit Kernenergie?',
reflectQ: 'Welche Trade-offs hast du erlebt? Gibt es einen "perfekten" Energiemix?',
reflectTip: '💡 Es gibt keine perfekte Lösung — Energiepolitik ist immer eine Abwägung zwischen Klima, Kosten und Versorgungssicherheit.',
},
}
// Get sim ID from URL
const params = new URLSearchParams(location.search)
const simId = params.get('id') || '05'
const config = SIMS[simId] || SIMS['05']
// Create simulation
const sim = new config.SimClass()
let renderer = null
// Populate header
document.getElementById('sim-title').textContent = sim.meta.name
document.getElementById('sim-klasse').textContent = getLocalName(AUSTRIA, sim.meta.primaryLevel) || `Stufe ${sim.meta.primaryLevel}`
document.getElementById('sim-zeit').textContent = `⏱ ${sim.meta.dpiMinuten} min`
document.getElementById('sim-typ').textContent = sim.meta.typ
// Populate intro
document.getElementById('intro-panel').innerHTML = config.intro
document.getElementById('predict-question').innerHTML = config.predictQ
document.getElementById('reflect-question').innerHTML = config.reflectQ
document.getElementById('reflect-tip').innerHTML = config.reflectTip
// Populate lernziele
const lzList = document.getElementById('lernziele-list')
sim.meta.lernziele.forEach(l => {
const li = document.createElement('li')
li.textContent = l
lzList.appendChild(li)
})
// Populate controls
const controlsHost = document.getElementById('controls-host')
const ranges = sim.getVariableRanges()
for (const [key, range] of Object.entries(ranges)) {
const div = document.createElement('div')
div.className = 'ctrl'
const isFloat = range.max <= 10 && range.max - range.min < 5
const step = isFloat ? 0.01 : 1
div.innerHTML = `
<label>${range.label} <span class="val">${range.default}${range.unit ? ' ' + range.unit : ''}</span></label>
<input type="range" min="${range.min}" max="${range.max}" value="${range.default}" step="${step}">
`
const input = div.querySelector('input')
const valSpan = div.querySelector('.val')
input.addEventListener('input', () => {
const v = parseFloat(input.value)
sim.setVariable(key, v)
valSpan.textContent = `${isFloat ? v.toFixed(2) : v}${range.unit ? ' ' + range.unit : ''}`
})
controlsHost.appendChild(div)
}
// Phase navigation
const phases = ['intro', 'predict', 'simulate', 'observe', 'reflect', 'complete']
let phaseIdx = 0
const btnLabels = ['Weiter → Vorhersage', 'Weiter → Simulation', 'Weiter → Beobachtung', 'Weiter → Reflexion', 'Abschließen']
const phaseBtn = document.getElementById('phase-btn')
const poeSteps = document.querySelectorAll('.poe-step')
phaseBtn.addEventListener('click', () => {
if (phases[phaseIdx] === 'predict') {
sim.setPrediction('free_text', document.getElementById('predict-input').value)
}
if (phases[phaseIdx] === 'reflect') {
sim.addReflection(document.getElementById('reflect-input').value)
}
phaseIdx++
if (phaseIdx >= phases.length - 1) {
phaseBtn.textContent = '✓ Fertig!'
phaseBtn.disabled = true
console.log('Assessment-Daten:', sim.getAssessmentData())
} else {
phaseBtn.textContent = btnLabels[phaseIdx] || 'Weiter'
}
sim.nextPhase()
updateUI()
})
function updateUI() {
const current = phases[phaseIdx]
poeSteps.forEach((el, i) => {
el.classList.remove('active', 'done')
if (i === phaseIdx) el.classList.add('active')
else if (i < phaseIdx) el.classList.add('done')
})
document.getElementById('intro-panel').style.display = current === 'intro' ? '' : 'none'
document.getElementById('predict-panel').style.display = current === 'predict' ? '' : 'none'
document.getElementById('reflect-panel').style.display = (current === 'reflect' || current === 'complete') ? '' : 'none'
const canvasWrap = document.getElementById('canvas-wrap')
const showCanvas = ['simulate', 'observe', 'reflect', 'complete'].includes(current)
canvasWrap.style.display = showCanvas ? '' : 'none'
if (showCanvas && !renderer) {
renderer = new config.RendererClass(canvasWrap, sim)
renderer.start()
}
}
updateUI()
</script>
</body>
</html>