Files
geograsim/App/assets/js/glossar-experiments-geowelt.js
T
Adminator 2e50aaf49f Glossar: 12 neue interaktive Widgets (Tourismus + Geo/Energie/Reise)
tourismus.js: beschneiung (Temp→Schnee), diversifizierung, kapazitaet, saisonalitaet,
uebertourismus, bevoelkerungsdichte
geowelt.js: gletscher, lawinenwarnstufe, lichtgeschwindigkeit, gigawattstunde,
netzspannung, kartenmaßstab
Einbindung in glossar.php (Registry via Object.assign).

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

577 lines
31 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 — Gruppe „geowelt".
*
* Kleine interaktive SVG-Widgets für das Detail-Modal eines Glossar-Eintrags
* (Abschnitt „Selbst ausprobieren"). Registrierung am Ende der Datei via
* window.GlossarExperiments[key_slug] = fn.
*
* Richtlinien: Vanilla JS, keine Libraries, nur bestehende .ge-*-CSS-Klassen.
* Grosse Tap-Ziele, keine Hover-Abhängigkeit. rAF-Schleifen stoppen, sobald das
* Modal geschlossen ist (root.isConnected-Guard). Zahlen quellenbar (ge-note).
*/
(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;
};
const fmtInt = n => Math.round(n).toLocaleString('de-AT');
const fmtDec = (n, d) => n.toLocaleString('de-AT', { minimumFractionDigits: d, maximumFractionDigits: d });
// =====================================================================
// gletscher — Zeit-Schieberegler 1900 → heute, Gletscher zieht sich zurück
// =====================================================================
function gletscher(root) {
root.innerHTML = `
<div class="ge-wrap">
<div class="ge-head">
<h3>🏔 Wie ein Alpengletscher schrumpft</h3>
<p>Schiebe durch die Jahre. Beobachte, wie sich die Gletscherzunge das Tal hinaufzieht und der Eis-Vorrat sinkt.</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-gl-range">Jahr: <strong id="ge-gl-year">1900</strong></label>
<input id="ge-gl-range" type="range" min="1900" max="2023" value="1900" step="1">
<div class="ge-gh-markers"><span>1900</span><span>1950</span><span>2000</span><span>heute</span></div>
</div>
<div class="ge-gh-result">
<div class="ge-gh-pill">Eis-Vorrat (1900 = 100 %): <strong data-ref="vol">100 %</strong></div>
<div class="ge-gh-pill" data-ref="verdict" data-state="good" aria-live="polite">noch fast unberührt</div>
</div>
<p class="ge-note">Werte veranschaulichen den Volumen-Rückgang der Alpengletscher seit 1900. Allein zwischen 2022 und 2023 verloren die Schweizer Gletscher rund 10 % ihres Volumens. Quelle: GLAMOS (Gletschermessnetz Schweiz).</p>
</div>
`;
const svg = root.querySelector('.ge-gh-svg');
const rng = root.querySelector('#ge-gl-range');
const yearOut = root.querySelector('#ge-gl-year');
const volOut = root.querySelector('[data-ref="vol"]');
const verdict = root.querySelector('[data-ref="verdict"]');
if (!svg || !rng) return;
// Statische Szene: Himmel, Berge, Tal
svgEl('rect', { x: 0, y: 0, width: 600, height: 300, fill: '#eaf2f6' }, svg);
svgEl('polygon', { points: '0,300 130,70 260,300', fill: '#b9c3c9' }, svg);
svgEl('polygon', { points: '180,300 320,50 470,300', fill: '#a7b3ba' }, svg);
svgEl('polygon', { points: '380,300 520,90 600,300', fill: '#b9c3c9' }, svg);
// Schneekappen
svgEl('polygon', { points: '130,70 108,105 152,105', fill: '#ffffff' }, svg);
svgEl('polygon', { points: '320,50 292,95 348,95', fill: '#ffffff' }, svg);
// Talboden (grün)
svgEl('rect', { x: 0, y: 250, width: 600, height: 50, fill: '#6f9c5f' }, svg);
// Gletscher als dicke Linie von der Quelle (oben) zur wandernden Zunge (unten)
const SRC = { x: 300, y: 78 }, TIP = { x: 300, y: 258 };
const glacierShade = svgEl('line', { x1: SRC.x, y1: SRC.y, x2: TIP.x, y2: TIP.y, stroke: '#9cc4e4', 'stroke-width': '34', 'stroke-linecap': 'round' }, svg);
const glacier = svgEl('line', { x1: SRC.x, y1: SRC.y, x2: TIP.x, y2: TIP.y, stroke: '#e2eef7', 'stroke-width': '24', 'stroke-linecap': 'round' }, svg);
const tipLabel = svgEl('text', { x: 300, y: 250, 'text-anchor': 'middle', 'font-size': '11', 'font-weight': '800', fill: '#1f4e5a' }, svg);
tipLabel.textContent = 'Zunge';
// Volumen-Balken rechts
svgEl('rect', { x: 552, y: 60, width: 26, height: 200, fill: '#ffffff', stroke: '#1f4e5a', 'stroke-width': '1.4', rx: '6' }, svg);
const volBar = svgEl('rect', { x: 555, y: 63, width: 20, height: 194, fill: '#4a9cc4', rx: '5' }, svg);
svgEl('text', { x: 565, y: 52, 'text-anchor': 'middle', 'font-size': '10', 'font-weight': '700', fill: '#1f4e5a' }, svg).textContent = 'Eis';
const anchors = [[1900, 100], [1950, 88], [1980, 78], [2000, 70], [2010, 60], [2020, 48], [2022, 44], [2023, 40]];
function volAt(y) {
if (y <= anchors[0][0]) return anchors[0][1];
for (let i = 0; i < anchors.length - 1; i++) {
const [y0, v0] = anchors[i], [y1, v1] = anchors[i + 1];
if (y <= y1) { const t = (y - y0) / (y1 - y0); return v0 + (v1 - v0) * t; }
}
return anchors[anchors.length - 1][1];
}
function update() {
const year = +rng.value;
const vol = volAt(year);
yearOut.textContent = year;
volOut.textContent = Math.round(vol) + ' %';
// Zunge wandert nach oben, je weniger Eis
const frac = vol / 100;
const tipY = SRC.y + (TIP.y - SRC.y) * frac;
glacier.setAttribute('y2', tipY);
glacierShade.setAttribute('y2', tipY);
tipLabel.setAttribute('y', Math.min(255, tipY + 22));
// Balken
const full = 194;
const h = full * frac;
volBar.setAttribute('y', 63 + (full - h));
volBar.setAttribute('height', h);
let txt, state;
if (vol >= 85) { txt = 'noch fast unberührt'; state = 'good'; }
else if (vol >= 65) { txt = 'Eis geht spürbar zurück'; state = 'now'; }
else if (vol >= 45) { txt = 'Zunge zieht sich stark zurück'; state = 'warn'; }
else { txt = 'rasanter Rückgang — allein 2022 2023 rund 10 %'; state = 'bad'; }
verdict.textContent = txt;
verdict.dataset.state = state;
}
rng.addEventListener('input', update);
update();
}
// =====================================================================
// lawinenwarnstufe — Regler Stufe 1 5, Gefahr steigt sprunghaft
// =====================================================================
function lawinenwarnstufe(root) {
root.innerHTML = `
<div class="ge-wrap">
<div class="ge-head">
<h3>🏔 Die Lawinen-Warnstufen</h3>
<p>Stelle die Warnstufe ein. Achte darauf: Die Gefahr steigt nicht gleichmäßig — der Sprung von Stufe 3 auf 4 ist besonders groß.</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-lw-range">Warnstufe: <strong id="ge-lw-num">1</strong> — <strong id="ge-lw-name">gering</strong></label>
<input id="ge-lw-range" type="range" min="1" max="5" value="1" step="1">
<div class="ge-gh-markers"><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span></div>
</div>
<div class="ge-gh-result">
<div class="ge-gh-pill">Gefahr: <strong data-ref="risk">gering</strong></div>
<div class="ge-gh-pill" data-ref="info" data-state="good" aria-live="polite"></div>
</div>
<p class="ge-note">Fünfstufige Europäische Lawinen-Gefahrenskala (EAWS). Die Skala ist nicht linear: Jede Stufe steht ungefähr für eine Verdopplung der Gefahr, der Schritt von 3 (erheblich) auf 4 (groß) ist besonders deutlich. Quelle: European Avalanche Warning Services.</p>
</div>
`;
const svg = root.querySelector('.ge-gh-svg');
const rng = root.querySelector('#ge-lw-range');
const numOut = root.querySelector('#ge-lw-num');
const nameOut = root.querySelector('#ge-lw-name');
const riskOut = root.querySelector('[data-ref="risk"]');
const info = root.querySelector('[data-ref="info"]');
if (!svg || !rng) return;
const levels = {
1: { name: 'gering', risk: 8, state: 'good', txt: 'Meist sichere Verhältnisse. Auslösen nur bei sehr großer Belastung an wenigen Steilstellen.' },
2: { name: 'mäßig', risk: 20, state: 'now', txt: 'An einzelnen steilen Hängen möglich. Sorgfältige Routenwahl nötig.' },
3: { name: 'erheblich', risk: 45, state: 'warn', txt: 'An vielen Steilhängen möglich — oft reicht schon eine Person. Die meisten Unfälle passieren bei Stufe 3.' },
4: { name: 'groß', risk: 80, state: 'bad', txt: 'An den meisten Steilhängen wahrscheinlich, auch von selbst. Sehr gefährlich — Touren nur mit viel Erfahrung.' },
5: { name: 'sehr groß', risk: 100, state: 'bad', txt: 'Zahlreiche große, auch spontane Lawinen bis ins flache Gelände. Extrem — Touren tabu.' }
};
// Statische Szene: Himmel + Hang
svgEl('rect', { x: 0, y: 0, width: 600, height: 300, fill: '#eaf2f6' }, svg);
// Hang als weißes Schnee-Dreieck
svgEl('polygon', { points: '0,300 0,120 600,260 600,300', fill: '#f4f8fb', stroke: '#c4d4de', 'stroke-width': '1.5' }, svg);
// Fels-Untergrund unten
svgEl('polygon', { points: '0,300 600,300 600,278 0,150', fill: '#8a7d6b', opacity: '0.5' }, svg);
// Bäume am Hangfuß
[ [90, 262], [150, 274] ].forEach(([x, y]) => {
svgEl('polygon', { points: `${x},${y - 26} ${x - 11},${y} ${x + 11},${y}`, fill: '#3a6b3e' }, svg);
});
// Dynamische Gruppen: Risse + abgehendes Schneebrett
const cracksG = svgEl('g', {}, svg);
const slabG = svgEl('g', {}, svg);
// Gefahren-Balken
svgEl('rect', { x: 40, y: 40, width: 520, height: 20, fill: '#ffffff', stroke: '#1f4e5a', 'stroke-width': '1.2', rx: '10' }, svg);
const riskBar = svgEl('rect', { x: 42, y: 42, width: 10, height: 16, fill: '#6f9c5f', rx: '8' }, svg);
svgEl('text', { x: 40, y: 32, 'font-size': '11', 'font-weight': '700', fill: '#1f4e5a' }, svg).textContent = 'Gefahr';
const stateColor = { good: '#6f9c5f', now: '#4a7c8a', warn: '#e08a2a', bad: '#c85c4a' };
function update() {
const n = +rng.value;
const L = levels[n];
numOut.textContent = n;
nameOut.textContent = L.name;
riskOut.textContent = L.name + ' (Stufe ' + n + ')';
info.textContent = L.txt;
info.dataset.state = L.state;
// Balken (nicht-linear → zeigt den Sprung 3→4)
riskBar.setAttribute('width', Math.max(8, (516 * L.risk) / 100));
riskBar.setAttribute('fill', stateColor[L.state]);
// Risse proportional zur Stufe
cracksG.innerHTML = '';
const nCracks = n - 1;
for (let i = 0; i < nCracks; i++) {
const x = 220 + i * 70 + Math.sin(i * 2) * 20;
const y = 150 + i * 18;
svgEl('path', { d: `M ${x} ${y} q 14 12 8 30 q -6 14 10 26`, stroke: '#7c8a94', 'stroke-width': '2', fill: 'none', 'stroke-linecap': 'round' }, cracksG);
}
// Abgehendes Schneebrett ab Stufe 4
slabG.innerHTML = '';
if (n >= 4) {
svgEl('polygon', { points: '300,175 470,205 460,240 300,215', fill: '#dbe7ef', stroke: '#c85c4a', 'stroke-width': '2.5', 'stroke-dasharray': n >= 5 ? '0' : '6 4' }, slabG);
// Bruchkante
svgEl('line', { x1: 300, y1: 173, x2: 300, y2: 213, stroke: '#c85c4a', 'stroke-width': '3', 'stroke-linecap': 'round' }, slabG);
if (n >= 5) {
svgEl('polygon', { points: '150,225 340,250 330,285 150,260', fill: '#eef4f8', stroke: '#c85c4a', 'stroke-width': '2' }, slabG);
}
}
}
rng.addEventListener('input', update);
update();
}
// =====================================================================
// lichtgeschwindigkeit — Ziel-Auswahl, Lichtlaufzeit-Uhr
// =====================================================================
function lichtgeschwindigkeit(root) {
root.innerHTML = `
<div class="ge-wrap">
<div class="ge-head">
<h3>💡 Wie lange braucht das Licht?</h3>
<p>Wähle ein Ziel. Ein Lichtblitz startet auf der Erde. Sieh, wie lange das Licht bis dorthin unterwegs ist.</p>
</div>
<div class="ge-albedo-choice" role="group">
<button class="ge-btn ge-btn-primary" data-key="mond">🌙 Mond</button>
<button class="ge-btn" data-key="sonne">☀ Sonne</button>
<button class="ge-btn" data-key="mars">🔴 Mars</button>
<button class="ge-btn" data-key="proxima">⭐ Proxima Centauri</button>
</div>
<svg class="ge-gh-svg" viewBox="0 0 600 200" preserveAspectRatio="xMidYMid meet" aria-hidden="true"></svg>
<div class="ge-gh-result">
<div class="ge-gh-pill">Entfernung: <strong data-ref="dist">384.400 km</strong></div>
<div class="ge-gh-pill" data-ref="time" data-state="now" aria-live="polite">Lichtlaufzeit: 1,3 Sekunden</div>
</div>
<p class="ge-note">Licht legt rund 299.792 km pro Sekunde zurück — extrem schnell. Und doch braucht es bis zum nächsten Stern über 4 Jahre: Das All ist unvorstellbar groß. Entfernungen gerundet, Quelle: NASA.</p>
</div>
`;
const svg = root.querySelector('.ge-gh-svg');
const distOut = root.querySelector('[data-ref="dist"]');
const timeOut = root.querySelector('[data-ref="time"]');
if (!svg) return;
const targets = {
mond: { name: 'Mond', dist: '384.400 km', time: '1,3 Sekunden', emoji: '🌙' },
sonne: { name: 'Sonne', dist: '149,6 Mio. km', time: '8 Minuten 20 Sekunden', emoji: '☀' },
mars: { name: 'Mars', dist: 'im Schnitt 225 Mio. km', time: 'rund 12,5 Minuten (3 22 min je nach Bahn)', emoji: '🔴' },
proxima: { name: 'Proxima Centauri', dist: 'rund 40 Billionen km', time: 'etwa 4,2 Jahre', emoji: '⭐' }
};
// Statische Szene: Erde links, Ziel rechts, Bahn dazwischen
svgEl('rect', { x: 0, y: 0, width: 600, height: 200, fill: '#10233a' }, svg);
// ein paar Sterne
for (let i = 0; i < 26; i++) {
svgEl('circle', { cx: 20 + Math.random() * 560, cy: 10 + Math.random() * 180, r: Math.random() * 1.3 + 0.4, fill: '#dfe8f2', opacity: (0.4 + Math.random() * 0.5).toFixed(2) }, svg);
}
svgEl('line', { x1: 70, y1: 100, x2: 520, y2: 100, stroke: '#3a5a7a', 'stroke-width': '2', 'stroke-dasharray': '5 6' }, svg);
svgEl('circle', { cx: 60, cy: 100, r: 22, fill: '#3a7cc4' }, svg);
svgEl('circle', { cx: 60, cy: 100, r: 22, fill: 'none', stroke: '#6fae5e', 'stroke-width': '3', 'stroke-dasharray': '10 8' }, svg);
svgEl('text', { x: 60, y: 150, 'text-anchor': 'middle', 'font-size': '12', 'font-weight': '700', fill: '#dfe8f2' }, svg).textContent = 'Erde';
const targetIcon = svgEl('text', { x: 530, y: 108, 'text-anchor': 'middle', 'font-size': '34' }, svg);
const targetLabel = svgEl('text', { x: 530, y: 150, 'text-anchor': 'middle', 'font-size': '12', 'font-weight': '700', fill: '#dfe8f2' }, svg);
// Lichtblitz (Photon)
const photon = svgEl('circle', { cx: 82, cy: 100, r: 6, fill: '#f4e04a' }, svg);
const photonGlow = svgEl('circle', { cx: 82, cy: 100, r: 12, fill: '#f4e04a', opacity: '0.35' }, svg);
const X0 = 82, X1 = 508;
let animId = null, current = 'mond';
function animate() {
cancelAnimationFrame(animId);
const dur = 2200;
const start = performance.now();
function tick(now) {
if (!root.isConnected) { cancelAnimationFrame(animId); return; }
const raw = (now - start) / dur;
const t = raw % 1;
const x = X0 + (X1 - X0) * t;
photon.setAttribute('cx', x);
photonGlow.setAttribute('cx', x);
const op = t > 0.9 ? (1 - t) * 10 : 1;
photon.setAttribute('opacity', op.toFixed(2));
photonGlow.setAttribute('opacity', (op * 0.35).toFixed(2));
animId = requestAnimationFrame(tick);
}
animId = requestAnimationFrame(tick);
}
function select(key) {
const T = targets[key] || targets.mond;
current = key;
distOut.textContent = T.dist;
timeOut.textContent = 'Lichtlaufzeit: ' + T.time;
timeOut.dataset.state = key === 'proxima' ? 'bad' : (key === 'sonne' || key === 'mars' ? 'warn' : 'now');
targetIcon.textContent = T.emoji;
targetLabel.textContent = T.name;
root.querySelectorAll('.ge-albedo-choice .ge-btn').forEach(b => {
b.classList.toggle('ge-btn-primary', b.dataset.key === key);
});
animate();
}
root.querySelectorAll('.ge-albedo-choice .ge-btn').forEach(b => {
b.addEventListener('click', () => select(b.dataset.key));
});
select('mond');
}
// =====================================================================
// gigawattstunde — Schieberegler GWh, „Strom für X Haushalte einen Tag"
// =====================================================================
function gigawattstunde(root) {
root.innerHTML = `
<div class="ge-wrap">
<div class="ge-head">
<h3>⚡ Wie viel ist eine Gigawattstunde?</h3>
<p>Schiebe den Regler. Sieh, für wie viele Haushalte diese Strommenge einen ganzen Tag lang reicht.</p>
</div>
<svg class="ge-gh-svg" viewBox="0 0 600 180" preserveAspectRatio="xMidYMid meet" aria-hidden="true"></svg>
<div class="ge-gh-slider">
<label for="ge-gw-range">Energiemenge: <strong id="ge-gw-val">1</strong> GWh</label>
<input id="ge-gw-range" type="range" min="1" max="100" value="1" step="1">
<div class="ge-gh-markers"><span>1</span><span>25</span><span>50</span><span>75</span><span>100</span></div>
</div>
<div class="ge-gh-result">
<div class="ge-gh-pill">= <strong data-ref="kwh">1.000.000 kWh</strong></div>
<div class="ge-gh-pill" data-ref="hh" data-state="now" aria-live="polite"></div>
</div>
<p class="ge-note">Ein durchschnittlicher österreichischer Haushalt verbraucht rund 3.500 kWh Strom pro Jahr, also etwa 9,6 kWh pro Tag. Quelle: E-Control. Schon 1 GWh ist eine riesige Strommenge.</p>
</div>
`;
const svg = root.querySelector('.ge-gh-svg');
const rng = root.querySelector('#ge-gw-range');
const valOut = root.querySelector('#ge-gw-val');
const kwhOut = root.querySelector('[data-ref="kwh"]');
const hhOut = root.querySelector('[data-ref="hh"]');
if (!svg || !rng) return;
const PER_DAY = 3500 / 365; // ≈ 9,589 kWh pro Haushalt und Tag
const housesG = svgEl('g', {}, svg);
// jedes Symbol steht für eine feste Anzahl Haushalte
const HOUSES_PER_ICON = 5000;
const COLS = 20;
function drawHouse(x, y, s) {
const g = svgEl('g', { transform: `translate(${x},${y})` }, housesG);
svgEl('rect', { x: -s / 2, y: -s * 0.55, width: s, height: s * 0.7, fill: '#f4c94e' }, g);
svgEl('polygon', { points: `${-s / 2 - 1},${-s * 0.55} 0,${-s * 1.05} ${s / 2 + 1},${-s * 0.55}`, fill: '#c85c4a' }, g);
}
function update() {
const gwh = +rng.value;
valOut.textContent = gwh;
kwhOut.textContent = fmtInt(gwh * 1e6) + ' kWh';
const households = (gwh * 1e6) / PER_DAY;
hhOut.textContent = 'Strom für ca. ' + fmtInt(households) + ' Haushalte einen Tag lang';
housesG.innerHTML = '';
const nIcons = Math.max(1, Math.min(60, Math.round(households / HOUSES_PER_ICON)));
const s = 20;
for (let i = 0; i < nIcons; i++) {
const c = i % COLS, r = Math.floor(i / COLS);
drawHouse(30 + c * 28, 60 + r * 46, s);
}
svgEl('text', { x: 300, y: 168, 'text-anchor': 'middle', 'font-size': '11', 'font-weight': '600', fill: '#4a7c8a' }, housesG)
.textContent = '1 Haus-Symbol ≈ ' + fmtInt(HOUSES_PER_ICON) + ' Haushalte';
}
rng.addEventListener('input', update);
update();
}
// =====================================================================
// netzspannung — Land-Buttons, Volt + Steckertyp + Lade-Hinweis
// =====================================================================
function netzspannung(root) {
root.innerHTML = `
<div class="ge-wrap">
<div class="ge-head">
<h3>🔌 Netzspannung rund um die Welt</h3>
<p>Tippe ein Land an. Sieh die Spannung, die Steckerform und ob dein Handy-Netzteil dort direkt lädt.</p>
</div>
<div class="ge-albedo-choice" role="group">
<button class="ge-btn ge-btn-primary" data-key="at">🇦🇹 Österreich / EU</button>
<button class="ge-btn" data-key="us">🇺🇸 USA</button>
<button class="ge-btn" data-key="jp">🇯🇵 Japan</button>
<button class="ge-btn" data-key="gb">🇬🇧 Großbritannien</button>
</div>
<svg class="ge-gh-svg" viewBox="0 0 600 160" preserveAspectRatio="xMidYMid meet" aria-hidden="true"></svg>
<div class="ge-gh-result">
<div class="ge-gh-pill">Spannung: <strong data-ref="volt">230 V</strong></div>
<div class="ge-gh-pill">Steckertyp: <strong data-ref="plug">Typ C / F</strong></div>
<div class="ge-gh-pill" data-ref="charge" data-state="good" aria-live="polite"></div>
</div>
<p class="ge-note">Nennspannungen und Steckertypen nach IEC: EU 230 V, USA 120 V, Japan 100 V, Großbritannien 230 V. Handy-Netzteile sind fast immer für 100 240 V gebaut und gleichen die Spannung selbst aus — nur die Steckerform ist weltweit verschieden.</p>
</div>
`;
const svg = root.querySelector('.ge-gh-svg');
const voltOut = root.querySelector('[data-ref="volt"]');
const plugOut = root.querySelector('[data-ref="plug"]');
const chargeOut = root.querySelector('[data-ref="charge"]');
if (!svg) return;
const lands = {
at: { volt: 230, plug: 'Typ C / F (Schuko)', home: true },
us: { volt: 120, plug: 'Typ A / B', home: false },
jp: { volt: 100, plug: 'Typ A / B', home: false },
gb: { volt: 230, plug: 'Typ G', home: false }
};
// Spannungs-Skala als Balken (Bezug 240 V)
svgEl('rect', { x: 40, y: 60, width: 520, height: 26, fill: '#ffffff', stroke: '#1f4e5a', 'stroke-width': '1.2', rx: '13' }, svg);
const voltBar = svgEl('rect', { x: 42, y: 62, width: 100, height: 22, fill: '#4a7c8a', rx: '11' }, svg);
const voltLabel = svgEl('text', { x: 300, y: 78, 'text-anchor': 'middle', 'font-size': '14', 'font-weight': '800', fill: '#ffffff' }, svg);
svgEl('text', { x: 40, y: 50, 'font-size': '11', 'font-weight': '700', fill: '#1f4e5a' }, svg).textContent = '0 V';
svgEl('text', { x: 560, y: 50, 'text-anchor': 'end', 'font-size': '11', 'font-weight': '700', fill: '#1f4e5a' }, svg).textContent = '240 V';
// Blitz-Icon + Steckerform
const plugIcon = svgEl('text', { x: 300, y: 135, 'text-anchor': 'middle', 'font-size': '13', 'font-weight': '700', fill: '#1f4e5a' }, svg);
function select(key) {
const L = lands[key] || lands.at;
voltOut.textContent = L.volt + ' V';
plugOut.textContent = L.plug;
voltBar.setAttribute('width', Math.max(20, (516 * L.volt) / 240));
voltBar.setAttribute('fill', L.volt >= 220 ? '#c85c4a' : '#4a7c8a');
voltLabel.textContent = L.volt + ' V';
plugIcon.textContent = 'Steckerform: ' + L.plug;
if (L.home) {
chargeOut.textContent = 'Dein Netzteil lädt direkt — und der Stecker passt (deine Heimat-Norm).';
chargeOut.dataset.state = 'good';
} else {
chargeOut.textContent = 'Dein Handy-Netzteil (100 240 V) lädt direkt — aber für die Steckerform brauchst du einen Reise-Adapter.';
chargeOut.dataset.state = 'warn';
}
root.querySelectorAll('.ge-albedo-choice .ge-btn').forEach(b => {
b.classList.toggle('ge-btn-primary', b.dataset.key === key);
});
}
root.querySelectorAll('.ge-albedo-choice .ge-btn').forEach(b => {
b.addEventListener('click', () => select(b.dataset.key));
});
select('at');
}
// =====================================================================
// kartenmaßstab — Maßstab-Schieberegler, gleicher Ort in versch. Ausschnitten
// =====================================================================
function kartenmassstab(root) {
root.innerHTML = `
<div class="ge-wrap">
<div class="ge-head">
<h3>🗺 Der Kartenmaßstab</h3>
<p>Schiebe den Regler vom Detail bis zum Landausschnitt. Sieh, wie derselbe Ort immer kleiner wird — und das Lineal, wie weit 1 cm auf der Karte in echt sind.</p>
</div>
<svg class="ge-gh-svg" viewBox="0 0 600 260" preserveAspectRatio="xMidYMid meet" aria-hidden="true"></svg>
<div class="ge-gh-slider">
<label for="ge-km-range">Maßstab: <strong>1 : <span id="ge-km-scale">100</span></strong></label>
<input id="ge-km-range" type="range" min="0" max="12" value="0" step="1">
<div class="ge-gh-markers"><span>Detail</span><span>Ort</span><span>Region</span><span>Land</span></div>
</div>
<div class="ge-gh-result">
<div class="ge-gh-pill">Lineal: <strong data-ref="ruler">1 cm = 1 m</strong></div>
<div class="ge-gh-pill" data-ref="view" data-state="now" aria-live="polite"></div>
</div>
<p class="ge-note">Rechenweg: 1 cm auf der Karte · Maßstabszahl = Strecke in echt. Merke: großer Maßstab (kleine Zahl) = kleiner Ausschnitt mit vielen Details, kleiner Maßstab (große Zahl) = großer Ausschnitt mit wenig Details.</p>
</div>
`;
const svg = root.querySelector('.ge-gh-svg');
const rng = root.querySelector('#ge-km-range');
const scaleOut = root.querySelector('#ge-km-scale');
const rulerOut = root.querySelector('[data-ref="ruler"]');
const viewOut = root.querySelector('[data-ref="view"]');
if (!svg || !rng) return;
const scales = [100, 200, 500, 1000, 2000, 5000, 10000, 25000, 50000, 100000, 250000, 500000, 1000000];
const sceneG = svgEl('g', {}, svg);
const rulerG = svgEl('g', {}, svg);
function levelOf(scale) {
if (scale <= 500) return 0; // Haus
if (scale <= 5000) return 1; // Straßenzug
if (scale <= 50000) return 2; // Ort / Viertel
if (scale <= 250000) return 3; // Region
return 4; // Land
}
function drawScene(level) {
sceneG.innerHTML = '';
svgEl('rect', { x: 20, y: 20, width: 560, height: 180, fill: '#eef4ee', stroke: '#c4d4ca', 'stroke-width': '1.5', rx: '8' }, sceneG);
const cx = 300, cy = 110;
if (level === 0) {
// Ein Haus mit Garten
svgEl('rect', { x: 40, y: 40, width: 520, height: 140, fill: '#d8ead0' }, sceneG);
svgEl('rect', { x: 210, y: 70, width: 180, height: 100, fill: '#f4c94e', stroke: '#1f4e5a', 'stroke-width': '2' }, sceneG);
svgEl('polygon', { points: '200,70 300,20 400,70', fill: '#c85c4a' }, sceneG);
svgEl('rect', { x: 285, y: 120, width: 30, height: 50, fill: '#6b4a2e' }, sceneG);
svgEl('polygon', { points: '110,175 128,135 146,175', fill: '#3a6b3e' }, sceneG);
svgEl('polygon', { points: '450,175 468,135 486,175', fill: '#3a6b3e' }, sceneG);
} else if (level === 1) {
// Straßenzug mit mehreren Häusern
svgEl('rect', { x: 40, y: 100, width: 520, height: 28, fill: '#c9cdd2' }, sceneG);
svgEl('line', { x1: 40, y1: 114, x2: 560, y2: 114, stroke: '#ffffff', 'stroke-width': '2', 'stroke-dasharray': '16 12' }, sceneG);
for (let i = 0; i < 6; i++) {
const x = 70 + i * 80;
svgEl('rect', { x, y: 55, width: 44, height: 34, fill: '#f4c94e', stroke: '#1f4e5a', 'stroke-width': '1.2' }, sceneG);
svgEl('polygon', { points: `${x - 3},55 ${x + 22},38 ${x + 47},55`, fill: '#c85c4a' }, sceneG);
svgEl('rect', { x: x + 6, y: 138, width: 36, height: 26, fill: '#a9c99a' }, sceneG);
}
} else if (level === 2) {
// Ort / Viertel: Häuserblöcke + Straßenraster
svgEl('rect', { x: 40, y: 40, width: 520, height: 140, fill: '#dfe8df' }, sceneG);
for (let gx = 0; gx < 5; gx++) for (let gy = 0; gy < 3; gy++) {
svgEl('rect', { x: 70 + gx * 96, y: 55 + gy * 42, width: 70, height: 28, fill: '#c4a97a', stroke: '#8a7256', 'stroke-width': '1' }, sceneG);
}
for (let i = 1; i < 5; i++) svgEl('line', { x1: 40 + i * 104, y1: 40, x2: 40 + i * 104, y2: 180, stroke: '#c9cdd2', 'stroke-width': '6' }, sceneG);
for (let i = 1; i < 3; i++) svgEl('line', { x1: 40, y1: 40 + i * 47, x2: 560, y2: 40 + i * 47, stroke: '#c9cdd2', 'stroke-width': '6' }, sceneG);
} else if (level === 3) {
// Region: Grün-/Wasserflächen, Fluss, Ortspunkte
svgEl('rect', { x: 40, y: 40, width: 520, height: 140, fill: '#cfe3c4' }, sceneG);
svgEl('path', { d: 'M 40 60 Q 200 90 300 70 T 560 120', stroke: '#4a9cc4', 'stroke-width': '9', fill: 'none', 'stroke-linecap': 'round' }, sceneG);
svgEl('polygon', { points: '380,90 470,110 440,175 350,150', fill: '#b7d59e' }, sceneG);
[ [140, 120], [300, 150], [470, 70], [230, 90] ].forEach(([x, y]) => {
svgEl('circle', { cx: x, cy: y, r: 7, fill: '#c85c4a', stroke: '#fff', 'stroke-width': '1.5' }, sceneG);
});
} else {
// Land: großer Ausschnitt mit Städten
svgEl('rect', { x: 40, y: 40, width: 520, height: 140, fill: '#cfe3c4' }, sceneG);
svgEl('path', { d: 'M 60 150 Q 120 60 240 90 Q 360 120 440 70 Q 520 40 545 120 L 545 175 L 60 175 Z', fill: '#b7d59e', stroke: '#6f9c5f', 'stroke-width': '2' }, sceneG);
svgEl('path', { d: 'M 40 40 Q 120 55 180 45 L 180 40 Z', fill: '#9cc4e4' }, sceneG);
[ [140, 130, 'groß'], [300, 100, 'mittel'], [430, 130, 'klein'], [230, 155, 'klein'] ].forEach(([x, y, sz]) => {
const r = sz === 'groß' ? 8 : (sz === 'mittel' ? 6 : 4);
svgEl('circle', { cx: x, cy: y, r, fill: '#c85c4a', stroke: '#fff', 'stroke-width': '1.5' }, sceneG);
});
}
}
function drawRuler(scale) {
rulerG.innerHTML = '';
// Referenzlinie „1 cm auf der Karte" (fixe Länge 90 px als Anschauung)
const x0 = 40, x1 = 130, y = 218;
svgEl('line', { x1: x0, y1: y, x2: x1, y2: y, stroke: '#1f4e5a', 'stroke-width': '3', 'stroke-linecap': 'round' }, rulerG);
svgEl('line', { x1: x0, y1: y - 6, x2: x0, y2: y + 6, stroke: '#1f4e5a', 'stroke-width': '3' }, rulerG);
svgEl('line', { x1: x1, y1: y - 6, x2: x1, y2: y + 6, stroke: '#1f4e5a', 'stroke-width': '3' }, rulerG);
svgEl('text', { x: 85, y: y + 24, 'text-anchor': 'middle', 'font-size': '12', 'font-weight': '700', fill: '#1f4e5a' }, rulerG).textContent = '1 cm auf der Karte';
const realM = scale / 100; // 1 cm * scale = scale cm = scale/100 m
let real;
if (realM >= 1000) real = fmtDec(realM / 1000, realM % 1000 === 0 ? 0 : 1) + ' km';
else real = fmtInt(realM) + ' m';
svgEl('text', { x: 150, y: y + 4, 'font-size': '14', 'font-weight': '800', fill: '#c85c4a' }, rulerG).textContent = '= ' + real + ' in echt';
return real;
}
function update() {
const scale = scales[+rng.value];
scaleOut.textContent = fmtInt(scale);
const level = levelOf(scale);
drawScene(level);
const real = drawRuler(scale);
rulerOut.textContent = '1 cm = ' + real;
const viewNames = ['Einzelnes Haus mit Garten', 'Ein Straßenzug', 'Ein Ort / Stadtviertel', 'Eine ganze Region', 'Großer Landausschnitt'];
const big = scale <= 5000;
viewOut.textContent = viewNames[level] + ' — ' + (big ? 'großer Maßstab, viele Details' : 'kleiner Maßstab, wenig Details');
viewOut.dataset.state = big ? 'good' : (level >= 3 ? 'warn' : 'now');
}
rng.addEventListener('input', update);
update();
}
// Registry — Keys entsprechen key_slug aus DB.
window.GlossarExperiments = Object.assign(window.GlossarExperiments || {}, {
'gletscher': gletscher,
'lawinenwarnstufe': lawinenwarnstufe,
'lichtgeschwindigkeit': lichtgeschwindigkeit,
'gigawattstunde': gigawattstunde,
'netzspannung': netzspannung,
'kartenmaßstab': kartenmassstab
});
})();