/**
* Glossar-Experimente „neu2" — Optik/Schatten, Verkehr, EU-Binnenmarkt,
* Tourismus und Grundwasser.
*
* Registrierung wie in glossar-experiments.js:
* window.GlossarExperiments[key_slug] = function (root) { … }
* Der Renderer (glossar.php) übergibt einen leeren Container; das Widget baut
* seinen DOM selbst und bindet seine Events. Styles: nur .ge-* aus glossar.php.
*
* Richtlinien:
* - Keine externen Libraries, Vanilla JS + SVG.
* - Mobile-/Tap-freundlich (große Ziele, Pointer-Events, keine Hover-Pflicht).
* - Zahlen mit kurzer Quelle in der .ge-note.
* - Animations-Loops stoppen selbst, sobald der Container aus dem DOM fällt
* (root.isConnected), damit kein Loop nach Modal-Schließen weiterläuft.
*/
(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 eur = (n) => n.toLocaleString('de-AT') + ' €';
const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
// Pointer-Drag auf einem SVG-Handle. onMove bekommt SVG-Koordinaten (x,y).
function makeDraggable(svg, handle, onMove) {
const pt = svg.createSVGPoint();
function toSvg(evt) {
pt.x = evt.clientX; pt.y = evt.clientY;
const ctm = svg.getScreenCTM();
if (!ctm) return null;
return pt.matrixTransform(ctm.inverse());
}
let active = false;
handle.style.cursor = 'grab';
handle.style.touchAction = 'none';
handle.addEventListener('pointerdown', (e) => {
active = true;
handle.style.cursor = 'grabbing';
if (handle.setPointerCapture) { try { handle.setPointerCapture(e.pointerId); } catch (_) {} }
e.preventDefault();
});
handle.addEventListener('pointermove', (e) => {
if (!active) return;
const p = toSvg(e);
if (p) onMove(p.x, p.y);
e.preventDefault();
});
const stop = () => { active = false; handle.style.cursor = 'grab'; };
handle.addEventListener('pointerup', stop);
handle.addEventListener('pointercancel', stop);
}
// =====================================================================
// Experiment: Halbschatten — Kernschatten + Halbschatten auf der Wand
// =====================================================================
function penumbra(root) {
root.innerHTML = `
🌑 Kernschatten und Halbschatten
Ziehe den Ball zwischen Lampe und Wand hin und her. Auf der Wand entsteht in der Mitte ein ganz dunkler Kernschatten und ringsum ein weicher, halbdunkler Halbschatten .
Weil die Lampe (wie die Sonne) eine ausgedehnte Lichtquelle ist, entstehen zwei Zonen. Genau so bei Finsternissen: Wer im Kernschatten steht, sieht eine totale Finsternis, wer im Halbschatten steht, nur eine partielle . Quelle: NASA Eclipses – umbra & penumbra.
`;
const svg = root.querySelector('.ge-track');
const coreEl = root.querySelector('[data-ref="core"]');
const softEl = root.querySelector('[data-ref="soft"]');
if (!svg || !coreEl || !softEl) return;
const Lx = 70, Ly = 170, SH = 26; // Lichtquelle: Segment (Lx, Ly±SH)
const Wx = 545, Wy_TOP = 24, Wy_BOT = 316; // Wand
const R = 34;
let Bx = 300, By = 170; // Ballmittelpunkt
// Wand
svgEl('rect', { x: Wx, y: Wy_TOP, width: 40, height: Wy_BOT - Wy_TOP, fill: '#d7ccbb', stroke: '#b7a98f', 'stroke-width': '1.5', rx: 3 }, svg);
// dynamische Schattenbänder auf der Wandvorderseite
const softBand = svgEl('rect', { x: Wx - 3, width: 8, fill: '#5b636b', opacity: '0.45' }, svg);
const coreBand = svgEl('rect', { x: Wx - 3, width: 8, fill: '#1b2027', opacity: '0.92' }, svg);
// Randstrahlen (Halbschatten-Grenzen)
const ray1 = svgEl('line', { stroke: '#f4c94e', 'stroke-width': '1.4', opacity: '0.6', 'stroke-dasharray': '4 3' }, svg);
const ray2 = svgEl('line', { stroke: '#f4c94e', 'stroke-width': '1.4', opacity: '0.6', 'stroke-dasharray': '4 3' }, svg);
// Lampe
svgEl('rect', { x: Lx - 26, y: Ly - SH - 6, width: 26, height: 2 * SH + 12, fill: '#3a4750', rx: 4 }, svg);
svgEl('line', { x1: Lx, y1: Ly - SH, x2: Lx, y2: Ly + SH, stroke: '#f4c94e', 'stroke-width': '6', 'stroke-linecap': 'round' }, svg);
svgEl('circle', { cx: Lx - 13, cy: Ly, r: 5, fill: '#f4c94e' }, svg);
svgEl('text', { x: Lx - 13, y: Ly + 46, 'text-anchor': 'middle', 'font-size': '11', 'font-weight': '700', fill: '#3a4750' }, svg).textContent = 'Lampe';
// Ball (Drag-Handle)
const ball = svgEl('g', {}, svg);
const ballCircle = svgEl('circle', { cx: Bx, cy: By, r: R, fill: '#4a7c8a', stroke: '#1f4e5a', 'stroke-width': '2' }, ball);
svgEl('text', { x: Bx, y: By - R - 8, 'text-anchor': 'middle', 'font-size': '11', 'font-weight': '700', fill: '#1f4e5a', 'data-ref': 'balllabel' }, ball).textContent = 'Ball ziehen ↔';
const yAtWall = (Px, Py, Qx, Qy) => Py + (Qy - Py) * (Wx - Px) / (Qx - Px);
function update() {
ballCircle.setAttribute('cx', Bx);
ballCircle.setAttribute('cy', By);
const lbl = ball.querySelector('[data-ref="balllabel"]');
if (lbl) { lbl.setAttribute('x', Bx); lbl.setAttribute('y', By - R - 8); }
const St = Ly - SH, Sb = Ly + SH; // Quelle oben / unten
const Bt = By - R, Bb = By + R; // Ball oben / unten
// Halbschatten-Ränder (weitester Schatten)
let penTop = yAtWall(Lx, Sb, Bx, Bt);
let penBot = yAtWall(Lx, St, Bx, Bb);
// Kernschatten-Ränder (nur wo ALLE Strahlen blockiert sind)
let umbTop = yAtWall(Lx, St, Bx, Bt);
let umbBot = yAtWall(Lx, Sb, Bx, Bb);
penTop = clamp(penTop, Wy_TOP, Wy_BOT);
penBot = clamp(penBot, Wy_TOP, Wy_BOT);
softBand.setAttribute('y', penTop);
softBand.setAttribute('height', Math.max(0, penBot - penTop));
const hasCore = umbTop < umbBot;
if (hasCore) {
umbTop = clamp(umbTop, Wy_TOP, Wy_BOT);
umbBot = clamp(umbBot, Wy_TOP, Wy_BOT);
coreBand.setAttribute('y', umbTop);
coreBand.setAttribute('height', Math.max(0, umbBot - umbTop));
coreBand.setAttribute('opacity', '0.92');
} else {
coreBand.setAttribute('height', 0);
coreBand.setAttribute('opacity', '0');
}
// Randstrahlen zeichnen
ray1.setAttribute('x1', Lx); ray1.setAttribute('y1', Sb);
ray1.setAttribute('x2', Wx); ray1.setAttribute('y2', clamp(yAtWall(Lx, Sb, Bx, Bt), Wy_TOP, Wy_BOT));
ray2.setAttribute('x1', Lx); ray2.setAttribute('y1', St);
ray2.setAttribute('x2', Wx); ray2.setAttribute('y2', clamp(yAtWall(Lx, St, Bx, Bb), Wy_TOP, Wy_BOT));
const coreH = hasCore ? Math.round(umbBot - umbTop) : 0;
const softH = Math.round((penBot - penTop) - coreH);
if (hasCore) {
coreEl.innerHTML = 'Kernschatten (ganz dunkel): total verdeckt ';
coreEl.dataset.state = 'bad';
} else {
coreEl.innerHTML = 'Ball zu weit weg – kein Kernschatten mehr';
coreEl.dataset.state = 'warn';
}
softEl.innerHTML = 'Halbschatten (halbdunkel): teilweise verdeckt ';
softEl.dataset.state = 'now';
}
makeDraggable(svg, ball, (x, y) => {
Bx = clamp(x, Lx + 90, Wx - 70);
By = clamp(y, 90, 250);
update();
});
update();
}
// =====================================================================
// Experiment: Blockabfertigung — Dosierung an der Grenze
// =====================================================================
function blockabfertigung(root) {
root.innerHTML = `
🚚 Wie viele LKW pro Stunde?
Stell den Regler ein. Die Grenze schafft nur eine bestimmte Menge. Lässt man zu viele auf einmal durch, staut es sich; dosiert man, fließt der Verkehr.
Bei der „Blockabfertigung" dosiert das Land Tirol den LKW-Verkehr Richtung Brenner auf einen Richtwert von rund 300 LKW pro Stunde, damit die Autobahn nicht überlastet wird. Quelle: Land Tirol – Blockabfertigung.
`;
const svg = root.querySelector('.ge-track');
const rng = root.querySelector('#ge-bl-range');
const valEl = root.querySelector('#ge-bl-val');
const flowEl = root.querySelector('[data-ref="flow"]');
const capEl = root.querySelector('[data-ref="cap"]');
if (!svg || !rng || !valEl || !flowEl || !capEl) return;
const CAP = 300;
const GATEX = 430;
// Straße + Grenzhäuschen
svgEl('rect', { x: 0, y: 150, width: 600, height: 56, fill: '#4a4a4a' }, svg);
svgEl('line', { x1: 0, y1: 178, x2: 600, y2: 178, stroke: '#f4c94e', 'stroke-width': '2', 'stroke-dasharray': '18 14' }, svg);
svgEl('rect', { x: GATEX, y: 96, width: 10, height: 110, fill: '#c85c4a' }, svg);
svgEl('rect', { x: GATEX - 34, y: 74, width: 78, height: 26, fill: '#e8d5b5', stroke: '#1f4e5a', 'stroke-width': '1.5', rx: 3 }, svg);
svgEl('text', { x: GATEX + 5, y: 91, 'text-anchor': 'middle', 'font-size': '11', 'font-weight': '800', fill: '#1f4e5a' }, svg).textContent = 'Grenze';
// Stau-Balken (rot, wächst nach links)
const stauBar = svgEl('rect', { x: GATEX, y: 130, height: 14, fill: '#c85c4a', rx: 4 }, svg);
const stauTxt = svgEl('text', { x: GATEX - 8, y: 124, 'text-anchor': 'end', 'font-size': '12', 'font-weight': '800', fill: '#c85c4a' }, svg);
const trucks = svgEl('g', {}, svg);
function update() {
const rate = +rng.value;
valEl.textContent = rate.toLocaleString('de-AT');
const over = Math.max(0, rate - CAP);
const stauKm = over / 50; // grobe Illustration
const barW = clamp((over / (600 - CAP)) * (GATEX - 30), 0, GATEX - 30);
stauBar.setAttribute('x', GATEX - barW);
stauBar.setAttribute('width', barW);
// Kolonne aus LKW-Emojis vor der Grenze
trucks.innerHTML = '';
const nQueue = Math.round(clamp(over / 40, 0, 10));
for (let i = 0; i < nQueue; i++) {
svgEl('text', { x: GATEX - 40 - i * 34, y: 172, 'font-size': '24', 'text-anchor': 'middle' }, trucks).textContent = '🚚';
}
// ein LKW, der gerade durchfährt
svgEl('text', { x: GATEX + 40, y: 172, 'font-size': '24', 'text-anchor': 'middle' }, trucks).textContent = '🚚';
if (over <= 0) {
stauTxt.textContent = '';
flowEl.innerHTML = '🟢 Verkehr fließt frei ';
flowEl.dataset.state = 'good';
} else if (stauKm < 2) {
stauTxt.textContent = 'Stau: ' + stauKm.toFixed(1).replace('.', ',') + ' km';
flowEl.innerHTML = '🟠 beginnender Stau ';
flowEl.dataset.state = 'warn';
} else {
stauTxt.textContent = 'Stau: ' + stauKm.toFixed(1).replace('.', ',') + ' km';
flowEl.innerHTML = '🔴 langer Stau – zu viele LKW';
flowEl.dataset.state = 'bad';
}
capEl.innerHTML = 'Grenze schafft rund ' + CAP + ' LKW/h ';
capEl.dataset.state = 'now';
}
rng.addEventListener('input', update);
update();
}
// =====================================================================
// Experiment: Grundfreiheiten — vier Freiheiten über die offene Grenze
// =====================================================================
function grundfreiheiten(root) {
root.innerHTML = `
🇪🇺 Die vier Grundfreiheiten
Ziehe jedes Symbol über die offene Grenze von Land A nach Land B. Im EU-Binnenmarkt dürfen alle vier frei über Grenzen.
Der EU-Binnenmarkt garantiert den freien Verkehr von Waren, Personen, Dienstleistungen und Kapital – die „vier Grundfreiheiten". Quelle: Vertrag über die Arbeitsweise der EU (AEUV), Art. 26 · Europäische Kommission.
`;
const svg = root.querySelector('.ge-track');
const countEl = root.querySelector('[data-ref="count"]');
const explainEl = root.querySelector('[data-ref="explain"]');
if (!svg || !countEl || !explainEl) return;
const BORDER = 300;
// Länder
svgEl('rect', { x: 0, y: 0, width: BORDER, height: 300, fill: '#dbe8dd' }, svg);
svgEl('rect', { x: BORDER, y: 0, width: 600 - BORDER, height: 300, fill: '#dae4ec' }, svg);
svgEl('line', { x1: BORDER, y1: 0, x2: BORDER, y2: 300, stroke: '#5a8a5e', 'stroke-width': '3', 'stroke-dasharray': '10 8' }, svg);
svgEl('text', { x: 20, y: 28, 'font-size': '13', 'font-weight': '800', fill: '#3a6b3e' }, svg).textContent = 'Land A';
svgEl('text', { x: 580, y: 28, 'font-size': '13', 'font-weight': '800', fill: '#1f4e5a', 'text-anchor': 'end' }, svg).textContent = 'Land B';
svgEl('text', { x: BORDER, y: 292, 'font-size': '10', 'font-weight': '700', fill: '#5a8a5e', 'text-anchor': 'middle' }, svg).textContent = 'offene Grenze';
const items = [
{ emoji: '📦', name: 'Warenverkehr', txt: 'Waren dürfen ohne Zölle und ohne Grenzkontrollen gehandelt werden.', y: 80 },
{ emoji: '🧑', name: 'Personenfreizügigkeit', txt: 'Menschen dürfen in jedem EU-Land wohnen und arbeiten.', y: 140 },
{ emoji: '🛠️', name: 'Dienstleistungsfreiheit', txt: 'Betriebe dürfen ihre Dienste in jedem EU-Land anbieten.', y: 200 },
{ emoji: '🪙', name: 'Kapitalverkehr', txt: 'Geld und Kapital dürfen frei zwischen den Ländern fließen.', y: 260 }
];
let crossed = 0;
items.forEach((it) => {
it.done = false;
it.x = 60;
const g = svgEl('g', {}, svg);
const bg = svgEl('circle', { cx: it.x, cy: it.y, r: 26, fill: '#fff', stroke: '#1f4e5a', 'stroke-width': '2' }, g);
const em = svgEl('text', { x: it.x, y: it.y + 9, 'text-anchor': 'middle', 'font-size': '26' }, g);
em.textContent = it.emoji;
const lab = svgEl('text', { x: it.x, y: it.y + 44, 'text-anchor': 'middle', 'font-size': '10', 'font-weight': '700', fill: '#1f4e5a' }, g);
lab.textContent = it.name;
function place(px) {
it.x = clamp(px, 40, 560);
bg.setAttribute('cx', it.x); em.setAttribute('x', it.x); lab.setAttribute('x', it.x);
const nowDone = it.x > BORDER + 6;
if (nowDone && !it.done) {
it.done = true; crossed++;
bg.setAttribute('stroke', '#5a8a5e');
bg.setAttribute('fill', '#eaf5ec');
} else if (!nowDone && it.done) {
it.done = false; crossed--;
bg.setAttribute('stroke', '#1f4e5a');
bg.setAttribute('fill', '#fff');
}
explainEl.innerHTML = '' + it.emoji + ' ' + it.name + ': ' + it.txt;
explainEl.dataset.state = it.done ? 'good' : 'now';
countEl.innerHTML = crossed >= 4
? '🎉 Alle vier Grundfreiheiten dürfen frei über die Grenze!'
: '' + crossed + ' von 4 Freiheiten über der Grenze';
countEl.dataset.state = crossed >= 4 ? 'good' : 'now';
}
makeDraggable(svg, g, (x) => place(x));
// Init-Label ohne Zustand
});
countEl.innerHTML = '0 von 4 Freiheiten über der Grenze';
countEl.dataset.state = 'now';
explainEl.innerHTML = 'Ziehe ein Symbol nach Land B, um seine Grundfreiheit zu lesen.';
explainEl.dataset.state = 'now';
}
// =====================================================================
// Experiment: Synergie — zwei Betriebe zusammen bringen mehr Gäste
// =====================================================================
function synergie(root) {
root.innerHTML = `
🤝 Zwei Betriebe – zusammen stärker
Der Lift steht fest. Zieh das Gasthaus daneben. Getrennt bringt jeder nur wenige Gäste – nebeneinander steigt der gemeinsame Gäste-Balken über die Summe.
Vereinfachtes Beispiel: Lift allein ~20, Gasthaus allein ~15 Gäste (Summe 35). Nebeneinander besuchen viele beide – so entstehen rund 52 Gäste. Fachbegriff: Synergie- bzw. Agglomerationseffekt (zusammen mehr als die Summe der Einzelteile).
`;
const svg = root.querySelector('.ge-track');
const sumEl = root.querySelector('[data-ref="sum"]');
const verdictEl = root.querySelector('[data-ref="verdict"]');
if (!svg || !sumEl || !verdictEl) return;
const LIFT_X = 150, LIFT_Y = 90;
const A_ALONE = 20, B_ALONE = 15, TOGETHER = 52;
// Gäste-Balken
svgEl('rect', { x: 40, y: 205, width: 520, height: 20, fill: '#eef2f0', rx: 6 }, svg);
const barFill = svgEl('rect', { x: 40, y: 205, width: 0, height: 20, fill: '#5a8a5e', rx: 6 }, svg);
const barTxt = svgEl('text', { x: 300, y: 220, 'text-anchor': 'middle', 'font-size': '12', 'font-weight': '800', fill: '#1f4e5a' }, svg);
const sumMark = svgEl('line', { y1: 200, y2: 230, stroke: '#c85c4a', 'stroke-width': '2', 'stroke-dasharray': '3 3' }, svg);
svgEl('text', { x: 40 + (A_ALONE + B_ALONE) / 60 * 520, y: 198, 'text-anchor': 'middle', 'font-size': '9', 'font-weight': '700', fill: '#c85c4a' }, svg).textContent = 'Summe 35';
// Lift (fix)
const liftG = svgEl('g', {}, svg);
svgEl('circle', { cx: LIFT_X, cy: LIFT_Y, r: 34, fill: '#dae4ec', stroke: '#1f4e5a', 'stroke-width': '2' }, liftG);
svgEl('text', { x: LIFT_X, y: LIFT_Y + 11, 'text-anchor': 'middle', 'font-size': '30' }, liftG).textContent = '🚡';
svgEl('text', { x: LIFT_X, y: LIFT_Y + 54, 'text-anchor': 'middle', 'font-size': '11', 'font-weight': '800', fill: '#1f4e5a' }, liftG).textContent = 'Lift';
// Gasthaus (drag)
let gx = 470, gy = 90;
const gastG = svgEl('g', {}, svg);
const gastBg = svgEl('circle', { cx: gx, cy: gy, r: 34, fill: '#f3e8d8', stroke: '#e08a3a', 'stroke-width': '2' }, gastG);
svgEl('text', { x: 0, y: 0, 'text-anchor': 'middle', 'font-size': '30', 'data-ref': 'gem' }, gastG).textContent = '🍽️';
svgEl('text', { x: 0, y: 0, 'text-anchor': 'middle', 'font-size': '11', 'font-weight': '800', fill: '#c86a1a', 'data-ref': 'glab' }, gastG).textContent = 'Gasthaus ziehen ↔';
function update() {
gastBg.setAttribute('cx', gx); gastBg.setAttribute('cy', gy);
const gem = gastG.querySelector('[data-ref="gem"]');
const glab = gastG.querySelector('[data-ref="glab"]');
if (gem) { gem.setAttribute('x', gx); gem.setAttribute('y', gy + 11); }
if (glab) { glab.setAttribute('x', gx); glab.setAttribute('y', gy + 54); }
const dist = Math.hypot(gx - LIFT_X, gy - LIFT_Y);
const together = dist < 110;
const guests = together ? TOGETHER : (A_ALONE + B_ALONE);
const w = (guests / 60) * 520;
barFill.setAttribute('width', clamp(w, 0, 520));
barFill.setAttribute('fill', together ? '#5a8a5e' : '#9bab74');
barTxt.textContent = guests + ' Gäste';
sumMark.setAttribute('x1', 40 + (35 / 60) * 520);
sumMark.setAttribute('x2', 40 + (35 / 60) * 520);
if (together) {
sumEl.innerHTML = 'Zusammen: ' + TOGETHER + ' Gäste ';
sumEl.dataset.state = 'good';
verdictEl.innerHTML = '🎉 mehr als die Summe (35) – Synergie! ';
verdictEl.dataset.state = 'good';
} else {
sumEl.innerHTML = 'Getrennt: 20 + 15 = 35 Gäste ';
sumEl.dataset.state = 'now';
verdictEl.innerHTML = 'Zieh das Gasthaus näher an den Lift …';
verdictEl.dataset.state = 'warn';
}
}
makeDraggable(svg, gastG, (x, y) => {
gx = clamp(x, 60, 560); gy = clamp(y, 60, 130);
update();
});
update();
}
// =====================================================================
// Experiment: Aufenthaltsdauer — Ausgaben wachsen mit jeder Nacht
// =====================================================================
function aufenthaltsdauer(root) {
root.innerHTML = `
🛏️ Je länger, desto mehr Ausgaben
Schieb den Regler auf die Zahl der Nächte . Jede Nacht kostet Übernachtung, Essen und Tickets – die Ausgaben eines Gastes wachsen mit jeder Nacht.
Beispielwerte pro Nacht: Übernachtung 85 €, Essen 40 €, Tickets/Aktivitäten 25 € = 150 € · Nacht. Ein Gast bleibt in Österreich im Schnitt rund 3,3 Nächte . Quelle: Statistik Austria – Tourismus, Ø Aufenthaltsdauer.
`;
const svg = root.querySelector('.ge-track');
const rng = root.querySelector('#ge-ad-range');
const valEl = root.querySelector('#ge-ad-val');
const calcEl = root.querySelector('[data-ref="calc"]');
const totalEl = root.querySelector('[data-ref="total"]');
if (!svg || !rng || !valEl || !calcEl || !totalEl) return;
const PER_NIGHT = 150; // 85 + 40 + 25
const parts = [
{ key: 'Übernachtung', v: 85, color: '#4a7c8a' },
{ key: 'Essen', v: 40, color: '#5a8a5e' },
{ key: 'Tickets', v: 25, color: '#e08a3a' }
];
const baseY = 200, colW = 56, gap = 14, x0 = 60;
const bars = svgEl('g', {}, svg);
// Legende
const leg = svgEl('g', {}, svg);
parts.forEach((p, i) => {
svgEl('rect', { x: 380, y: 20 + i * 22, width: 14, height: 14, fill: p.color, rx: 3 }, leg);
svgEl('text', { x: 400, y: 32 + i * 22, 'font-size': '11', 'font-weight': '600', fill: '#1f4e5a' }, leg).textContent = p.key + ' ' + eur(p.v);
});
function update() {
const nights = +rng.value;
valEl.textContent = nights.toString().replace('.', ',');
bars.innerHTML = '';
const total = nights * PER_NIGHT;
const maxH = 150; // Höhe einer vollen Nacht-Säule (150 €)
for (let n = 0; n < nights; n++) {
const x = x0 + n * (colW + gap);
let yTop = baseY;
parts.forEach((p) => {
const segH = (p.v / PER_NIGHT) * maxH;
yTop -= segH;
svgEl('rect', { x, y: yTop, width: colW, height: segH, fill: p.color }, bars);
});
svgEl('text', { x: x + colW / 2, y: baseY + 16, 'text-anchor': 'middle', 'font-size': '10', 'font-weight': '700', fill: '#1f4e5a' }, bars).textContent = (n + 1) + '.';
}
svgEl('line', { x1: 40, y1: baseY, x2: 560, y2: baseY, stroke: '#b7a98f', 'stroke-width': '1.5' }, bars);
calcEl.innerHTML = nights + ' Nächte · 150 € = ' + eur(total) + ' ';
calcEl.dataset.state = 'now';
totalEl.innerHTML = 'Ausgaben gesamt: ' + eur(total) + ' ';
totalEl.dataset.state = nights >= 4 ? 'good' : 'now';
}
rng.addEventListener('input', update);
update();
}
// =====================================================================
// Experiment: Grundwasser — Regen sickert, wird gefiltert, sammelt sich
// =====================================================================
function grundwasser(root) {
root.innerHTML = `
💧 So entsteht sauberes Grundwasser
Regentropfen sickern durch Erde und Kies nach unten. Schmutz bleibt in den Schichten hängen – gefiltert . Ganz unten sammelt sich sauberes Grundwasser.
⏸ Pause
↺ Zurück
Beim Durchsickern durch Boden- und Kiesschichten wird das Wasser gereinigt. In Österreich stammt das Trinkwasser praktisch vollständig aus Grund- und Quellwasser. Quelle: Umweltbundesamt / BML – Wasser.
`;
const svg = root.querySelector('.ge-track');
const toggleBtn = root.querySelector('[data-act="toggle"]');
const resetBtn = root.querySelector('[data-act="reset"]');
const levelEl = root.querySelector('[data-ref="level"]');
if (!svg || !toggleBtn || !resetBtn || !levelEl) return;
const SKY = 70, SOIL = 150, GRAVEL = 220, BOTTOM = 300;
// Schichten
svgEl('rect', { x: 0, y: 0, width: 600, height: SKY, fill: '#dbeef5' }, svg);
svgEl('rect', { x: 0, y: SKY, width: 600, height: SOIL - SKY, fill: '#8a6a3e' }, svg);
svgEl('rect', { x: 0, y: SOIL, width: 600, height: GRAVEL - SOIL, fill: '#b8a074' }, svg);
svgEl('rect', { x: 0, y: GRAVEL, width: 600, height: BOTTOM - GRAVEL, fill: '#c9b98f' }, svg);
// Kies-Punkte in der Filterschicht
for (let i = 0; i < 40; i++) {
svgEl('circle', { cx: Math.random() * 600, cy: SOIL + 6 + Math.random() * (GRAVEL - SOIL - 12), r: 3 + Math.random() * 3, fill: '#9c8557', opacity: '0.7' }, svg);
}
svgEl('text', { x: 8, y: SKY + 22, 'font-size': '11', 'font-weight': '700', fill: '#fff' }, svg).textContent = 'Erde';
svgEl('text', { x: 8, y: SOIL + 20, 'font-size': '11', 'font-weight': '700', fill: '#5a4a2a' }, svg).textContent = 'Kies (Filter)';
// Grundwasser-Füllung (steigt)
const water = svgEl('rect', { x: 0, y: BOTTOM, width: 600, height: 0, fill: '#3a86b5', opacity: '0.75' }, svg);
svgEl('text', { x: 592, y: BOTTOM - 8, 'font-size': '11', 'font-weight': '800', fill: '#0f3a52', 'text-anchor': 'end' }, svg).textContent = 'Grundwasser';
const dropsG = svgEl('g', {}, svg);
let drops = [];
let level = 0; // 0..1 Füllstand
let collected = 0;
let running = true;
function spawn() {
const dirty = Math.random() < 0.5;
drops.push({
el: null, x: 30 + Math.random() * 540, y: -6,
vy: 0.9 + Math.random() * 0.8, dirty, filtered: false
});
}
// Startvorrat
for (let i = 0; i < 8; i++) spawn();
function draw() {
dropsG.innerHTML = '';
drops.forEach((d) => {
d.el = svgEl('circle', { cx: d.x, cy: d.y, r: 4, fill: '#3a86b5' }, dropsG);
if (d.dirty && !d.filtered) {
svgEl('circle', { cx: d.x, cy: d.y, r: 2, fill: '#5a3a1a' }, dropsG);
}
});
}
function tick() {
if (!root.isConnected) return; // Modal geschlossen → Loop stoppen
if (running) {
const waterTopY = BOTTOM - level * (BOTTOM - GRAVEL - 4);
drops.forEach((d) => {
d.y += d.vy;
// Schmutz wird in der Kiesschicht gefiltert (bleibt hängen)
if (d.dirty && !d.filtered && d.y >= SOIL + 20) {
d.filtered = true; // Schmutz raus, Wasser sickert sauber weiter
}
// langsamer im Kies
if (d.y > SOIL && d.y < GRAVEL) d.y += -d.vy * 0.5;
});
// ankommende Tropfen sammeln
drops = drops.filter((d) => {
if (d.y >= waterTopY - 2) {
collected++;
level = clamp(level + 0.006, 0, 1);
return false;
}
return true;
});
// Nachschub
if (drops.length < 12 && Math.random() < 0.25) spawn();
water.setAttribute('y', waterTopY);
water.setAttribute('height', BOTTOM - waterTopY);
draw();
levelEl.innerHTML = 'Sauberes Grundwasser: ' + Math.round(level * 100) + ' % gefüllt';
levelEl.dataset.state = level > 0.6 ? 'good' : 'now';
}
requestAnimationFrame(tick);
}
toggleBtn.addEventListener('click', () => {
running = !running;
toggleBtn.textContent = running ? '⏸ Pause' : '▶ Weiter';
});
resetBtn.addEventListener('click', () => {
drops = []; level = 0; collected = 0;
for (let i = 0; i < 8; i++) spawn();
water.setAttribute('height', 0);
levelEl.innerHTML = 'Sauberes Grundwasser: 0 % gefüllt';
levelEl.dataset.state = 'now';
});
levelEl.innerHTML = 'Sauberes Grundwasser: 0 % gefüllt';
levelEl.dataset.state = 'now';
requestAnimationFrame(tick);
}
// Registry — Key entspricht key_slug aus DB.
window.GlossarExperiments = Object.assign(window.GlossarExperiments || {}, {
'halbschatten': penumbra,
'blockabfertigung': blockabfertigung,
'grundfreiheiten': grundfreiheiten,
'synergie': synergie,
'aufenthaltsdauer': aufenthaltsdauer,
'grundwasser': grundwasser
});
})();