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>
289 lines
14 KiB
HTML
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>
|