Files
geograsim/App/assets/js/glossar-experiments-schoko.js
Adminator 685b4700f8 Glossar: 4 erklärende Widgets + 2 Einträge anschaulicher
Widgets: Ernährungspyramide (US-1992 vs DACH-2024 vs Denkfehler), Kakao-Bohne
(Schoko-Fließband + Mischverhältnis→Sorte/kaputte Masse), Klimazone (Bänder Äquator→Pol
mit Beispielpflanzen), Monsun (Monatsregen-Balkendiagramm Mumbai + Wien-Vergleich, Quelle IMD).
Texte: off-season-import (Winter-Erdbeere ~10× CO₂, Schiff vs Flug) + lebensmittelverschwendung
(70 kg = ~6 Wochen Essen; 700 Mio. Hungernde) — greifbare Vergleiche.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 01:42:20 +02:00

310 lines
14 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Glossar-Experimente — Gruppe „schoko".
*
* Registrierung: window.GlossarExperiments[key_slug] = function (root) { … }
* Der Renderer (glossar.php) hängt einen leeren Container ein und ruft die
* Funktion mit diesem Container als einzigem Argument auf.
*
* Nur `.ge-*`-Klassen aus glossar.php verwenden, kein neues CSS.
*/
(function () {
'use strict';
const NS = 'http://www.w3.org/2000/svg';
const svgEl = (tag, attrs, parent) => {
const el = document.createElementNS(NS, tag);
if (attrs) for (const k in attrs) el.setAttribute(k, attrs[k]);
if (parent) parent.appendChild(el);
return el;
};
// =====================================================================
// Experiment: Von der Kakaobohne zur Schokolade
// Teil 1 — Fließband mit Maschinen (Schritt für Schritt)
// Teil 2 — Mischmaschine: Kakao / Zucker / Milch -> Schoko-Sorte
// =====================================================================
function beanToBar(root) {
root.innerHTML = `
<div class="ge-wrap">
<div class="ge-head">
<h3>🍫 Von der Kakaobohne zur Schokolade</h3>
<p>Schicke die Kakaobohne über das Fließband. Tippe auf <strong>▶ Weiter</strong> und schau, was jede Maschine macht.</p>
</div>
<svg class="ge-track" viewBox="0 0 600 200" preserveAspectRatio="xMidYMid meet" aria-hidden="true"></svg>
<div class="ge-gh-result">
<div class="ge-gh-pill" data-ref="stepname" data-state="now"></div>
</div>
<p class="ge-head" style="margin:0"><span data-ref="steptext" style="font-size:.85rem"></span></p>
<div class="ge-controls">
<button class="ge-btn ge-btn-primary" data-act="next">▶ Weiter</button>
<button class="ge-btn" data-act="reset">↺ Von vorn</button>
</div>
<div data-ref="mix" style="display:none; flex-direction:column; gap:.6rem;">
<div class="ge-head">
<h3>🥣 Die Mischmaschine</h3>
<p>Jetzt kommt die Kakaomasse in die Mischmaschine. Stelle mit den Reglern ein — oder tippe einen fertigen Vorschlag.</p>
</div>
<div class="ge-controls">
<button class="ge-btn" data-preset="zart">🌑 Zartbitter</button>
<button class="ge-btn" data-preset="voll">🐄 Vollmilch</button>
<button class="ge-btn" data-preset="weiss">🤍 Weiße</button>
<button class="ge-btn" data-preset="wasser">🚱 Wasser rein!</button>
</div>
<div class="ge-gh-slider">
<label>Kakao-Anteil (Kakaomasse): <strong data-ref="kv">75</strong> %</label>
<input data-ref="kakao" type="range" min="0" max="90" value="75" step="5">
<div class="ge-gh-markers"><span>keine</span><span>mittel</span><span>sehr viel</span></div>
</div>
<div class="ge-gh-slider">
<label>Zucker: <strong data-ref="zv">25</strong> %</label>
<input data-ref="zucker" type="range" min="0" max="80" value="25" step="5">
<div class="ge-gh-markers"><span>keiner</span><span>mittel</span><span>sehr viel</span></div>
</div>
<div class="ge-gh-slider">
<label>Milch (Milchpulver): <strong data-ref="mv">0</strong> %</label>
<input data-ref="milch" type="range" min="0" max="40" value="0" step="5">
<div class="ge-gh-markers"><span>keine</span><span>etwas</span><span>viel</span></div>
</div>
<svg class="ge-track" viewBox="0 0 600 220" preserveAspectRatio="xMidYMid meet" aria-hidden="true" data-ref="barsvg"></svg>
<div class="ge-gh-result">
<div class="ge-gh-pill" data-ref="verdict" aria-live="polite"></div>
</div>
<p class="ge-head" style="margin:0"><span data-ref="explain" style="font-size:.85rem"></span></p>
</div>
<p class="ge-note">So entsteht Schokolade: Kakaomasse + Kakaobutter + Zucker (bei Milchschokolade auch Milchpulver). Weiße Schokolade hat gar keine Kakaomasse, nur die helle Kakaobutter. Beispielwerte: dunkle Schokolade 50 90 % Kakao, Vollmilch 25 40 %, weiße 0 %. Quelle: Leitsätze für Kakao und Schokoladen · EU-Kakaoverordnung 2000/36/EG.</p>
</div>
`;
// ---------- Teil 1: Fließband ----------
const svg = root.querySelector('.ge-track');
if (!svg) return;
const stepNameEl = root.querySelector('[data-ref="stepname"]');
const stepTextEl = root.querySelector('[data-ref="steptext"]');
const mixBox = root.querySelector('[data-ref="mix"]');
const STEPS = [
{ emoji: '🫘', name: 'Kakaobohne', text: 'Die getrockneten Bohnen kommen vom Kakaobaum — noch bitter und hart.' },
{ emoji: '🔥', name: 'Rösten', text: 'Im heißen Ofen bekommen die Bohnen erst ihren Schoko-Duft.' },
{ emoji: '🥜', name: 'Schälen & Brechen', text: 'Die Schale wird entfernt. Übrig bleiben kleine Stückchen: die Kakao-Nibs.' },
{ emoji: '⚙️', name: 'Mahlen', text: 'Die Nibs werden fein zermahlen — dabei wird flüssige Kakaomasse daraus.' },
{ emoji: '🌀', name: 'Conchieren', text: 'Stundenlanges Rühren macht die Masse ganz fein und zart.' },
{ emoji: '🥣', name: 'Mischen', text: 'Jetzt kommt Zucker (und oft Milch) dazu — hier entsteht deine Schokolade!' }
];
const N = STEPS.length;
const X0 = 55, X1 = 545;
const gap = (X1 - X0) / (N - 1);
const beltY = 130;
// Fließband
svgEl('rect', { x: 30, y: beltY, width: 540, height: 14, rx: 7, fill: '#c9b79a' }, svg);
for (let i = 0; i <= 18; i++) {
svgEl('line', { x1: 40 + i * 28, y1: beltY, x2: 40 + i * 28, y2: beltY + 14, stroke: '#a9906c', 'stroke-width': '1.5' }, svg);
}
// Maschinen-Boxen
const boxes = STEPS.map((s, i) => {
const cx = X0 + i * gap;
const g = svgEl('g', {}, svg);
svgEl('rect', { x: cx - 26, y: beltY - 62, width: 52, height: 54, rx: 8,
fill: '#f2ede3', stroke: '#c9b79a', 'stroke-width': '2', 'data-box': String(i) }, g);
const ic = svgEl('text', { x: cx, y: beltY - 28, 'text-anchor': 'middle', 'font-size': '26' }, g);
ic.textContent = s.emoji;
const lb = svgEl('text', { x: cx, y: beltY + 32, 'text-anchor': 'middle', 'font-size': '9.5',
'font-weight': '700', fill: '#5a4632' }, g);
lb.textContent = s.name;
return g.querySelector('rect');
});
// wandernde Bohne
const beanX = X0;
const bean = svgEl('text', { x: beanX, y: beltY - 4, 'text-anchor': 'middle', 'font-size': '22' }, svg);
bean.textContent = '🫘';
let idx = 0;
let animId = null;
function highlight() {
boxes.forEach((b, i) => {
b.setAttribute('fill', i === idx ? '#ffe9c2' : '#f2ede3');
b.setAttribute('stroke', i === idx ? '#e0a04a' : '#c9b79a');
b.setAttribute('stroke-width', i === idx ? '3' : '2');
});
const s = STEPS[idx];
bean.textContent = idx >= 3 ? '🟤' : '🫘'; // ab dem Mahlen: Kakaomasse
if (stepNameEl) stepNameEl.textContent = (idx + 1) + '. ' + s.name;
if (stepTextEl) stepTextEl.textContent = s.text;
if (mixBox) mixBox.style.display = (idx === N - 1) ? 'flex' : 'none';
}
function tweenTo(targetX) {
cancelAnimationFrame(animId);
const fromX = parseFloat(bean.getAttribute('x')) || X0;
const start = performance.now();
const dur = 480;
function frame(now) {
if (!root.isConnected) return;
const t = Math.min(1, (now - start) / dur);
const e = 1 - Math.pow(1 - t, 3);
bean.setAttribute('x', String(fromX + (targetX - fromX) * e));
if (t < 1) animId = requestAnimationFrame(frame);
}
animId = requestAnimationFrame(frame);
}
function goNext() {
if (idx < N - 1) {
idx++;
tweenTo(X0 + idx * gap);
highlight();
}
}
function goReset() {
cancelAnimationFrame(animId);
idx = 0;
bean.setAttribute('x', String(X0));
highlight();
}
root.querySelector('[data-act="next"]').addEventListener('click', goNext);
root.querySelector('[data-act="reset"]').addEventListener('click', goReset);
// ---------- Teil 2: Mischmaschine ----------
const kakaoIn = root.querySelector('[data-ref="kakao"]');
const zuckerIn = root.querySelector('[data-ref="zucker"]');
const milchIn = root.querySelector('[data-ref="milch"]');
const kv = root.querySelector('[data-ref="kv"]');
const zv = root.querySelector('[data-ref="zv"]');
const mv = root.querySelector('[data-ref="mv"]');
const barSvg = root.querySelector('[data-ref="barsvg"]');
const verdict = root.querySelector('[data-ref="verdict"]');
const explain = root.querySelector('[data-ref="explain"]');
let waterIn = false;
function classify(K, Z, M) {
if (waterIn) {
return { key: 'kaputt', label: 'Klumpige, kaputte Masse', color: '#8f8577', state: 'bad',
text: 'Wasser und Fett vertragen sich nicht — die Schokolade wird klumpig und grieselig. Deshalb bleibt bei Schokolade alles trocken!' };
}
const hatFett = K > 0 || M > 0; // Kakaomasse bringt Kakaobutter, Milchpulver bringt Milchfett
if (!hatFett) {
return { key: 'kaputt', label: 'Bröseliger Zuckerhaufen', color: '#d8c9a0', state: 'bad',
text: 'Ohne Fett (keine Kakaomasse, keine Kakaobutter, keine Milch) hält nichts zusammen — es bleibt nur trockener Zucker.' };
}
if (Z >= 70) {
return { key: 'kaputt', label: 'Grieselige Zuckermasse', color: '#c9a86a', state: 'warn',
text: 'Viel zu viel Zucker: Die Masse wird körnig und sandig statt cremig. Weniger Zucker wäre besser.' };
}
if (K >= 55 && M <= 5) {
return { key: 'zart', label: 'Zartbitter-Schokolade 🌑', color: '#3a2417', state: 'good',
text: 'Viel Kakao, wenig Zucker, keine Milch: kräftig-dunkle Schokolade, die leicht bitter schmeckt.' };
}
if (K <= 15 && M >= 15) {
return { key: 'weiss', label: 'Weiße Schokolade 🤍', color: '#f2e4c4', state: 'good',
text: 'Keine Kakaomasse — nur die helle Kakaobutter, dazu Milch und Zucker. Darum ist sie hell und milchig.' };
}
if (M >= 15) {
return { key: 'voll', label: 'Vollmilch-Schokolade 🐄', color: '#7a4a24', state: 'good',
text: 'Mittel viel Kakao mit Milch und Zucker: cremig-süß, die beliebteste Sorte.' };
}
return { key: 'dunkel', label: 'Dunkle Schokolade', color: '#4a2f1c', state: 'now',
text: 'Kakao und Zucker ohne Milch — eine dunkle Schokolade. Mehr Milch macht sie heller, mehr Kakao herber.' };
}
function drawBar(res) {
barSvg.innerHTML = '';
// Maschinen-Beschriftung oben
const cap = svgEl('text', { x: 300, y: 24, 'text-anchor': 'middle', 'font-size': '13',
'font-weight': '800', fill: '#5a4632' }, barSvg);
cap.textContent = '🥣 heraus kommt:';
if (res.state === 'bad' || res.state === 'warn') {
// kaputte / grieselige Masse: unregelmäßige Klumpen
const blobs = [
[180, 120, 34], [250, 150, 26], [320, 110, 30], [380, 155, 22],
[230, 100, 20], [300, 165, 24], [360, 115, 18], [410, 130, 26], [200, 160, 22]
];
blobs.forEach(([x, y, r]) => {
svgEl('circle', { cx: x, cy: y, r: r, fill: res.color, opacity: '0.9' }, barSvg);
});
const emo = svgEl('text', { x: 300, y: 205, 'text-anchor': 'middle', 'font-size': '16' }, barSvg);
emo.textContent = waterIn ? '💧 klumpig' : '⚠️ krümelig';
return;
}
// gute Tafel: 5 × 3 Felder
const cols = 5, rows = 3;
const bw = 240, bh = 132, bx = 300 - bw / 2, by = 44;
const cw = bw / cols, ch = bh / rows;
svgEl('rect', { x: bx - 6, y: by - 6, width: bw + 12, height: bh + 12, rx: 12,
fill: res.color, opacity: '0.35' }, barSvg);
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
svgEl('rect', { x: bx + c * cw + 3, y: by + r * ch + 3, width: cw - 6, height: ch - 6, rx: 4,
fill: res.color, stroke: 'rgba(0,0,0,0.15)', 'stroke-width': '1' }, barSvg);
}
}
const emo = svgEl('text', { x: 300, y: 205, 'text-anchor': 'middle', 'font-size': '15',
'font-weight': '700', fill: '#5a4632' }, barSvg);
emo.textContent = '😋 fertige Tafel';
}
function update() {
const K = parseInt(kakaoIn.value, 10);
const Z = parseInt(zuckerIn.value, 10);
const M = parseInt(milchIn.value, 10);
if (kv) kv.textContent = String(K);
if (zv) zv.textContent = String(Z);
if (mv) mv.textContent = String(M);
const res = classify(K, Z, M);
if (verdict) {
verdict.textContent = res.label;
verdict.setAttribute('data-state', res.state);
}
if (explain) explain.textContent = res.text;
drawBar(res);
}
function onSlide() { waterIn = false; update(); }
[kakaoIn, zuckerIn, milchIn].forEach(inp => {
if (inp) inp.addEventListener('input', onSlide);
});
const presets = {
zart: { k: 75, z: 25, m: 0, w: false },
voll: { k: 35, z: 45, m: 20, w: false },
weiss: { k: 0, z: 50, m: 30, w: false },
wasser: { k: 40, z: 20, m: 10, w: true }
};
root.querySelectorAll('[data-preset]').forEach(btn => {
btn.addEventListener('click', () => {
const p = presets[btn.getAttribute('data-preset')];
if (!p) return;
kakaoIn.value = String(p.k);
zuckerIn.value = String(p.z);
milchIn.value = String(p.m);
waterIn = p.w;
if (kv) kv.textContent = String(p.k);
if (zv) zv.textContent = String(p.z);
if (mv) mv.textContent = String(p.m);
const res = classify(p.k, p.z, p.m);
if (verdict) { verdict.textContent = res.label; verdict.setAttribute('data-state', res.state); }
if (explain) explain.textContent = res.text;
drawBar(res);
});
});
// Initialzustand
highlight();
update();
}
// Registry — Keys entsprechen key_slug aus der Tabelle `glossar`
window.GlossarExperiments = Object.assign(window.GlossarExperiments || {}, {
'kakao-bohne': beanToBar
});
})();