/** * 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 = `

🍫 Von der Kakaobohne zur Schokolade

Schicke die Kakaobohne über das Fließband. Tippe auf ▶ Weiter und schau, was jede Maschine macht.

🥣 Die Mischmaschine

Jetzt kommt die Kakaomasse in die Mischmaschine. Stelle mit den Reglern ein — oder tippe einen fertigen Vorschlag.

keinemittelsehr viel
keinermittelsehr viel
keineetwasviel

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.

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