22f5e9b686
- 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>
660 lines
34 KiB
JavaScript
660 lines
34 KiB
JavaScript
/**
|
||
* 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 100–160 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
|
||
};
|
||
})();
|