Files
geograsim/App/assets/js/glossar-experiments.js
T
Adminator 22f5e9b686 Glossar: 3 weitere Experimente + Watt-Leiter-Infografik
- Treibhauseffekt-Slider (CO2-ppm 200..800 -> Temperatur-Anomalie via IPCC-Faustformel, Moleküle füllen Atmosphäre dynamisch, Thermometer visualisiert ΔT)
- Windrad-Höhenslider (0..260 m, 7 Vergleichsobjekte Mensch..modernes Windrad leuchten je nach Schieberstellung auf)
- Methan-GWP-Waage (CO2-Schale variabel 1..60 kg, Waage kippt physikalisch bis bei 28 kg Balance erreicht ist)
- kWh-Rennen: Fahrzeuge fahren jetzt von rechts nach links (plattformunabhängige Emoji-Orientierung), Animation auf 6,5s verlangsamt
- Watt-Leiter: neue gemeinsame Infografik (log-Skala 1 W..1 GW mit 6 Haushalts- und Kraftwerks-Beispielen), verknüpft mit watt/kilowatt/megawatt
- CSS für Slider-Experimente (.ge-gh-*), Waagen-Animation, Windrad-Highlighting

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 00:52:39 +02:00

660 lines
34 KiB
JavaScript
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.
/**
* Glossar-Experimente — kleine interaktive Widgets im Detail-Modal.
*
* Registrierung: window.GlossarExperiments[key_slug] = function (root) { … }
* Der Renderer (glossar.php) hängt einen leeren Container ein und ruft die
* Funktion mit diesem Container als einzigem Argument auf. Das Widget baut
* selbst seinen DOM und bindet seine Events.
*
* Richtlinien:
* - Keine externen Libraries.
* - Mobile-/Tap-freundlich: grosse Buttons, keine Hover-Abhängigkeit.
* - Zahlen quellenbar, kurze Begründung in der UI sichtbar.
*/
(function () {
'use strict';
const NS = 'http://www.w3.org/2000/svg';
const svgEl = (tag, attrs, parent) => {
const el = document.createElementNS(NS, tag);
if (attrs) for (const k in attrs) el.setAttribute(k, attrs[k]);
if (parent) parent.appendChild(el);
return el;
};
// =====================================================================
// Experiment: kWh-Rennen — wie weit kommt man mit 1 Kilowattstunde?
// =====================================================================
function kwhRace(root) {
root.innerHTML = `
<div class="ge-wrap">
<div class="ge-head">
<h3>🏁 Das kWh-Rennen</h3>
<p>Drei Fahrzeuge starten rechts mit <strong>derselben Energie</strong>: 1 Kilowattstunde. Wie weit kommen sie nach links?</p>
</div>
<svg class="ge-track" viewBox="0 0 600 320" preserveAspectRatio="xMidYMid meet" aria-hidden="true"></svg>
<div class="ge-controls">
<button class="ge-btn ge-btn-primary" data-act="start">▶ Start</button>
<button class="ge-btn" data-act="reset">↺ Zurück</button>
</div>
<ul class="ge-legend">
<li><span class="ge-dot" style="background:#5a8a5e"></span>E-Fahrrad · <strong>10 Wh/km</strong> → ca. 100 km</li>
<li><span class="ge-dot" style="background:#4a7c8a"></span>E-Auto · <strong>150 Wh/km</strong> → ca. 6,5 km</li>
<li><span class="ge-dot" style="background:#c85c4a"></span>Benzin-Auto · <strong>530 Wh/km</strong> aus Kraftstoffenergie → ca. 1,9 km</li>
</ul>
<p class="ge-note">Quellen: ADAC Stromverbrauchstests E-Auto · Hersteller-Angaben E-Bike Bosch Performance · WLTP Benziner 6 L/100 km × 8,8 kWh/L. Der Benziner verliert rund 70 % der Kraftstoffenergie als Motor-Wärme — deshalb kommt er weniger weit.</p>
</div>
`;
const svg = root.querySelector('.ge-track');
// Emoji-Fahrzeuge: auf Windows/Chrome schauen 🚴 🚗 🚙 nach links → sie fahren
// von rechts nach links, Emojis passen zur Fahrtrichtung ohne Plattform-Trick.
const vehicles = [
{ label: 'E-Fahrrad', emoji: '🚴', km: 100, color: '#5a8a5e', y: 100 },
{ label: 'E-Auto', emoji: '🚗', km: 6.5, color: '#4a7c8a', y: 190 },
{ label: 'Benzin-Auto', emoji: '🚙', km: 1.9, color: '#c85c4a', y: 280 }
];
const MAX_KM = 100;
const TRACK_L = 50, TRACK_R = 560;
const trackLen = TRACK_R - TRACK_L;
// Skala oben — Start rechts (0 km), Ziel links (100 km)
const axis = svgEl('g', { class: 'ge-axis' }, svg);
svgEl('line', { x1: TRACK_L, y1: 52, x2: TRACK_R, y2: 52, stroke: '#1f4e5a', 'stroke-width': '1.2' }, axis);
[0, 25, 50, 75, 100].forEach(km => {
const x = TRACK_R - (km / MAX_KM) * trackLen;
svgEl('line', { x1: x, y1: 48, x2: x, y2: 56, stroke: '#1f4e5a', 'stroke-width': '1' }, axis);
const t = svgEl('text', { x, y: 40, 'text-anchor': 'middle', 'font-size': '11', fill: '#4a4a4a', 'font-weight': '600' }, axis);
t.textContent = km + ' km';
});
// „Start" Marker ganz rechts
const startTxt = svgEl('text', { x: TRACK_R, y: 20, 'text-anchor': 'end', 'font-size': '11', fill: '#1f4e5a', 'font-weight': '700' }, svg);
startTxt.textContent = '← Start mit 1 kWh';
// Spuren + Fahrzeuge
const nodes = vehicles.map(v => {
const g = svgEl('g', { class: 'ge-lane' }, svg);
// Label ÜBER der Spur, rechts beim Start
const label = svgEl('text', { x: TRACK_R, y: v.y - 22, 'text-anchor': 'end', 'font-size': '12', 'font-weight': '800', fill: v.color }, g);
label.textContent = v.label;
// Straßenlinie
svgEl('line', { x1: TRACK_L, y1: v.y, x2: TRACK_R, y2: v.y, stroke: '#dae8ec', 'stroke-width': '5', 'stroke-linecap': 'round' }, g);
// Ziel-Markierung (links vom Start)
const xEnd = TRACK_R - (v.km / MAX_KM) * trackLen;
svgEl('line', { x1: xEnd, y1: v.y - 16, x2: xEnd, y2: v.y + 16, stroke: v.color, 'stroke-width': '2', 'stroke-dasharray': '3 2', opacity: '.55' }, g);
// Emoji-Fahrzeug
const icon = svgEl('text', { x: TRACK_R, y: v.y + 8, 'text-anchor': 'middle', 'font-size': '30' }, g);
icon.textContent = v.emoji;
// Ergebnis-Label LINKS vom Ziel (in Fahrtrichtung weiter)
const result = svgEl('text', { x: xEnd - 10, y: v.y + 4, 'text-anchor': 'end', 'font-size': '12', 'font-weight': '800', fill: v.color, opacity: '0' }, g);
result.textContent = v.km.toFixed(1).replace('.', ',') + ' km';
return { v, icon, result, xStart: TRACK_R, xEnd };
});
// Animation
let animId = null;
function setX(n, x) { n.icon.setAttribute('x', x); }
function animate() {
cancelAnimationFrame(animId);
const dur = 6500; // ms — bewusst langsam
const start = performance.now();
nodes.forEach(n => { setX(n, n.xStart); n.result.setAttribute('opacity', '0'); });
function tick(now) {
const t = Math.min(1, (now - start) / dur);
const e = 1 - Math.pow(1 - t, 3);
nodes.forEach(n => {
const x = n.xStart + (n.xEnd - n.xStart) * e;
setX(n, x);
if (t > 0.96) n.result.setAttribute('opacity', '1');
});
if (t < 1) animId = requestAnimationFrame(tick);
}
animId = requestAnimationFrame(tick);
}
function reset() {
cancelAnimationFrame(animId);
nodes.forEach(n => { setX(n, n.xStart); n.result.setAttribute('opacity', '0'); });
}
root.querySelector('[data-act="start"]').addEventListener('click', animate);
root.querySelector('[data-act="reset"]').addEventListener('click', reset);
}
// =====================================================================
// Experiment: ppm-Raster — 1 Teilchen von 1.000.000 findet man kaum
// =====================================================================
function ppmGrid(root) {
root.innerHTML = `
<div class="ge-wrap">
<div class="ge-head">
<h3>🔍 Finde das eine Teilchen</h3>
<p>Unten siehst du ein Gitter mit <strong>10.000 Punkten</strong>. Ein einziger Punkt ist rot — das entspräche 100 ppm (echte 1 ppm wäre nochmal 100× feiner). Versuche, ihn zu finden und zu tippen!</p>
</div>
<div class="ge-ppm-grid" id="ge-ppm"></div>
<div class="ge-ppm-msg" aria-live="polite"></div>
<div class="ge-controls">
<button class="ge-btn ge-btn-primary" data-act="highlight">💡 Tipp zeigen</button>
<button class="ge-btn" data-act="new">🎲 Neues Teilchen</button>
</div>
<p class="ge-note">Bei der aktuellen CO₂-Konzentration von 422 ppm sind 422 von 1.000.000 Luftteilchen CO₂. Das klingt winzig — reicht aber, um die Erde messbar zu erwärmen. Quelle: NOAA Mauna Loa (2024).</p>
</div>
`;
const grid = root.querySelector('#ge-ppm');
const msg = root.querySelector('.ge-ppm-msg');
const COLS = 100, ROWS = 100;
let special = Math.floor(Math.random() * COLS * ROWS);
let found = false;
function render() {
grid.innerHTML = '';
grid.style.gridTemplateColumns = `repeat(${COLS}, 1fr)`;
for (let i = 0; i < COLS * ROWS; i++) {
const cell = document.createElement('span');
if (i === special) cell.className = 'ge-ppm-special';
grid.appendChild(cell);
}
msg.textContent = '';
msg.classList.remove('show', 'miss');
found = false;
}
function highlight() {
const el = grid.children[special];
if (!el) return;
el.classList.add('ge-ppm-flash');
setTimeout(() => el.classList.remove('ge-ppm-flash'), 1800);
}
function newOne() {
special = Math.floor(Math.random() * COLS * ROWS);
render();
}
function onHit(cell) {
if (found) return;
found = true;
cell.classList.add('ge-ppm-hit');
msg.textContent = '🎉 Getroffen! Das war 1 von 10.000 Punkten.';
msg.classList.remove('miss');
msg.classList.add('show');
}
function onMiss() {
if (found) return;
msg.textContent = 'Daneben — probier "Tipp zeigen" für eine kurze Hilfe.';
msg.classList.remove('show');
msg.classList.add('show', 'miss');
}
// Click-Handler delegiert auf das Grid (funktioniert auch bei 4-px-Zellen)
grid.addEventListener('click', ev => {
const target = ev.target;
if (target === grid) return;
if (target.classList && target.classList.contains('ge-ppm-special')) onHit(target);
else onMiss();
});
render();
root.querySelector('[data-act="highlight"]').addEventListener('click', highlight);
root.querySelector('[data-act="new"]').addEventListener('click', newOne);
}
// =====================================================================
// Experiment: Albedo-Slider — Oberfläche wechseln, Reflexion beobachten
// =====================================================================
function albedoSlider(root) {
const surfaces = [
{ key: 'snow', label: 'Frischer Schnee', albedo: 0.85, color: '#ffffff', border: '#b5c9cf' },
{ key: 'desert', label: 'Sand-Wüste', albedo: 0.35, color: '#e8d5b5', border: '#c4a97a' },
{ key: 'forest', label: 'Wald', albedo: 0.12, color: '#3a6b3e', border: '#2a5030' },
{ key: 'ocean', label: 'Ozean', albedo: 0.06, color: '#1f4e5a', border: '#0f2f3a' },
{ key: 'asphalt', label: 'Asphalt', albedo: 0.05, color: '#2a2a2a', border: '#1a1a1a' }
];
root.innerHTML = `
<div class="ge-wrap">
<div class="ge-head">
<h3>☀ Welche Oberfläche reflektiert wie viel?</h3>
<p>Wähle eine Oberfläche. Die Pfeile zeigen, wie viel Sonnenlicht zurückgeworfen wird.</p>
</div>
<div class="ge-albedo-choice" role="tablist">
${surfaces.map((s, i) => `
<button class="ge-btn ${i===0?'ge-btn-primary':''}" data-key="${s.key}">${s.label}</button>
`).join('')}
</div>
<svg class="ge-albedo-svg" viewBox="0 0 600 320" preserveAspectRatio="xMidYMid meet"></svg>
<div class="ge-albedo-stats">
<div>Albedo: <strong id="ge-alb-val">0,85</strong></div>
<div>Reflektiert: <strong id="ge-alb-refl">85 %</strong></div>
<div>Absorbiert: <strong id="ge-alb-abs">15 %</strong></div>
</div>
<p class="ge-note">Darum erwärmt sich die Arktis 4-mal schneller als die Erde insgesamt: Schmilzt Eis, kommt darunter dunkler Ozean zum Vorschein, der viel mehr Sonnenenergie aufnimmt. Quelle: Rantanen et al. 2022.</p>
</div>
`;
const svg = root.querySelector('.ge-albedo-svg');
const valEl = root.querySelector('#ge-alb-val');
const reflEl = root.querySelector('#ge-alb-refl');
const absEl = root.querySelector('#ge-alb-abs');
// Statischer Aufbau: Himmel + Sonne + Bodenrechteck
svgEl('rect', { x: 0, y: 0, width: 600, height: 210, fill: '#eff5f6' }, svg);
svgEl('circle', { cx: 90, cy: 70, r: 30, fill: '#f4c94e' }, svg);
const surfaceRect = svgEl('rect', { x: 0, y: 210, width: 600, height: 110, fill: '#ffffff', stroke: '#b5c9cf', 'stroke-width': '1.5' }, svg);
// Einfallender Lichtstrahl (Sonne → Boden)
svgEl('line', { x1: 110, y1: 100, x2: 300, y2: 210, stroke: '#e8833a', 'stroke-width': '3', 'stroke-linecap': 'round' }, svg);
svgEl('polygon', { points: '295,205 306,208 300,215', fill: '#e8833a' }, svg);
// Reflexions-Pfeile (dynamisch nach Albedo)
const reflG = svgEl('g', { class: 'ge-albedo-refl' }, svg);
function setSurface(key) {
const s = surfaces.find(x => x.key === key) || surfaces[0];
surfaceRect.setAttribute('fill', s.color);
surfaceRect.setAttribute('stroke', s.border);
// Anzahl reflektierter Pfeile proportional zur Albedo (1 bis 9 Pfeile)
reflG.innerHTML = '';
const nArrows = Math.max(1, Math.round(s.albedo * 10));
for (let i = 0; i < nArrows; i++) {
const angle = -60 + i * (120 / Math.max(1, nArrows - 1));
const rad = angle * Math.PI / 180;
const startX = 300, startY = 210;
const len = 120;
const endX = startX + Math.cos(rad) * len;
const endY = startY - Math.sin(Math.abs(rad) * Math.PI / 180 + 0.3) * Math.abs(len * Math.cos(rad) / 90) - 60;
// Simpler: Linien gleichmäßig nach oben gefächert
const e2x = 300 + Math.sin(rad) * len;
const e2y = 210 - Math.cos(rad) * len;
svgEl('line', { x1: startX, y1: startY, x2: e2x, y2: e2y, stroke: '#f4c94e', 'stroke-width': '2', 'stroke-linecap': 'round', opacity: '0.85' }, reflG);
}
valEl.textContent = s.albedo.toFixed(2).replace('.', ',');
reflEl.textContent = Math.round(s.albedo * 100) + ' %';
absEl.textContent = Math.round((1 - s.albedo) * 100) + ' %';
root.querySelectorAll('.ge-albedo-choice .ge-btn').forEach(b => {
b.classList.toggle('ge-btn-primary', b.dataset.key === s.key);
});
}
root.querySelectorAll('.ge-albedo-choice .ge-btn').forEach(b => {
b.addEventListener('click', () => setSurface(b.dataset.key));
});
setSurface('snow');
}
// =====================================================================
// Experiment: CO₂-Fußabdruck-Rechner — schneller Alltags-Schätzer
// =====================================================================
function footprintCalc(root) {
root.innerHTML = `
<div class="ge-wrap">
<div class="ge-head">
<h3>🧮 Schätze deinen CO₂-Fußabdruck</h3>
<p>Einfache Auswahl pro Bereich. Das Ergebnis ist eine Richtgröße — kein genauer Wert.</p>
</div>
<div class="ge-calc">
<div class="ge-calc-row">
<div class="ge-calc-q">🏠 Wohnen</div>
<div class="ge-calc-opts" data-key="home">
<button class="ge-btn" data-val="1.0">Wärmepumpe / Fernwärme</button>
<button class="ge-btn ge-btn-primary" data-val="2.0">Gas/Öl, mittlere Größe</button>
<button class="ge-btn" data-val="3.5">Altbau, schlecht isoliert</button>
</div>
</div>
<div class="ge-calc-row">
<div class="ge-calc-q">🚗 Verkehr</div>
<div class="ge-calc-opts" data-key="car">
<button class="ge-btn" data-val="0.3">meist zu Fuß / Rad / ÖV</button>
<button class="ge-btn ge-btn-primary" data-val="1.8">Pkw mittel, kein Flug</button>
<button class="ge-btn" data-val="4.5">Pkw viel + 1 Fernflug/Jahr</button>
</div>
</div>
<div class="ge-calc-row">
<div class="ge-calc-q">🍽 Ernährung</div>
<div class="ge-calc-opts" data-key="food">
<button class="ge-btn" data-val="1.2">vegan / vegetarisch</button>
<button class="ge-btn ge-btn-primary" data-val="1.8">gemischt, wenig Fleisch</button>
<button class="ge-btn" data-val="2.6">viel Fleisch und Milchprodukte</button>
</div>
</div>
<div class="ge-calc-row">
<div class="ge-calc-q">🛒 Konsum</div>
<div class="ge-calc-opts" data-key="shop">
<button class="ge-btn" data-val="1.0">bewusst, wenig Neues</button>
<button class="ge-btn ge-btn-primary" data-val="2.0">durchschnittlich</button>
<button class="ge-btn" data-val="3.0">häufig Neues / Elektronik</button>
</div>
</div>
</div>
<div class="ge-calc-result">
<div>Dein geschätzter Fußabdruck:</div>
<strong class="ge-calc-total">7,6 t CO₂ / Jahr</strong>
<div class="ge-calc-bar"><div class="ge-calc-fill" style="width: 76%"></div></div>
<div class="ge-calc-scale"><span>0 t</span><span>Welt-Ø 4,7</span><span>AT-Ø 7,7</span><span>10 t</span></div>
<div class="ge-calc-label">Klimaneutral-Ziel: unter 1 t — Welt-Durchschnitt: 4,7 t — Österreich: 7,7 t.</div>
</div>
<p class="ge-note">Die Anteile decken sich mit den Haupt-Kategorien im Klimaschutzbericht 2024 des Umweltbundesamts Österreich. Details pro Kategorie unter <a href="?term=co2-fussabdruck#gl-source-3" class="gl-inline-link" data-key="co2-fussabdruck">CO₂-Fußabdruck</a>.</p>
</div>
`;
const totals = { home: 2.0, car: 1.8, food: 1.8, shop: 2.0 };
function update() {
const sum = Object.values(totals).reduce((a, b) => a + b, 0);
root.querySelector('.ge-calc-total').textContent = sum.toFixed(1).replace('.', ',') + ' t CO₂ / Jahr';
const pct = Math.min(100, (sum / 10) * 100);
root.querySelector('.ge-calc-fill').style.width = pct + '%';
}
root.querySelectorAll('.ge-calc-opts').forEach(group => {
const key = group.dataset.key;
group.querySelectorAll('.ge-btn').forEach(btn => {
btn.addEventListener('click', () => {
group.querySelectorAll('.ge-btn').forEach(b => b.classList.remove('ge-btn-primary'));
btn.classList.add('ge-btn-primary');
totals[key] = parseFloat(btn.dataset.val);
update();
});
});
});
update();
}
// =====================================================================
// Experiment: Treibhauseffekt-Slider — CO₂-ppm → globale Erwärmung
// =====================================================================
function greenhouseSlider(root) {
root.innerHTML = `
<div class="ge-wrap">
<div class="ge-head">
<h3>🌡 Wie viel CO₂ verträgt das Klima?</h3>
<p>Schiebe den Regler und beobachte, wie sich die Atmosphäre füllt und die Erde wärmer wird.</p>
</div>
<svg class="ge-gh-svg" viewBox="0 0 600 300" preserveAspectRatio="xMidYMid meet" aria-hidden="true"></svg>
<div class="ge-gh-slider">
<label for="ge-gh-range">CO₂-Konzentration: <strong id="ge-gh-ppm">420</strong> ppm</label>
<input id="ge-gh-range" type="range" min="200" max="800" value="420" step="10">
<div class="ge-gh-markers">
<span>200</span><span>280<br>1750</span><span>420<br>heute</span><span>560<br>Verdopplung</span><span>800</span>
</div>
</div>
<div class="ge-gh-result">
<div class="ge-gh-pill">Temperaturanstieg gegenüber 1850: <strong id="ge-gh-dt">+1,2 °C</strong></div>
<div class="ge-gh-pill" id="ge-gh-verdict">nahe heutiger Wert</div>
</div>
<p class="ge-note">Vereinfachte Faustformel: ΔT = 3 °C × log₂(CO₂ / 280 ppm). Das bildet die IPCC-Klimasensitivität (rund 3 °C pro Verdopplung) grob ab. Quellen: IPCC AR6 WG1 Kap. 7 · NOAA Mauna Loa.</p>
</div>
`;
const svg = root.querySelector('.ge-gh-svg');
const rng = root.querySelector('#ge-gh-range');
const ppmOut = root.querySelector('#ge-gh-ppm');
const dtOut = root.querySelector('#ge-gh-dt');
const verdict = root.querySelector('#ge-gh-verdict');
// Statisches Szenen-Layout
// Sonne
svgEl('circle', { cx: 90, cy: 55, r: 28, fill: '#f4c94e' }, svg);
svgEl('text', { x: 90, y: 62, 'text-anchor': 'middle', 'font-size': '22' }, svg).textContent = '☀';
// Atmosphäre (gradient wird per Opacity per Zustand gesteuert)
const sky = svgEl('rect', { x: 0, y: 30, width: 600, height: 190, fill: '#4a7c8a', opacity: '0.15' }, svg);
// Erdoberfläche
const earth = svgEl('rect', { x: 0, y: 220, width: 600, height: 80, fill: '#5a8a5e' }, svg);
// Baum und Haus dekorativ
svgEl('polygon', { points: '100,220 115,195 130,220', fill: '#3a6b3e' }, svg);
svgEl('rect', { x: 112, y: 220, width: 6, height: 10, fill: '#6b4a2e' }, svg);
svgEl('rect', { x: 470, y: 198, width: 36, height: 22, fill: '#e8d5b5', stroke: '#1f4e5a', 'stroke-width': '1' }, svg);
svgEl('polygon', { points: '468,199 488,182 508,199', fill: '#c85c4a' }, svg);
// Container für dynamische Moleküle
const molGroup = svgEl('g', { class: 'ge-gh-mols' }, svg);
// Temperatur-Anzeige rechts oben
const thermo = svgEl('g', { transform: 'translate(540, 40)' }, svg);
svgEl('rect', { x: -10, y: 0, width: 20, height: 140, fill: '#fff', stroke: '#1f4e5a', rx: '10' }, thermo);
const mercury = svgEl('rect', { x: -6, y: 130, width: 12, height: 0, fill: '#c85c4a', rx: '6' }, thermo);
svgEl('circle', { cx: 0, cy: 148, r: 10, fill: '#c85c4a' }, thermo);
function update() {
const ppm = +rng.value;
ppmOut.textContent = ppm;
// ΔT gegen 1850 (≈ 280 ppm als Referenz)
const dt = 3 * Math.log2(ppm / 280);
const dtRounded = Math.round(dt * 10) / 10;
dtOut.textContent = (dtRounded >= 0 ? '+' : '') + dtRounded.toFixed(1).replace('.', ',') + ' °C';
// Sky-Farbton rotläuft je höher der CO₂-Wert (rotes Leuchten)
const warmth = Math.min(1, Math.max(0, (ppm - 280) / 520));
const redAmount = Math.round(40 + warmth * 140);
sky.setAttribute('fill', `rgb(${40 + redAmount}, ${140 - warmth * 50}, ${170 - warmth * 80})`);
sky.setAttribute('opacity', (0.15 + warmth * 0.35).toFixed(2));
earth.setAttribute('fill', warmth > 0.6 ? '#c4a97a' : (warmth > 0.3 ? '#9bab74' : '#5a8a5e'));
// Moleküle proportional zu ppm
molGroup.innerHTML = '';
const nMols = Math.round((ppm - 200) / 8); // 0..75 Moleküle
for (let i = 0; i < nMols; i++) {
const mx = 40 + Math.random() * 520;
const my = 50 + Math.random() * 155;
const isMethane = Math.random() < 0.12;
svgEl('circle', {
cx: mx, cy: my, r: 5,
fill: isMethane ? '#e8833a' : '#c85c4a', opacity: '0.7'
}, molGroup);
}
// Mercury-Säule (0 °C = y=130, max +5°C bei y=10 → 120 px / 5°C = 24 px/°C)
const mh = Math.max(0, Math.min(120, dt * 24));
mercury.setAttribute('y', 130 - mh);
mercury.setAttribute('height', mh);
// Verdict-Text
let v, cls;
if (ppm < 300) { v = '🟢 Vorindustrielles Klima'; cls = 'good'; }
else if (ppm < 450) { v = '🟡 nahe heutiger Wert'; cls = 'now'; }
else if (ppm < 600) { v = '🟠 Verdopplung — kritisch'; cls = 'warn'; }
else { v = '🔴 weit jenseits 2 °C-Ziel'; cls = 'bad'; }
verdict.textContent = v;
verdict.dataset.state = cls;
}
rng.addEventListener('input', update);
update();
}
// =====================================================================
// Experiment: Windrad-Höhenslider — Höhe ändern, Vergleichsobjekte leuchten
// =====================================================================
function windradSlider(root) {
const objects = [
{ label: 'Mensch', h: 1.8, color: '#1f4e5a' },
{ label: 'Einfamilienhaus', h: 8, color: '#c85c4a' },
{ label: 'Alter Baum', h: 25, color: '#5a8a5e' },
{ label: 'Kirchturm', h: 60, color: '#c4a97a' },
{ label: 'Altes Windrad', h: 100, color: '#8a8a8a' },
{ label: 'DC Tower Wien', h: 220, color: '#4a7c8a' },
{ label: 'Modernes Windrad',h: 240, color: '#3a6b3e' }
];
root.innerHTML = `
<div class="ge-wrap">
<div class="ge-head">
<h3>🌬 Wie groß ist ein modernes Windrad?</h3>
<p>Schiebe den Regler auf die vermutete Höhe. Das passende Objekt leuchtet auf — ab 240 m bist du auf Höhe des modernen Windrads.</p>
</div>
<svg class="ge-wind-svg" viewBox="0 0 600 360" preserveAspectRatio="xMidYMid meet" aria-hidden="true"></svg>
<div class="ge-gh-slider">
<label for="ge-wind-range">Vermutete Höhe: <strong id="ge-wind-m">120</strong> m</label>
<input id="ge-wind-range" type="range" min="0" max="260" value="120" step="5">
</div>
<div class="ge-gh-result">
<div class="ge-gh-pill">In dieser Höhenklasse: <strong id="ge-wind-match">Altes Windrad</strong></div>
</div>
<p class="ge-note">Moderne Windräder in Österreich: Nabe 100160 m, Rotor bis 80 m Länge, Gesamthöhe bis 240 m. Quelle: IG Windkraft Österreich.</p>
</div>
`;
const svg = root.querySelector('.ge-wind-svg');
const rng = root.querySelector('#ge-wind-range');
const mOut = root.querySelector('#ge-wind-m');
const matchOut = root.querySelector('#ge-wind-match');
// Ground
svgEl('rect', { x: 0, y: 320, width: 600, height: 40, fill: '#5a8a5e' }, svg);
// SKALA: 1 m = 1.2 px, Boden y=320, top bei 240 m = 320 - 240*1.2 = 32
const mToY = m => 320 - m * 1.2;
// Objekte als Silhouetten, jedes an fester x-Position
const objG = objects.map((o, i) => {
const g = svgEl('g', { class: 'ge-wind-obj' }, svg);
const x = 80 + i * 72;
// Silhouette: einfaches Rechteck oder Dreieck (stilisiert)
const y = mToY(o.h);
const h = 320 - y;
if (o.label.includes('Baum')) {
svgEl('ellipse', { cx: x, cy: y + 10, rx: 14, ry: 18, fill: o.color }, g);
svgEl('rect', { x: x - 2, y: y + 25, width: 4, height: h - 25, fill: '#6b4a2e' }, g);
} else if (o.label.includes('Mensch')) {
svgEl('circle', { cx: x, cy: y + 2, r: 1.4, fill: o.color }, g);
svgEl('rect', { x: x - 0.5, y: y + 2, width: 1, height: 2, fill: o.color }, g);
} else if (o.label.includes('Haus')) {
svgEl('rect', { x: x - 12, y: y + 4, width: 24, height: h - 4, fill: '#e8d5b5', stroke: '#1f4e5a', 'stroke-width': '0.6' }, g);
svgEl('polygon', { points: `${x-14},${y+4} ${x},${y-4} ${x+14},${y+4}`, fill: o.color }, g);
} else if (o.label.includes('Kirchturm')) {
svgEl('rect', { x: x - 8, y: y + 12, width: 16, height: h - 12, fill: o.color, stroke: '#1f4e5a', 'stroke-width': '0.6' }, g);
svgEl('polygon', { points: `${x-10},${y+12} ${x},${y} ${x+10},${y+12}`, fill: '#c85c4a' }, g);
} else if (o.label.includes('Tower')) {
svgEl('rect', { x: x - 7, y, width: 14, height: h, fill: o.color, stroke: '#1f4e5a', 'stroke-width': '0.6' }, g);
// Window stripes
for (let k = 0; k < 8; k++) {
svgEl('rect', { x: x - 5, y: y + 8 + k * (h / 10), width: 10, height: 2, fill: '#e8d5b5', opacity: '0.7' }, g);
}
} else if (o.label === 'Altes Windrad') {
svgEl('rect', { x: x - 1.5, y, width: 3, height: h, fill: o.color }, g);
svgEl('circle', { cx: x, cy: y, r: 2.2, fill: '#1f4e5a' }, g);
svgEl('line', { x1: x, y1: y, x2: x, y2: y - 22, stroke: '#1f4e5a', 'stroke-width': '1.6', 'stroke-linecap': 'round' }, g);
svgEl('line', { x1: x, y1: y, x2: x + 18, y2: y + 12, stroke: '#1f4e5a', 'stroke-width': '1.6', 'stroke-linecap': 'round' }, g);
svgEl('line', { x1: x, y1: y, x2: x - 18, y2: y + 12, stroke: '#1f4e5a', 'stroke-width': '1.6', 'stroke-linecap': 'round' }, g);
} else if (o.label === 'Modernes Windrad') {
svgEl('polygon', { points: `${x-3},320 ${x+3},320 ${x+2},${y+8} ${x-2},${y+8}`, fill: '#fff', stroke: '#1f4e5a', 'stroke-width': '0.8' }, g);
svgEl('rect', { x: x - 4, y: y + 4, width: 8, height: 6, fill: '#1f4e5a' }, g);
svgEl('circle', { cx: x, cy: y + 8, r: 3, fill: o.color }, g);
// Rotor
svgEl('line', { x1: x, y1: y + 8, x2: x, y2: y - 72, stroke: '#1f4e5a', 'stroke-width': '2', 'stroke-linecap': 'round' }, g);
svgEl('line', { x1: x, y1: y + 8, x2: x + 62, y2: y + 44, stroke: '#1f4e5a', 'stroke-width': '2', 'stroke-linecap': 'round' }, g);
svgEl('line', { x1: x, y1: y + 8, x2: x - 62, y2: y + 44, stroke: '#1f4e5a', 'stroke-width': '2', 'stroke-linecap': 'round' }, g);
}
// Label unter dem Objekt
const lbl = svgEl('text', { x, y: 340, 'text-anchor': 'middle', 'font-size': '8', fill: '#4a4a4a', 'font-weight': '600' }, g);
lbl.textContent = o.h < 10 ? o.h.toFixed(1).replace('.', ',') + ' m' : o.h + ' m';
return { g, o };
});
// Markierungslinie (aktuelle Höhe)
const markerLine = svgEl('line', { x1: 30, y1: 200, x2: 570, y2: 200, stroke: '#c85c4a', 'stroke-width': '1.5', 'stroke-dasharray': '4 3' }, svg);
const markerLabel = svgEl('text', { x: 34, y: 196, 'font-size': '10', fill: '#c85c4a', 'font-weight': '700' }, svg);
function update() {
const m = +rng.value;
mOut.textContent = m;
const y = mToY(m);
markerLine.setAttribute('y1', y);
markerLine.setAttribute('y2', y);
markerLabel.setAttribute('y', y - 4);
markerLabel.textContent = m + ' m';
// Aktives Objekt finden: Höhe ≤ m und max
let bestIdx = -1;
objects.forEach((o, i) => {
if (o.h <= m + 5 && (bestIdx === -1 || o.h > objects[bestIdx].h)) bestIdx = i;
});
objG.forEach((ob, i) => {
ob.g.classList.toggle('ge-wind-active', i === bestIdx);
});
matchOut.textContent = bestIdx >= 0 ? objects[bestIdx].label : '(noch nichts)';
}
rng.addEventListener('input', update);
update();
}
// =====================================================================
// Experiment: Methan-GWP-Waage — 1 kg Methan gegen wie viel CO₂?
// =====================================================================
function methaneBalance(root) {
root.innerHTML = `
<div class="ge-wrap">
<div class="ge-head">
<h3>⚖ Wie stark ist 1 kg Methan?</h3>
<p>Auf der linken Waagschale liegt 1 kg Methan. Lege so viele kg CO₂ auf die rechte Schale, bis die Waage im Gleichgewicht ist (auf 100 Jahre).</p>
</div>
<svg class="ge-bal-svg" viewBox="0 0 600 360" preserveAspectRatio="xMidYMid meet" aria-hidden="true"></svg>
<div class="ge-gh-slider">
<label for="ge-bal-range">CO₂ auf der rechten Schale: <strong id="ge-bal-kg">10</strong> kg</label>
<input id="ge-bal-range" type="range" min="1" max="60" value="10" step="1">
</div>
<div class="ge-gh-result">
<div class="ge-gh-pill" id="ge-bal-verdict">noch zu leicht</div>
</div>
<p class="ge-note">Laut IPCC AR6 (2021) hat 1 kg Methan auf 100 Jahre die Klimawirkung von 28 kg CO₂ (GWP-100). Auf 20 Jahre sogar 82 kg.</p>
</div>
`;
const svg = root.querySelector('.ge-bal-svg');
const rng = root.querySelector('#ge-bal-range');
const kgOut = root.querySelector('#ge-bal-kg');
const verdict = root.querySelector('#ge-bal-verdict');
// Grundaufbau: Ständer + Balken
svgEl('rect', { x: 296, y: 200, width: 8, height: 120, fill: '#6b4a2e' }, svg);
svgEl('rect', { x: 260, y: 316, width: 80, height: 10, fill: '#6b4a2e', rx: '3' }, svg);
svgEl('circle', { cx: 300, cy: 200, r: 6, fill: '#1f4e5a' }, svg);
// Balken (rotiert um 300,200)
const beam = svgEl('g', { transform: 'rotate(0, 300, 200)' }, svg);
svgEl('rect', { x: 100, y: 196, width: 400, height: 8, fill: '#4a4a4a', rx: '4' }, beam);
// Aufhängungen
svgEl('line', { x1: 120, y1: 200, x2: 120, y2: 150, stroke: '#4a4a4a', 'stroke-width': '1.2' }, beam);
svgEl('line', { x1: 480, y1: 200, x2: 480, y2: 150, stroke: '#4a4a4a', 'stroke-width': '1.2' }, beam);
// Schalen
svgEl('ellipse', { cx: 120, cy: 152, rx: 45, ry: 8, fill: '#c4a97a', stroke: '#1f4e5a', 'stroke-width': '1' }, beam);
svgEl('path', { d: 'M 75 152 L 85 170 L 155 170 L 165 152 Z', fill: '#c4a97a', stroke: '#1f4e5a', 'stroke-width': '1' }, beam);
svgEl('ellipse', { cx: 480, cy: 152, rx: 45, ry: 8, fill: '#c4a97a', stroke: '#1f4e5a', 'stroke-width': '1' }, beam);
svgEl('path', { d: 'M 435 152 L 445 170 L 515 170 L 525 152 Z', fill: '#c4a97a', stroke: '#1f4e5a', 'stroke-width': '1' }, beam);
// Links: 1 Methan-Molekül (dick und orange)
const methaneG = svgEl('g', {}, beam);
svgEl('circle', { cx: 120, cy: 140, r: 14, fill: '#e8833a', stroke: '#1f4e5a', 'stroke-width': '1' }, methaneG);
const methaneLabel = svgEl('text', { x: 120, y: 144, 'text-anchor': 'middle', 'font-size': '11', 'font-weight': '800', fill: '#fff' }, methaneG);
methaneLabel.textContent = 'CH₄';
const methaneCount = svgEl('text', { x: 120, y: 125, 'text-anchor': 'middle', 'font-size': '10', 'font-weight': '700', fill: '#1f4e5a' }, methaneG);
methaneCount.textContent = '1 kg';
// Rechts: variable CO₂-Moleküle
const co2G = svgEl('g', {}, beam);
const co2Count = svgEl('text', { x: 480, y: 125, 'text-anchor': 'middle', 'font-size': '10', 'font-weight': '700', fill: '#1f4e5a' }, beam);
function update() {
const kg = +rng.value;
kgOut.textContent = kg;
co2Count.textContent = kg + ' kg';
// CO₂-Moleküle auf rechte Schale (gestapelt, maximal 60)
co2G.innerHTML = '';
const perRow = 8;
const cols = Math.min(perRow, kg);
const rows = Math.ceil(kg / perRow);
for (let i = 0; i < Math.min(kg, 60); i++) {
const r = Math.floor(i / perRow);
const c = i % perRow;
const cx = 480 - ((cols - 1) * 9) / 2 + c * 9;
const cy = 145 - r * 9;
svgEl('circle', { cx, cy, r: 4, fill: '#c85c4a', opacity: '0.95' }, co2G);
}
// Balken-Rotation: Methan ist immer gleich "schwer" wie 28 kg CO₂
// Diff in kg-Äquivalent; Max-Rotation ±18°
const diff = kg - 28;
const angle = Math.max(-18, Math.min(18, diff * 0.8));
beam.setAttribute('transform', `rotate(${angle}, 300, 200)`);
// Verdict
if (kg < 20) verdict.textContent = '⬇ noch zu leicht';
else if (kg < 27) verdict.textContent = '🟡 fast — mehr CO₂ drauf';
else if (kg <= 29) verdict.textContent = '🎯 genau! 1 kg Methan ≈ 28 kg CO₂';
else verdict.textContent = '⬆ jetzt zu viel CO₂';
verdict.dataset.state = (kg >= 27 && kg <= 29) ? 'good' : 'now';
}
rng.addEventListener('input', update);
update();
}
// Registry — Key entspricht key_slug aus DB
window.GlossarExperiments = {
'kilowattstunde': kwhRace,
'ppm': ppmGrid,
'albedo': albedoSlider,
'co2-fussabdruck': footprintCalc,
'treibhauseffekt': greenhouseSlider,
'windenergie': windradSlider,
'methan': methaneBalance
};
})();