/** * 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.

0300
Richtwert
600

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.

13,3
Ø
7

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.

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 }); })();