Files
geograsim/App/assets/js/glossar-experiments.js
Adminator c4276083a2 Glossar visuell komplett: 162 Bilder, 13 Kapitel-Icons, 12 interaktive Widgets
- 162 neue Glossar-Bilder (Flat-Scandinavian, gpt-image-1) → alle 402 Einträge bebildert;
  image_path-Update: App/Don_t_Deploy/2026-07-16-glossar-bilder.sql
- 13 Kapitel-Icons (icon-<id>.webp) ersetzen die Emojis in Buch-Übersicht + Kapitel-h1
  (buchKapIcon-Helper mit Emoji-Fallback)
- 12 interaktive Glossar-Experimente in 2 Modulen:
  natur (Mondrotation, Kernschatten, Keeling-Kurve, Pegel/HQ100, Wärmeausdehnung,
  Klimaträgheit) + wirtschaft (Preiselastizität, Engpass, Skaleneffekt, Modal Split,
  Netzfrequenz/Hertz, Lageenergie/Pumpspeicher); Registry via Object.assign,
  Script-Einbindung in glossar.php

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 00:59:30 +02:00

550 lines
28 KiB
JavaScript
Raw Permalink 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: 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.
// Object.assign, damit weitere Dateien (glossar-experiments-*.js) unabhängig
// von der Ladereihenfolge dazuregistrieren können.
window.GlossarExperiments = Object.assign(window.GlossarExperiments || {}, {
'kilowattstunde': kwhRace,
'ppm': ppmGrid,
'albedo': albedoSlider,
'co2-fussabdruck': footprintCalc,
'treibhauseffekt': greenhouseSlider,
'methan': methaneBalance
// 'windenergie' bewusst raus — redundant mit windrad-groessenvergleich.svg
});
})();