diff --git a/App/assets/js/glossar-experiments-geowelt.js b/App/assets/js/glossar-experiments-geowelt.js
new file mode 100644
index 0000000..78d2801
--- /dev/null
+++ b/App/assets/js/glossar-experiments-geowelt.js
@@ -0,0 +1,576 @@
+/**
+ * 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 = `
+
+
+
🏔 Wie ein Alpengletscher schrumpft
+
Schiebe durch die Jahre. Beobachte, wie sich die Gletscherzunge das Tal hinaufzieht und der Eis-Vorrat sinkt.
+
+
+
+
Jahr: 1900
+
+
1900 1950 2000 heute
+
+
+
Eis-Vorrat (1900 = 100 %): 100 %
+
noch fast unberührt
+
+
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).
+
+ `;
+ 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 = `
+
+
+
🏔 Die Lawinen-Warnstufen
+
Stelle die Warnstufe ein. Achte darauf: Die Gefahr steigt nicht gleichmäßig — der Sprung von Stufe 3 auf 4 ist besonders groß.
+
+
+
+
Warnstufe: 1 — gering
+
+
1 2 3 4 5
+
+
+
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.
+
+ `;
+ 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 = `
+
+
+
💡 Wie lange braucht das Licht?
+
Wähle ein Ziel. Ein Lichtblitz startet auf der Erde. Sieh, wie lange das Licht bis dorthin unterwegs ist.
+
+
+ 🌙 Mond
+ ☀ Sonne
+ 🔴 Mars
+ ⭐ Proxima Centauri
+
+
+
+
Entfernung: 384.400 km
+
Lichtlaufzeit: 1,3 Sekunden
+
+
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.
+
+ `;
+ 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 = `
+
+
+
⚡ Wie viel ist eine Gigawattstunde?
+
Schiebe den Regler. Sieh, für wie viele Haushalte diese Strommenge einen ganzen Tag lang reicht.
+
+
+
+
Energiemenge: 1 GWh
+
+
1 25 50 75 100
+
+
+
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.
+
+ `;
+ 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 = `
+
+
+
🔌 Netzspannung rund um die Welt
+
Tippe ein Land an. Sieh die Spannung, die Steckerform und ob dein Handy-Netzteil dort direkt lädt.
+
+
+ 🇦🇹 Österreich / EU
+ 🇺🇸 USA
+ 🇯🇵 Japan
+ 🇬🇧 Großbritannien
+
+
+
+
Spannung: 230 V
+
Steckertyp: Typ C / F
+
+
+
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.
+
+ `;
+ 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 = `
+
+
+
🗺 Der Kartenmaßstab
+
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.
+
+
+
+
Maßstab: 1 : 100
+
+
Detail Ort Region Land
+
+
+
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.
+
+ `;
+ 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
+ });
+})();
diff --git a/App/assets/js/glossar-experiments-tourismus.js b/App/assets/js/glossar-experiments-tourismus.js
new file mode 100644
index 0000000..33b59c2
--- /dev/null
+++ b/App/assets/js/glossar-experiments-tourismus.js
@@ -0,0 +1,554 @@
+/**
+ * Glossar-Experimente — Tourismus.
+ *
+ * Registrierung wie in glossar-experiments.js:
+ * 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.
+ *
+ * Richtlinien:
+ * - Keine externen Libraries, Vanilla JS + SVG.
+ * - Mobile-/Tap-freundlich: grosse Buttons, keine Hover-Abhängigkeit.
+ * - Zahlen quellenbar, kurze Begründung in der UI sichtbar (.ge-note).
+ * - Rechenzeichen: Malpunkt · und Doppelpunkt : (nie ×, ÷, *).
+ * - Deutsche Zahlen via toLocaleString('de-AT').
+ */
+
+(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, digits) => n.toLocaleString('de-AT', {
+ minimumFractionDigits: digits, maximumFractionDigits: digits
+ });
+
+ // =====================================================================
+ // Experiment: Beschneiung — Schneekanone und die Feuchtkugel-Grenze
+ // =====================================================================
+ function beschneiung(root) {
+ root.innerHTML = `
+
+
+
❄ Wann kann die Schneekanone arbeiten?
+
Eine Schneekanone verwandelt Wasser nur dann in Schnee, wenn es kalt genug ist.
+ Schiebe den Regler und beobachte, ab welcher Temperatur echter Schnee entsteht.
+
+
+
+
Lufttemperatur: −5 °C
+
+
−8 °C −2 °C 0 °C +4 °C
+
+
+
🟢 Schneekanone AN
+
Schneehöhe: —
+
+
Faustregel der Pisten-Technik: Beschneiung gelingt erst unterhalb einer
+ Feuchtkugeltemperatur von rund −2 °C. Je kälter (und trockener) die Luft, desto mehr Schnee
+ je Stunde. Über der Grenze fällt das Wasser nur nass herunter. Quelle: Seilbahnen Österreich,
+ Beschneiungsleitfaden.
+
+ `;
+ const svg = root.querySelector('.ge-gh-svg');
+ const rng = root.querySelector('#ge-bs-range');
+ const tempOut = root.querySelector('#ge-bs-temp');
+ const stateP = root.querySelector('#ge-bs-state');
+ const snowP = root.querySelector('#ge-bs-snow');
+ const THRESHOLD = -2;
+
+ // Himmel + Berg
+ svgEl('rect', { x: 0, y: 0, width: 600, height: 260, fill: '#eff5f6' }, svg);
+ svgEl('polygon', { points: '360,260 470,90 520,140 600,60 600,260', fill: '#cddbe0' }, svg);
+ // Piste (Boden)
+ const PISTE_Y = 210;
+ svgEl('rect', { x: 0, y: PISTE_Y, width: 600, height: 50, fill: '#8aa39a' }, svg);
+ // Schneekanone links
+ svgEl('rect', { x: 60, y: PISTE_Y - 22, width: 16, height: 22, fill: '#4a4a4a', rx: '2' }, svg);
+ svgEl('rect', { x: 74, y: PISTE_Y - 30, width: 48, height: 18, fill: '#1f4e5a', rx: '6' }, svg);
+ svgEl('circle', { cx: 122, cy: PISTE_Y - 21, r: 11, fill: '#4a7c8a', stroke: '#1f4e5a', 'stroke-width': '2' }, svg);
+ svgEl('text', { x: 68, y: PISTE_Y - 40, 'font-size': '11', 'font-weight': '700', fill: '#1f4e5a' }, svg).textContent = 'Schneekanone';
+
+ // Schneeband auf der Piste (wächst mit Kälte)
+ const snowBand = svgEl('rect', { x: 140, y: PISTE_Y, width: 440, height: 0, fill: '#ffffff', stroke: '#b5c9cf', 'stroke-width': '1' }, svg);
+ // Dynamische Partikel-Gruppe (Schnee oder Tropfen)
+ const spray = svgEl('g', {}, svg);
+ // Status-Text in der Szene
+ const sceneMsg = svgEl('text', { x: 360, y: 40, 'text-anchor': 'middle', 'font-size': '15', 'font-weight': '800', fill: '#1f4e5a' }, svg);
+
+ function update() {
+ const temp = +rng.value;
+ tempOut.textContent = (temp > 0 ? '+' : '') + temp + ' °C';
+ spray.innerHTML = '';
+ const on = temp <= THRESHOLD;
+ // coldFactor: 0 an der Grenze (−2), 1 bei −8
+ const coldFactor = Math.max(0, Math.min(1, (THRESHOLD - temp) / 6));
+
+ if (on) {
+ stateP.textContent = '🟢 Schneekanone AN';
+ stateP.dataset.state = 'good';
+ // Schneeband wächst
+ const h = 6 + coldFactor * 42;
+ snowBand.setAttribute('y', PISTE_Y - h);
+ snowBand.setAttribute('height', h);
+ // Schneeflocken aus der Kanone
+ const nFlakes = 10 + Math.round(coldFactor * 30);
+ for (let i = 0; i < nFlakes; i++) {
+ const t = i / nFlakes;
+ const px = 130 + t * 300 + (Math.random() * 30 - 15);
+ const py = (PISTE_Y - 25) - Math.sin(t * Math.PI) * 70 + (Math.random() * 16 - 8);
+ svgEl('circle', { cx: px, cy: py, r: 3.2, fill: '#ffffff', stroke: '#cddbe0', 'stroke-width': '.8' }, spray);
+ }
+ sceneMsg.textContent = '❄ Echter Schnee entsteht';
+ const cmPerH = 2 + coldFactor * 10; // anschaulicher Richtwert
+ snowP.innerHTML = 'Schnee-Leistung: ' + fmtDec(cmPerH, 0) + ' cm je Stunde ';
+ } else {
+ stateP.textContent = '🟠 Schneekanone AUS';
+ stateP.dataset.state = 'warn';
+ snowBand.setAttribute('height', 0);
+ // Nasse Tropfen fallen einfach herunter
+ for (let i = 0; i < 14; i++) {
+ const px = 130 + Math.random() * 300;
+ const py = (PISTE_Y - 20) - Math.random() * 40;
+ svgEl('ellipse', { cx: px, cy: py, rx: 2.4, ry: 4, fill: '#4a7c8a', opacity: '0.75' }, spray);
+ }
+ sceneMsg.textContent = '💧 Zu warm — nur nasses Wasser';
+ snowP.innerHTML = 'Schnee-Leistung: 0 cm — kein Schnee ';
+ }
+ }
+ rng.addEventListener('input', update);
+ update();
+ }
+
+ // =====================================================================
+ // Experiment: Diversifizierung — mehrere Standbeine sichern den Umsatz
+ // =====================================================================
+ function diversifizierung(root) {
+ root.innerHTML = `
+
+
+
🌗 Ein Standbein oder mehrere?
+
Ein Urlaubsort lebt vom Winter. Stelle ein, wie gut der Winter ausfällt, und schalte
+ Sommer-Standbeine dazu. Beobachte, was mit dem Jahresumsatz passiert.
+
+
+
Winter: guter Winter
+
+
schneearm guter Winter
+
+
+ ☀ Sommer-Standbeine dazuschalten
+
+
+
+
Modellzahlen: höchstmöglicher Jahresumsatz 500.000 €. Ohne zweites Standbein
+ hängt alles am Winter — ein schneearmer Winter bricht den Umsatz fast völlig weg. Mit
+ Sommer-Angeboten (Wandern, Bike, See) bleibt rund die Hälfte des Umsatzes auch ohne Schnee
+ erhalten. Genau das meint Diversifizierung: das Risiko auf mehrere Standbeine verteilen.
+
+ `;
+ const rng = root.querySelector('#ge-dv-range');
+ const winterOut = root.querySelector('#ge-dv-winter');
+ const sumOut = root.querySelector('#ge-dv-sum');
+ const verdict = root.querySelector('#ge-dv-verdict');
+ const btn = root.querySelector('[data-act="summer"]');
+ const svg = root.querySelector('.ge-bal-svg');
+ const MAX = 500000, BAR_X = 130, BAR_W = 400, BAR_Y = 40, BAR_H = 34;
+ let summer = false;
+
+ // Balken-Rahmen
+ svgEl('text', { x: BAR_X - 10, y: BAR_Y + 22, 'text-anchor': 'end', 'font-size': '13', 'font-weight': '700', fill: '#1f4e5a' }, svg).textContent = 'Umsatz';
+ svgEl('rect', { x: BAR_X, y: BAR_Y, width: BAR_W, height: BAR_H, fill: '#eff5f6', rx: '8' }, svg);
+ const bar = svgEl('rect', { x: BAR_X, y: BAR_Y, width: 0, height: BAR_H, fill: '#5a8a5e', rx: '8' }, svg);
+ const barTxt = svgEl('text', { x: BAR_X + 8, y: BAR_Y + 22, 'font-size': '13', 'font-weight': '800', fill: '#1f4e5a' }, svg);
+ // Winter-Anteil-Markierung (bei summer der Sockel)
+ svgEl('text', { x: BAR_X, y: 108, 'font-size': '11', fill: '#4a4a4a' }, svg).textContent = '0 €';
+ svgEl('text', { x: BAR_X + BAR_W, y: 108, 'text-anchor': 'end', 'font-size': '11', fill: '#4a4a4a' }, svg).textContent = '500.000 €';
+ const sockelLine = svgEl('line', { x1: BAR_X, y1: BAR_Y - 6, x2: BAR_X, y2: BAR_Y + BAR_H + 6, stroke: '#e8833a', 'stroke-width': '2', 'stroke-dasharray': '4 3', opacity: '0' }, svg);
+ const sockelTxt = svgEl('text', { x: BAR_X, y: BAR_Y - 10, 'text-anchor': 'middle', 'font-size': '10', 'font-weight': '700', fill: '#e8833a', opacity: '0' }, svg);
+
+ function update() {
+ const winter = +rng.value; // 0..100
+ const wf = winter / 100;
+ winterOut.textContent = winter >= 66 ? 'guter Winter' : (winter >= 33 ? 'mittlerer Winter' : 'schneearmer Winter');
+ let umsatz, summerFloor;
+ if (summer) {
+ // 40 % feste Sommerbasis + 60 % vom Winter
+ summerFloor = MAX * 0.4;
+ umsatz = summerFloor + MAX * 0.6 * wf;
+ } else {
+ summerFloor = 0;
+ umsatz = MAX * wf;
+ }
+ const w = (umsatz / MAX) * BAR_W;
+ bar.setAttribute('width', w);
+ bar.setAttribute('fill', umsatz < MAX * 0.25 ? '#c85c4a' : (umsatz < MAX * 0.55 ? '#e8833a' : '#5a8a5e'));
+ barTxt.setAttribute('x', Math.min(BAR_X + w + 8, BAR_X + BAR_W - 92));
+ barTxt.textContent = fmtInt(umsatz) + ' €';
+
+ // Sommer-Sockel-Linie
+ if (summer) {
+ const sx = BAR_X + (summerFloor / MAX) * BAR_W;
+ sockelLine.setAttribute('x1', sx); sockelLine.setAttribute('x2', sx); sockelLine.setAttribute('opacity', '1');
+ sockelTxt.setAttribute('x', sx); sockelTxt.setAttribute('opacity', '1');
+ sockelTxt.textContent = 'Sommer-Sockel';
+ } else {
+ sockelLine.setAttribute('opacity', '0');
+ sockelTxt.setAttribute('opacity', '0');
+ }
+
+ sumOut.textContent = fmtInt(umsatz) + ' €';
+ btn.classList.toggle('ge-btn-primary', summer);
+ btn.textContent = summer ? '☀ Sommer-Standbeine AN (zum Abschalten tippen)' : '☀ Sommer-Standbeine dazuschalten';
+
+ if (summer && winter < 33) {
+ verdict.textContent = '🟢 Schneearm — trotzdem stabil dank Sommer';
+ verdict.dataset.state = 'good';
+ } else if (!summer && winter < 33) {
+ verdict.textContent = '🔴 Nur Winter — schneearm bricht den Umsatz weg';
+ verdict.dataset.state = 'bad';
+ } else if (summer) {
+ verdict.textContent = '🟢 Zwei Standbeine — sicher aufgestellt';
+ verdict.dataset.state = 'good';
+ } else {
+ verdict.textContent = '🟡 Ein Standbein — alles hängt am Winter';
+ verdict.dataset.state = 'warn';
+ }
+ }
+ rng.addEventListener('input', update);
+ btn.addEventListener('click', () => { summer = !summer; update(); });
+ update();
+ }
+
+ // =====================================================================
+ // Experiment: Kapazität — 30 Plätze sind die Obergrenze
+ // =====================================================================
+ function kapazitaet(root) {
+ root.innerHTML = `
+
+
+
☕ Das Café hat 30 Plätze
+
Ein Bergcafé hat genau 30 Stühle. Stelle ein, wie viele Gäste in einer Stunde kommen —
+ und beobachte, wer noch Platz findet und wer warten muss.
+
+
+
+
Gäste pro Stunde: 24
+
+
0 30 = Kapazität 55
+
+
+
Sitzen: 24 · Warten: 0
+
…
+
+
Die Kapazität ist die feste Obergrenze: Ab dem 31. Gast finden keine weiteren
+ mehr Platz — sie müssen warten oder bekommen eine Absage. Mehr Nachfrage füllt die vorhandenen
+ Plätze, erhöht aber nie die Kapazität. Modellbeispiel Bergcafé mit 30 Sitzplätzen.
+
+ `;
+ const svg = root.querySelector('.ge-bal-svg');
+ const rng = root.querySelector('#ge-kp-range');
+ const guestsOut = root.querySelector('#ge-kp-guests');
+ const sitOut = root.querySelector('#ge-kp-sit');
+ const waitOut = root.querySelector('#ge-kp-wait');
+ const verdict = root.querySelector('#ge-kp-verdict');
+ const CAP = 30;
+
+ // Café-Bereich (Stuhl-Raster 6 x 5) links, Warteschlange rechts
+ svgEl('rect', { x: 14, y: 14, width: 372, height: 212, fill: '#eff5f6', stroke: '#b5c9cf', 'stroke-width': '1.5', rx: '10' }, svg);
+ svgEl('text', { x: 24, y: 34, 'font-size': '12', 'font-weight': '800', fill: '#1f4e5a' }, svg).textContent = '☕ Café — 30 Plätze';
+ svgEl('text', { x: 470, y: 34, 'text-anchor': 'middle', 'font-size': '12', 'font-weight': '800', fill: '#c85c4a' }, svg).textContent = 'Warteschlange';
+
+ const seats = [];
+ const COLS = 6, ROWS = 5;
+ for (let i = 0; i < CAP; i++) {
+ const c = i % COLS, r = Math.floor(i / COLS);
+ const cx = 46 + c * 58, cy = 66 + r * 34;
+ // Stuhl-Slot
+ svgEl('circle', { cx, cy, r: 13, fill: '#ffffff', stroke: '#b5c9cf', 'stroke-width': '1.5' }, svg);
+ const occ = svgEl('circle', { cx, cy, r: 10, fill: '#5a8a5e', opacity: '0' }, svg);
+ const head = svgEl('circle', { cx, cy: cy - 4, r: 3.4, fill: '#ffffff', opacity: '0' }, svg);
+ seats.push({ occ, head });
+ }
+ const queueG = svgEl('g', {}, svg);
+
+ function update() {
+ const guests = +rng.value;
+ const sit = Math.min(guests, CAP);
+ const wait = Math.max(0, guests - CAP);
+ guestsOut.textContent = guests;
+ sitOut.textContent = sit;
+ waitOut.textContent = wait;
+ seats.forEach((s, i) => {
+ const on = i < sit ? '1' : '0';
+ s.occ.setAttribute('opacity', on);
+ s.head.setAttribute('opacity', on);
+ });
+ // Warteschlange zeichnen (max 25 Figuren sichtbar)
+ queueG.innerHTML = '';
+ const shown = Math.min(wait, 25);
+ for (let i = 0; i < shown; i++) {
+ const c = i % 5, r = Math.floor(i / 5);
+ const x = 418 + c * 30, y = 60 + r * 32;
+ svgEl('circle', { cx: x, cy: y, r: 9, fill: '#c85c4a' }, queueG);
+ svgEl('circle', { cx: x, cy: y - 3, r: 3, fill: '#ffffff' }, queueG);
+ }
+ if (wait > 25) {
+ svgEl('text', { x: 470, y: 216, 'text-anchor': 'middle', 'font-size': '12', 'font-weight': '800', fill: '#c85c4a' }, queueG).textContent = '+ ' + (wait - 25) + ' weitere';
+ }
+
+ if (wait === 0 && guests < CAP) {
+ verdict.textContent = '🟢 Alle ' + guests + ' Gäste finden Platz';
+ verdict.dataset.state = 'good';
+ } else if (wait === 0) {
+ verdict.textContent = '🟡 Café voll — genau an der Kapazität';
+ verdict.dataset.state = 'warn';
+ } else {
+ verdict.textContent = '🔴 ' + wait + ' Gäste müssen warten oder absagen';
+ verdict.dataset.state = 'bad';
+ }
+ }
+ rng.addEventListener('input', update);
+ update();
+ }
+
+ // =====================================================================
+ // Experiment: Saisonalität — Ganzjahrestourismus glättet die Kurve
+ // =====================================================================
+ function saisonalitaet(root) {
+ root.innerHTML = `
+
+
+
📅 Volle und leere Monate
+
Viele Bergorte leben fast nur vom Winter. Schalte Sommer-Angebote dazu und beobachte,
+ wie sich die Gäste übers Jahr verteilen.
+
+
+
+ ☀ Sommer-Angebote einschalten
+
+
+
Höchster Monat: — · Schwächster: —
+
…
+
+
Die starke Ballung auf wenige Monate heißt Saisonalität. Ganzjahrestourismus
+ (Wandern, Bike, Seen, Kultur im Sommer) verteilt die Nachfrage gleichmäßiger übers Jahr — das
+ sichert Arbeitsplätze und entlastet die Spitzen. Modellhafte Nachfragewerte, angelehnt an das
+ Muster alpiner Wintersportorte.
+
+ `;
+ const svg = root.querySelector('.ge-bal-svg');
+ const btn = root.querySelector('[data-act="summer"]');
+ const maxOut = root.querySelector('#ge-sa-max');
+ const minOut = root.querySelector('#ge-sa-min');
+ const verdict = root.querySelector('#ge-sa-verdict');
+ const months = ['Jän', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'];
+ const winterOnly = [85, 90, 70, 30, 18, 22, 26, 30, 22, 20, 42, 80];
+ const withSummer = [85, 90, 72, 42, 48, 70, 80, 82, 66, 48, 45, 80];
+ let summer = false;
+
+ const BASE_Y = 180, MAXH = 140, BW = 34, GAP = 12, X0 = 60;
+ svgEl('line', { x1: 40, y1: BASE_Y, x2: 580, y2: BASE_Y, stroke: '#b5c9cf', 'stroke-width': '1.5' }, svg);
+ const bars = months.map((m, i) => {
+ const x = X0 + i * (BW + GAP);
+ const bar = svgEl('rect', { x, y: BASE_Y, width: BW, height: 0, fill: '#4a7c8a', rx: '4' }, svg);
+ svgEl('text', { x: x + BW / 2, y: BASE_Y + 16, 'text-anchor': 'middle', 'font-size': '10', 'font-weight': '600', fill: '#4a4a4a' }, svg).textContent = m;
+ return bar;
+ });
+
+ function update() {
+ const data = summer ? withSummer : winterOnly;
+ let maxV = -1, minV = 999, maxI = 0, minI = 0;
+ data.forEach((v, i) => {
+ if (v > maxV) { maxV = v; maxI = i; }
+ if (v < minV) { minV = v; minI = i; }
+ });
+ bars.forEach((bar, i) => {
+ const h = data[i] / 100 * MAXH;
+ bar.setAttribute('y', BASE_Y - h);
+ bar.setAttribute('height', h);
+ // Sommermonate hervorheben, wenn eingeschaltet
+ const isSummer = i >= 4 && i <= 9;
+ bar.setAttribute('fill', summer && isSummer ? '#5a8a5e' : '#4a7c8a');
+ });
+ maxOut.textContent = months[maxI];
+ minOut.textContent = months[minI];
+ btn.classList.toggle('ge-btn-primary', summer);
+ btn.textContent = summer ? '☀ Sommer-Angebote AN (zum Abschalten tippen)' : '☀ Sommer-Angebote einschalten';
+ const spread = maxV - minV;
+ if (summer) {
+ verdict.textContent = '🟢 Gleichmäßiger — Unterschied nur noch ' + spread + ' Punkte';
+ verdict.dataset.state = 'good';
+ } else {
+ verdict.textContent = '🔴 Winterlastig — Unterschied ' + spread + ' Punkte';
+ verdict.dataset.state = 'bad';
+ }
+ }
+ btn.addEventListener('click', () => { summer = !summer; update(); });
+ update();
+ }
+
+ // =====================================================================
+ // Experiment: Übertourismus — Gäste je Einwohner (Hallstatt)
+ // =====================================================================
+ function uebertourismus(root) {
+ root.innerHTML = `
+
+
+
🏘 Wie viele Gäste verträgt ein kleiner Ort?
+
Hallstatt in Oberösterreich hat rund 760 Einwohner:innen . Schiebe den
+ Regler und sieh, wie viele Gäste je Einwohner:in an einem Tag im Ort sind.
+
+
+
+
Gäste pro Tag: 3.000
+
+
0 5.000 10.000
+
+
+
Je Einwohner:in: — Gäste
+
…
+
+
Hallstatt: rund 760 Einwohner:innen, an Spitzentagen bis zu 10.000 Gäste —
+ das sind über 13 Gäste je Einwohner:in. So viel Andrang überlastet Wege, Verkehr und
+ Alltag der Menschen vor Ort. Das nennt man Übertourismus. Quellen: Gemeinde Hallstatt,
+ Statistik Austria.
+
+ `;
+ const svg = root.querySelector('.ge-bal-svg');
+ const rng = root.querySelector('#ge-ut-range');
+ const guestsOut = root.querySelector('#ge-ut-guests');
+ const ratioOut = root.querySelector('#ge-ut-ratio');
+ const verdict = root.querySelector('#ge-ut-verdict');
+ const RESIDENTS = 760, MAX_GUESTS = 10000;
+ const BAR_X = 20, BAR_W = 560, BAR_Y = 44, BAR_H = 40;
+
+ svgEl('text', { x: BAR_X, y: 30, 'font-size': '12', 'font-weight': '700', fill: '#1f4e5a' }, svg).textContent = 'Belastung des Ortes';
+ svgEl('rect', { x: BAR_X, y: BAR_Y, width: BAR_W, height: BAR_H, fill: '#eff5f6', stroke: '#b5c9cf', 'stroke-width': '1', rx: '10' }, svg);
+ const bar = svgEl('rect', { x: BAR_X, y: BAR_Y, width: 0, height: BAR_H, fill: '#5a8a5e', rx: '10' }, svg);
+ const barTxt = svgEl('text', { x: BAR_X + BAR_W / 2, y: BAR_Y + 26, 'text-anchor': 'middle', 'font-size': '14', 'font-weight': '800', fill: '#1f4e5a' }, svg);
+ svgEl('text', { x: BAR_X, y: 108, 'font-size': '11', fill: '#5a8a5e', 'font-weight': '700' }, svg).textContent = '🟢 entspannt';
+ svgEl('text', { x: BAR_X + BAR_W, y: 108, 'text-anchor': 'end', 'font-size': '11', fill: '#c85c4a', 'font-weight': '700' }, svg).textContent = 'überlastet 🔴';
+
+ function update() {
+ const guests = +rng.value;
+ const ratio = guests / RESIDENTS;
+ guestsOut.textContent = fmtInt(guests);
+ ratioOut.textContent = fmtDec(ratio, 1);
+ const w = (guests / MAX_GUESTS) * BAR_W;
+ bar.setAttribute('width', w);
+ barTxt.textContent = fmtDec(ratio, 1) + ' Gäste je Einwohner:in';
+ let color, v, state;
+ if (ratio < 2) { color = '#5a8a5e'; v = '🟢 Entspannt — der Ort verkraftet das gut'; state = 'good'; }
+ else if (ratio < 6) { color = '#e8833a'; v = '🟡 Viel los — Wege und Verkehr werden eng'; state = 'warn'; }
+ else { color = '#c85c4a'; v = '🔴 Übertourismus — der Ort ist überlastet'; state = 'bad'; }
+ bar.setAttribute('fill', color);
+ verdict.textContent = v;
+ verdict.dataset.state = state;
+ }
+ rng.addEventListener('input', update);
+ update();
+ }
+
+ // =====================================================================
+ // Experiment: Bevölkerungsdichte — Menschen je Quadratkilometer
+ // =====================================================================
+ function bevoelkerungsdichte(root) {
+ root.innerHTML = `
+
+
+
🧑🤝🧑 Wie eng wohnen die Menschen?
+
Jedes Feld unten ist genau 1 km² groß. Wähle einen Ort und sieh, wie viele
+ Menschen dort auf diesem einen Quadratkilometer leben.
+
+
+ Bergtal
+ Österreich ⌀
+ Stadt (Wien)
+ Monaco
+
+
+
+
Dichte: — Menschen je km²
+
…
+
+
Bevölkerungsdichte = Menschen : Fläche. Bergtal rund 5, Österreich im Schnitt
+ 109, Wien rund 4.600 und Monaco rund 26.000 Menschen je km². Ein Punkt steht hier für
+ 50 Menschen. Quellen: Statistik Austria (2024), Statista.
+
+ `;
+ const svg = root.querySelector('.ge-bal-svg');
+ const valOut = root.querySelector('#ge-bd-val');
+ const verdict = root.querySelector('#ge-bd-verdict');
+ const PER_DOT = 50;
+ const places = {
+ tal: { label: 'Bergtal', density: 5, v: '🟢 Sehr dünn besiedelt — viel Platz', state: 'good' },
+ at: { label: 'Österreich ⌀', density: 109, v: '🟢 Ländlich-durchschnittlich', state: 'good' },
+ wien: { label: 'Stadt (Wien)', density: 4600, v: '🟡 Dicht — typische Großstadt', state: 'warn' },
+ monaco: { label: 'Monaco', density: 26000, v: '🔴 Extrem dicht — dichtestes Land der Welt', state: 'bad' }
+ };
+
+ // 1-km²-Feld (Quadrat)
+ const FX = 175, FY = 20, FS = 260;
+ svgEl('rect', { x: FX, y: FY, width: FS, height: FS, fill: '#eef4ef', stroke: '#5a8a5e', 'stroke-width': '2', rx: '6' }, svg);
+ svgEl('text', { x: FX + FS / 2, y: FY + FS + 20, 'text-anchor': 'middle', 'font-size': '12', 'font-weight': '700', fill: '#1f4e5a' }, svg).textContent = '1 km² · 1 Punkt = 50 Menschen';
+ const dotG = svgEl('g', {}, svg);
+
+ // Feste Punkt-Positionen (Raster mit Jitter), damit der Vergleich fair bleibt
+ const CAP = 600; // max sichtbare Punkte (25 x 24)
+ const positions = [];
+ const gcols = 25, grows = 24;
+ const cellW = (FS - 16) / gcols, cellH = (FS - 16) / grows;
+ for (let r = 0; r < grows; r++) {
+ for (let c = 0; c < gcols; c++) {
+ positions.push({
+ x: FX + 8 + c * cellW + cellW / 2,
+ y: FY + 8 + r * cellH + cellH / 2
+ });
+ }
+ }
+
+ function setPlace(key) {
+ const p = places[key] || places.at;
+ const nDots = Math.min(CAP, Math.round(p.density / PER_DOT));
+ dotG.innerHTML = '';
+ for (let i = 0; i < nDots; i++) {
+ const pos = positions[i];
+ svgEl('circle', { cx: pos.x, cy: pos.y, r: 2.6, fill: '#c85c4a', opacity: '0.9' }, dotG);
+ }
+ if (nDots === 0) {
+ // wenigstens 1 Mensch sichtbar bei sehr geringer Dichte
+ svgEl('circle', { cx: positions[0].x, cy: positions[0].y, r: 3, fill: '#5a8a5e' }, dotG);
+ }
+ valOut.textContent = fmtInt(p.density);
+ verdict.textContent = p.v;
+ verdict.dataset.state = p.state;
+ 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', () => setPlace(b.dataset.key));
+ });
+ setPlace('at');
+ }
+
+ // Registry — Keys entsprechen key_slug aus der Tabelle `glossar`
+ window.GlossarExperiments = Object.assign(window.GlossarExperiments || {}, {
+ 'beschneiung': beschneiung,
+ 'diversifizierung': diversifizierung,
+ 'kapazitaet': kapazitaet,
+ 'saisonalitaet': saisonalitaet,
+ 'uebertourismus': uebertourismus,
+ 'bevoelkerungsdichte': bevoelkerungsdichte
+ });
+})();
diff --git a/App/pages/glossar.php b/App/pages/glossar.php
index 67062ab..48907e8 100644
--- a/App/pages/glossar.php
+++ b/App/pages/glossar.php
@@ -647,6 +647,8 @@ include __DIR__ . '/_partials/erkundungs_subnav.php';
+
+