Files
geograsim/App/sims/weltkueche/game.html
T
Adminator 7915512195 Weltküche: DACH/EU-Priorität wiederhergestellt + Beschreibungen
Die Umstellung auf echte Menge hatte die EU-Priorität eingeebnet. Jetzt Stufen-
folge: EU-Nachbar (Hauptweg zu UNSEREM Markt, kurzer Weg) VOR fernem Welt-
Lieferant (nur wo es hier nicht wächst, z.B. Pfeffer→Vietnam+Brasilien) VOR
"Big Player, kommt kaum an" (world-far). Innerhalb einer Stufe zählt die Menge.

- MARKET_TIER {eu:0, global:1, world-far:2}
- explainChoice: EU-Antwort erklärt "aus EU-Binnenmarkt, kurzer Weg, kein Zoll";
  world-far-Fehlklick erklärt "weltweit groß, aber kaum im EU-Handel"
- "in dieser Runde"-Texte entfernt (Auswahl ist jetzt deterministisch)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 16:54:39 +02:00

1626 lines
100 KiB
HTML
Raw 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.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Weltküche Woher kommt mein Essen?</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.css"/>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,500;12..96,700;12..96,800&family=Manrope:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
:root{
--bg:#EFEBE0;--bg2:#E6E0D1;--card:#FBF9F3;--ink:#2B2E27;--muted:#797B6E;--line:#D9D3C4;
--green:#5A7A52;--green-d:#3F5A39;--green-soft:#CBD9C0;
--clay:#C97B5A;--clay-soft:#EFD6CB;--gold:#D2A24C;--gold-soft:#F0E2C3;
--radius:20px;--shadow:0 6px 18px rgba(43,46,39,.07);font-size:16px;
}
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%}
body{font-family:'Manrope',sans-serif;background:var(--bg);color:var(--ink);line-height:1.5;-webkit-font-smoothing:antialiased}
h1,h2,h3,.display{font-family:'Bricolage Grotesque',sans-serif;letter-spacing:-.01em;line-height:1.1}
button{font-family:inherit;cursor:pointer;border:none}
.wrap{max-width:1180px;margin:0 auto;padding:0 18px}
header.top{position:sticky;top:0;z-index:600;background:rgba(239,235,224,.88);backdrop-filter:blur(8px);border-bottom:1px solid var(--line)}
.top-in{display:flex;align-items:center;gap:14px;padding:12px 0}
.logo{display:flex;align-items:center;gap:10px;font-family:'Bricolage Grotesque';font-weight:800;font-size:1.15rem}
.logo .globe{width:34px;height:34px;border-radius:11px;background:var(--green);color:#fff;display:grid;place-items:center;font-size:1.1rem}
.top-stats{margin-left:auto;display:flex;gap:8px;flex-wrap:wrap}
.pill{background:var(--card);border:1px solid var(--line);border-radius:999px;padding:7px 14px;font-weight:700;font-size:.85rem;display:flex;align-items:center;gap:7px}
.pill .v{color:var(--green-d)}
.ghostbtn{background:var(--card);border:1px solid var(--line);border-radius:999px;padding:7px 14px;font-weight:600;font-size:.85rem;color:var(--ink)}
.ghostbtn:hover{background:var(--bg2)}
.screen{display:none;animation:fade .4s ease}
.screen.active{display:block}
@keyframes fade{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.hero{padding:54px 0 30px;text-align:center}
.hero .kicker{display:inline-block;background:var(--green-soft);color:var(--green-d);font-weight:700;font-size:.8rem;padding:6px 14px;border-radius:999px;letter-spacing:.04em;text-transform:uppercase}
.hero h1{font-size:clamp(2.1rem,6vw,3.6rem);margin:18px 0 10px;font-weight:800}
.hero p.lead{max-width:640px;margin:0 auto;color:var(--muted);font-size:1.08rem}
.startcard{max-width:480px;margin:30px auto 0;background:var(--card);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);padding:26px;text-align:left}
.startcard label{font-weight:700;font-size:.9rem;display:block;margin-bottom:8px}
.startcard input{width:100%;padding:13px 15px;border-radius:13px;border:1px solid var(--line);background:#fff;font-family:inherit;font-size:1rem;color:var(--ink)}
.startcard input:focus{outline:none;border-color:var(--green)}
.btn{background:var(--green);color:#fff;font-weight:700;border-radius:13px;padding:14px 22px;font-size:1rem;transition:transform .12s,background .2s;width:100%;margin-top:16px}
.btn:hover{background:var(--green-d);transform:translateY(-1px)}
.btn.secondary{background:transparent;color:var(--green-d);border:1px solid var(--green);margin-top:10px}
.btn.secondary:hover{background:var(--green-soft);transform:none}
.howto{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;max-width:860px;margin:34px auto 0}
.howto .step{background:var(--card);border:1px solid var(--line);border-radius:16px;padding:18px;text-align:center}
.howto .num{width:30px;height:30px;border-radius:9px;background:var(--gold-soft);color:#9b7320;font-weight:800;display:grid;place-items:center;margin:0 auto 10px}
.howto h4{font-family:'Bricolage Grotesque';font-size:1rem;margin-bottom:4px}
.howto p{font-size:.85rem;color:var(--muted)}
.section-head{display:flex;align-items:flex-end;justify-content:space-between;gap:12px;margin:30px 0 18px;flex-wrap:wrap}
.section-head h2{font-size:1.8rem;font-weight:800}
.section-head p{color:var(--muted);font-size:.95rem}
.dishgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:16px;padding-bottom:40px}
.dishcard{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);padding:20px;box-shadow:var(--shadow);text-align:left;transition:transform .14s,border-color .2s;position:relative;overflow:hidden}
.dishcard:hover{transform:translateY(-3px);border-color:var(--green)}
.dishcard .emoji{font-size:2.6rem;line-height:1}
.dishcard h3{font-size:1.25rem;margin:10px 0 2px}
.dishcard .origin{color:var(--muted);font-size:.85rem;font-weight:600}
.dishcard .tags{display:flex;gap:6px;margin-top:12px;flex-wrap:wrap}
.tag{background:var(--bg2);border-radius:999px;padding:4px 10px;font-size:.74rem;font-weight:600;color:var(--ink)}
.diff{position:absolute;top:16px;right:16px;font-size:.72rem;font-weight:700;color:var(--green-d);background:var(--green-soft);padding:4px 9px;border-radius:999px}
.done-flag{position:absolute;top:16px;right:16px;background:var(--gold);color:#fff;font-size:.72rem;font-weight:700;padding:4px 9px;border-radius:999px}
.game-grid{display:grid;grid-template-columns:320px 1fr;gap:14px;padding:14px 0 40px;align-items:start}
/* iPad-Landscape (1180×820): kompaktere linke Spalte, alles auf einen Blick */
@media(min-width:881px) and (max-width:1280px){
.game-grid{grid-template-columns:280px 1fr;gap:12px}
}
@media(max-width:880px){.game-grid{grid-template-columns:1fr}.howto{grid-template-columns:1fr}}
.panel{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);overflow:hidden}
.panel-pad{padding:20px}
.recipe-top{display:flex;align-items:center;gap:14px;border-bottom:1px solid var(--line);padding:18px 20px}
.recipe-top .emoji{font-size:2.4rem}
.recipe-top h2{font-size:1.35rem;font-weight:800}
.recipe-top .origin{color:var(--muted);font-size:.85rem;font-weight:600}
.ing-list{list-style:none}
.ing-item{display:flex;align-items:center;gap:10px;padding:6px 10px;border-radius:10px;margin-bottom:4px;border:1px solid transparent;background:var(--bg);transition:.2s;min-height:36px}
.ing-item .ic{font-size:1.25rem;width:22px;text-align:center;line-height:1}
.ing-item .nm{font-weight:700;font-size:.92rem;line-height:1.15}
.ing-item .sub{font-size:.72rem;color:var(--muted);display:none}
.ing-item.local{opacity:.7}
.ing-item.active{background:var(--green-soft);border-color:var(--green)}
.ing-item.solved{background:#fff;border-color:var(--line)}
.ing-item .nm{flex:1}
.ing-item .chk{margin-left:auto;font-weight:800;color:var(--green);font-size:.95rem}
.ing-item .cnt{margin-left:auto;font-size:.72rem;font-weight:700;color:var(--green-d);background:var(--green-soft);border-radius:999px;padding:2px 8px}
.ing-item .pantry-badge{margin-left:auto;font-size:.7rem;color:var(--muted);font-weight:600}
.task-banner{background:var(--gold-soft);border:1px solid #e6cf95;border-radius:14px;padding:13px 15px;margin:0 0 14px;font-size:.92rem}
.task-banner b{color:#8a6516}
.map-wrap{position:relative}
#map{height:540px;width:100%;border-radius:var(--radius);z-index:1;transition:height .55s cubic-bezier(.4,0,.2,1)}
/* Rundenabschluss: Karte schrumpft, damit Result-Card mehr Platz bekommt */
#map.solved{height:280px}
/* iPad-Landscape: niedrigere Karte, damit Ergebnis-Card direkt drunter sichtbar bleibt */
@media(min-width:881px) and (max-width:1280px){#map{height:420px} #map.solved{height:220px}}
@media(max-width:880px){#map{height:400px} #map.solved{height:200px}}
.map-head{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:0 0 12px}
.map-head h3{font-size:1.1rem}
.map-head .hint{font-size:.82rem;color:var(--muted)}
.cm-wrap{background:transparent;border:none}
.country-btn{position:relative;transform:translate(-50%,-50%);display:flex;align-items:center;gap:9px;background:#fff;border:2.5px solid var(--green);color:var(--ink);font-family:'Manrope';font-weight:700;font-size:.98rem;padding:11px 17px;border-radius:14px;white-space:nowrap;cursor:pointer;box-shadow:0 6px 16px rgba(43,46,39,.25);transition:background .15s,border-color .15s,transform .12s}
.country-btn .flag{font-size:1.35rem;line-height:1}
.country-btn:hover:not(:disabled){background:var(--green-soft);transform:translate(-50%,-50%) scale(1.07)}
.country-btn:active:not(:disabled){transform:translate(-50%,-50%) scale(.98)}
.country-btn.correct{background:var(--green);border-color:var(--green-d);color:#fff}
.country-btn.wrong{background:var(--clay);border-color:#a85a3c;color:#fff;opacity:.9}
.country-btn:disabled{cursor:default}
.leaflet-div-icon{background:transparent;border:none}
.result-card{margin-top:14px;border-radius:16px;padding:16px;border:1px solid var(--line);display:none}
.result-card.show{display:block;animation:fade .3s}
.result-card.ok{background:#fff;border-color:var(--green)}
.result-card.mid{background:#fff;border-color:var(--gold)}
.result-card.no{background:var(--clay-soft);border-color:var(--clay)}
.result-card .rc-head{font-family:'Bricolage Grotesque';font-weight:800;font-size:1.05rem;display:flex;align-items:flex-start;gap:8px;margin-bottom:6px;line-height:1.25}
.result-card.ok .rc-head{color:var(--green-d)}
.result-card.mid .rc-head{color:#8a6516}
.result-card.no .rc-head{color:#9c4d2e}
.econ{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:12px 0}
.econ .box{background:var(--bg);border-radius:12px;padding:11px 13px}
.econ .box .k{font-size:.72rem;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.03em}
.econ .box .val{font-weight:700;font-size:.95rem;margin-top:2px}
.econ .box.price{background:var(--gold-soft)}
.econ .box.price .val{color:#8a6516}
.note{font-size:.88rem;background:var(--green-soft);border-radius:12px;padding:11px 13px;color:var(--green-d);margin-bottom:10px}
.result-card.no .note{background:#fff;color:var(--ink);margin-bottom:0}
.more-line{font-size:.86rem;color:var(--ink);background:var(--bg);border-radius:12px;padding:11px 13px;margin-bottom:10px}
.more-line b{color:var(--green-d)}
.regions{margin-bottom:10px}
.regions .rlabel,.econ-list .rlabel{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;margin-bottom:6px}
.region-item{display:flex;gap:9px;align-items:flex-start;font-size:.85rem;background:var(--bg);border-radius:12px;padding:9px 12px;margin-bottom:6px}
.econ-list{margin:10px 0}
.prod-row{display:flex;align-items:center;gap:10px;background:var(--bg);border-radius:10px;padding:6px 10px;margin-bottom:4px;font-size:.82rem}
.prod-row.match{background:var(--green-soft);font-weight:700;color:var(--green-d)}
.prod-row .prod-nm{flex:1;font-weight:600}
.prod-row .prod-val{font-variant-numeric:tabular-nums;font-size:.78rem;color:var(--muted);text-align:right}
.prod-row.match .prod-val{color:var(--green-d)}
/* Alle-Länder-dieser-Runde — am Ende jeder Zutat sehen Schüler:innen jede Karten-Option erklärt */
.all-choices{margin:10px 0 12px;padding:10px 12px;background:rgba(255,255,255,.55);border:1px solid var(--line);border-radius:12px}
.all-choices .rlabel{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;margin-bottom:8px}
.all-choice{display:grid;grid-template-columns:auto 1fr auto;gap:4px 10px;align-items:center;padding:8px 10px;margin-bottom:6px;border-radius:10px;background:var(--bg)}
.all-choice:last-child{margin-bottom:0}
.all-choice .cf{font-size:1.25rem;line-height:1}
.all-choice .cnm{font-weight:700;font-size:.88rem}
.all-choice .cb{font-weight:800;font-size:.95rem;justify-self:end}
.all-choice .ctxt{grid-column:2/-1;font-size:.78rem;color:#3a4a35;line-height:1.45;margin-top:1px}
.all-choice .clicked-tag{grid-column:2/-1;justify-self:start;font-size:.62rem;color:#a85a3c;font-weight:800;letter-spacing:.05em;text-transform:uppercase;background:rgba(168,90,60,.10);padding:2px 7px;border-radius:999px;margin-top:4px}
.all-choice.ok{background:rgba(76,124,78,.13)}
.all-choice.ok .cnm{color:var(--green-d)}
.all-choice.mid{background:rgba(210,162,76,.13)}
.all-choice.no{background:rgba(168,90,60,.10)}
.prod-more{font-size:.78rem;color:var(--muted);text-align:center;padding:4px}
.region-item .rf{font-size:1.2rem;line-height:1.2}
.region-item .rn{font-weight:700;display:block;margin-bottom:1px}
.next-row{display:flex;gap:10px;margin-top:4px}
.next-row .btn{margin-top:0}
.overlay{position:fixed;inset:0;background:rgba(43,46,39,.45);backdrop-filter:blur(3px);display:none;place-items:center;z-index:900;padding:18px}
.overlay.show{display:grid;animation:fade .25s}
.modal{background:var(--card);border-radius:24px;max-width:470px;width:100%;padding:30px;text-align:center;box-shadow:0 24px 60px rgba(0,0,0,.25);max-height:90vh;overflow:auto}
.modal .big-emoji{font-size:3.4rem}
.modal h2{font-size:1.6rem;font-weight:800;margin:8px 0}
.modal .scoreline{font-size:1.05rem;color:var(--green-d);font-weight:700;margin:4px 0 14px}
.fact{background:var(--gold-soft);border-radius:14px;padding:14px;font-size:.92rem;text-align:left;margin-bottom:14px}
.fact b{color:#8a6516}
.earned-badges{display:flex;gap:10px;justify-content:center;flex-wrap:wrap;margin-bottom:14px}
.bdg{width:66px;text-align:center}
.bdg .ring{width:54px;height:54px;border-radius:50%;background:var(--gold-soft);border:2px solid var(--gold);display:grid;place-items:center;font-size:1.5rem;margin:0 auto 5px}
.bdg .lbl{font-size:.66rem;font-weight:700;line-height:1.2}
.board{max-width:640px;margin:0 auto}
.rank-row{display:flex;align-items:center;gap:14px;background:var(--card);border:1px solid var(--line);border-radius:14px;padding:13px 16px;margin-bottom:9px}
.rank-row .pos{font-family:'Bricolage Grotesque';font-weight:800;font-size:1.2rem;width:34px;color:var(--muted)}
.rank-row.me{border-color:var(--green);background:#fff}
.rank-row .nm{font-weight:700}
.rank-row .sc{margin-left:auto;font-weight:800;color:var(--green-d)}
.rank-row .medal{font-size:1.3rem;width:34px;text-align:center}
.badge-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:14px;max-width:820px;margin:0 auto}
.badge-card{background:var(--card);border:1px solid var(--line);border-radius:16px;padding:18px;text-align:center;transition:.2s}
.badge-card.locked{opacity:.45;filter:grayscale(.6)}
.badge-card .ring{width:60px;height:60px;border-radius:50%;display:grid;place-items:center;font-size:1.7rem;margin:0 auto 10px;background:var(--gold-soft);border:2px solid var(--gold)}
.badge-card.locked .ring{background:var(--bg2);border-color:var(--line)}
.badge-card h4{font-family:'Bricolage Grotesque';font-size:.98rem;margin-bottom:4px}
.badge-card p{font-size:.78rem;color:var(--muted)}
.center{text-align:center;padding:30px 0}
.footer-note{text-align:center;color:var(--muted);font-size:.78rem;padding:24px 0 40px;max-width:700px;margin:0 auto}
.progress-dots{display:flex;gap:6px;margin-top:10px}
.progress-dots .d{width:9px;height:9px;border-radius:50%;background:var(--line)}
.progress-dots .d.on{background:var(--green)}
</style>
</head>
<body>
<header class="top">
<div class="wrap top-in">
<a href="../../schueler.html" title="Zurück zum Cockpit" style="text-decoration:none;color:inherit;font-size:1.3rem;margin-right:6px">🏠</a>
<div class="logo"><span class="globe">🌍</span> Weltküche</div>
<div class="top-stats">
<span class="pill" id="pillName">👤 <span class="v" id="statName"></span></span>
<span class="pill"><span class="v" id="statScore">0</span> Pkt</span>
<span class="pill">🏅 <span class="v" id="statBadges">0</span></span>
<button class="ghostbtn" onclick="show('badges')">Abzeichen</button>
</div>
</div>
</header>
<section class="screen active" id="screen-start">
<div class="wrap">
<div class="hero" style="padding:40px 0 20px">
<img id="wkSplashImg" alt="" style="max-width:360px;width:80%;border-radius:24px;box-shadow:var(--shadow)">
</div>
</div>
</section>
<!-- Tutorial-Overlay: standardisierte Einführung wie in den anderen Sims -->
<div id="tutorial-overlay" style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.55);z-index:9999;align-items:center;justify-content:center">
<div style="background:var(--card);max-width:560px;border-radius:20px;padding:28px 30px;margin:20px;box-shadow:0 10px 40px rgba(0,0,0,.3);max-height:88vh;overflow-y:auto">
<img id="tutorial-step-img" alt="" style="display:block;width:160px;height:160px;margin:0 auto 10px;border-radius:18px;background:var(--bg)">
<h2 id="tutorial-step-title" style="font-size:1.4rem;font-family:'Bricolage Grotesque';margin-bottom:14px;text-align:center;color:var(--green-d)">Willkommen!</h2>
<div id="tutorial-step-body" style="font-size:.96rem;line-height:1.55;color:var(--ink);margin-bottom:18px"></div>
<div style="display:flex;gap:10px;justify-content:space-between;align-items:center;margin-top:10px">
<button class="ghostbtn" onclick="skipTutorial()" style="font-size:.85rem">Überspringen</button>
<div id="tutorial-dots" style="display:flex;gap:6px"></div>
<button class="btn" id="tutorial-next" onclick="nextTutorialStep()" style="font-size:.92rem;width:auto;margin:0;padding:10px 18px">Weiter →</button>
</div>
</div>
</div>
<section class="screen" id="screen-dishes">
<div class="wrap">
<div class="section-head">
<div><h2>Wähle dein Gericht</h2><p id="dishCount">30 Gerichte aus aller Welt.</p></div>
<button class="ghostbtn" onclick="startTutorial()">️ Information</button>
</div>
<div id="dishFilter" style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:18px">
<button class="ghostbtn active" data-cat="all" onclick="setDishFilter('all')" style="background:var(--green);color:#fff;border-color:var(--green)">Alle</button>
<button class="ghostbtn" data-cat="regional-at" onclick="setDishFilter('regional-at')">🇦🇹 AT-regional</button>
<button class="ghostbtn" data-cat="dach" onclick="setDishFilter('dach')">🇦🇹🇩🇪🇨🇭 DACH</button>
<button class="ghostbtn" data-cat="europa" onclick="setDishFilter('europa')">🇪🇺 Europa</button>
<button class="ghostbtn" data-cat="exotisch" onclick="setDishFilter('exotisch')">🌍 Exotisch</button>
</div>
<div class="dishgrid" id="dishGrid"></div>
<p class="footer-note">Tipp: Die „falschen“ Länder sind kein Zufall. Frag dich: zu kalt? zu trocken? am Meer oder Binnenland? Manche Zutaten (z. B. Äpfel) kommen sogar von uns aus der Nähe.</p>
</div>
</section>
<section class="screen" id="screen-recipe">
<div class="wrap" style="max-width:760px">
<div class="section-head" style="margin-bottom:6px">
<button class="ghostbtn" onclick="backToDishes()">← Gerichte</button>
</div>
<div style="text-align:center;margin:0 0 18px">
<button class="btn" style="max-width:420px;margin:0 auto" onclick="startResearch()">🌍 Jetzt Welt-Zutaten erforschen →</button>
</div>
<div class="panel panel-pad" id="recipe-body" style="background:var(--card)"></div>
<div style="text-align:center;margin:18px 0 40px">
<button class="btn" style="max-width:420px;margin:0 auto" onclick="startResearch()">🌍 Jetzt Welt-Zutaten erforschen →</button>
</div>
</div>
</section>
<section class="screen" id="screen-game">
<div class="wrap">
<div class="section-head" style="margin-bottom:6px">
<button class="ghostbtn" onclick="backToDishes()">← Gerichte</button>
<div class="progress-dots" id="progressDots"></div>
</div>
<div class="game-grid">
<div class="panel">
<div class="recipe-top">
<span class="emoji" id="gEmoji">🍕</span>
<div><h2 id="gName">Gericht</h2><div class="origin" id="gOrigin">Herkunft</div></div>
</div>
<div class="panel-pad"><ul class="ing-list" id="ingList"></ul></div>
</div>
<div class="panel panel-pad">
<div class="task-banner" id="taskBanner">Suche: …</div>
<div class="map-head"><h3>Weltkarte</h3><span class="hint">tippe das/die Herkunftsland/-länder an</span></div>
<div class="map-wrap"><div id="map"></div></div>
<div class="result-card" id="resultCard"></div>
</div>
</div>
</div>
</section>
<section class="screen" id="screen-recipe-full">
<div class="wrap" style="max-width:760px">
<div class="section-head" style="margin-bottom:6px">
<button class="ghostbtn" onclick="show('dishes')">← Gerichte</button>
</div>
<div class="panel panel-pad" id="recipe-full-body" style="background:var(--card)"></div>
</div>
</section>
<section class="screen" id="screen-badges">
<div class="wrap">
<div class="section-head"><div><h2>Abzeichen</h2><p>Sammle alle durch Entdecken, Genauigkeit und Wissen.</p></div>
<button class="ghostbtn" onclick="backFromSub()">← Zurück</button></div>
<div class="badge-grid" id="badgeGrid"></div>
<div class="center"><button class="btn secondary" style="max-width:260px;margin:20px auto 0" onclick="backFromSub()">Weiter forschen</button></div>
</div>
</section>
<div class="overlay" id="overlay"><div class="modal" id="modalBox"></div></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.js"></script>
<script>
/* ====================== LÄNDER (mit Regionen-Info) ====================== */
let C = {
vietnam:{n:"Vietnam",f:"🇻🇳",lat:14.06,lng:108.28,info:"Südostasien, tropisches Monsunklima. Heute weltgrößter Pfeffer-Exporteur und Nr. 2 bei Kaffee."},
indien:{n:"Indien",f:"🇮🇳",lat:21.0,lng:78.0,info:"Riesiges Land mit vielen Klimazonen vom tropischen Süden bis zum Himalaya. Einer der größten Agrarproduzenten der Welt."},
china:{n:"China",f:"🇨🇳",lat:35.0,lng:104.0,info:"Bevölkerungsreich, mit vielen Klimazonen. Weltgrößter Produzent vieler Lebensmittel: Reis, Erdnüsse, Äpfel, Chili."},
island:{n:"Island",f:"🇮🇸",lat:64.96,lng:-19.02,info:"Vulkaninsel knapp unter dem Polarkreis. Zu kalt für Ackerbau Gemüse wächst nur in beheizten Gewächshäusern."},
kanada:{n:"Kanada",f:"🇨🇦",lat:56.0,lng:-106.0,info:"Riesig mit kalten Wintern. Im Süden (Prärie) wachsen aber Weizen und Raps auf gewaltigen Feldern."},
spanien:{n:"Spanien",f:"🇪🇸",lat:40.0,lng:-3.7,info:"Mittelmeerklima mit heißen, trockenen Sommern perfekt für Oliven, Mandeln und Zitrusfrüchte."},
italien:{n:"Italien",f:"🇮🇹",lat:42.5,lng:12.5,info:"Vom Alpenraum bis zum heißen Süden. Bekannt für Oliven, Tomaten, Hartweizen und Wein."},
schweden:{n:"Schweden",f:"🇸🇪",lat:62.0,lng:16.0,info:"Nordeuropa, lange kalte Winter. Getreide und Beeren ja tropische Gewürze unmöglich."},
polen:{n:"Polen",f:"🇵🇱",lat:52.0,lng:19.1,info:"Mitteleuropa, kühles Klima. Großer Produzent von Äpfeln, Getreide und Kartoffeln."},
norwegen:{n:"Norwegen",f:"🇳🇴",lat:62.0,lng:9.0,info:"Lange Küste, kühles Klima, viel Gebirge. Stark in der Fischerei kaum Ackerbau."},
saudi:{n:"Saudi-Arabien",f:"🇸🇦",lat:24.0,lng:45.0,info:"Wüstenland mit extremer Hitze und kaum Regen. Landwirtschaft nur mit künstlicher Bewässerung."},
elfenbein:{n:"Elfenbeinküste",f:"🇨🇮",lat:7.5,lng:-5.5,info:"Westafrika, nahe am Äquator: heiß und feucht. Weltgrößter Kakao-Produzent."},
ghana:{n:"Ghana",f:"🇬🇭",lat:7.9,lng:-1.0,info:"Westafrika, tropisch. Nach der Elfenbeinküste der zweitgrößte Kakao-Produzent der Welt."},
deutschland:{n:"Deutschland",f:"🇩🇪",lat:51.0,lng:10.4,info:"Gemäßigtes Klima. Getreide, Zuckerrüben, Kartoffeln aber keine tropischen Pflanzen."},
brasilien:{n:"Brasilien",f:"🇧🇷",lat:-10.0,lng:-52.0,info:"Größtes Land Südamerikas, viel Tropen. Nummer 1 bei Kaffee, Zucker, Orangen und Soja."},
finnland:{n:"Finnland",f:"🇫🇮",lat:63.0,lng:26.0,info:"Hoher Norden mit langen, kalten Wintern. Wald und Beeren statt Gewürzanbau."},
madagaskar:{n:"Madagaskar",f:"🇲🇬",lat:-19.0,lng:46.8,info:"Tropische Insel vor Afrika. Liefert rund drei Viertel der Welt-Vanille."},
indonesien:{n:"Indonesien",f:"🇮🇩",lat:-2.0,lng:117.0,info:"Tausende tropische Inseln am Äquator. Groß bei Kokos, Gewürzen, Palmöl und Kaffee."},
frankreich:{n:"Frankreich",f:"🇫🇷",lat:46.5,lng:2.2,info:"Vielfältiges Klima. Europas größter Agrarproduzent Getreide, Wein, Milch."},
japan:{n:"Japan",f:"🇯🇵",lat:36.2,lng:138.2,info:"Inselstaat mit langen Küsten. Reis und Meeresprodukte wie Nori-Algen sind zentral."},
thailand:{n:"Thailand",f:"🇹🇭",lat:15.0,lng:101.0,info:"Südostasien, tropisch-feucht. Einer der größten Reis-Exporteure der Welt."},
philippinen:{n:"Philippinen",f:"🇵🇭",lat:12.9,lng:121.8,info:"Tropischer Inselstaat. Weltweit führend bei Kokosnüssen und Bananen."},
russland:{n:"Russland",f:"🇷🇺",lat:60.0,lng:90.0,info:"Riesig und überwiegend kalt. Im Süden Weizen, im Norden nur Wald und Tundra."},
srilanka:{n:"Sri Lanka",f:"🇱🇰",lat:7.9,lng:80.8,info:"Tropische Insel südlich von Indien. Heimat des echten Ceylon-Zimts und großer Tee-Produzent."},
guatemala:{n:"Guatemala",f:"🇬🇹",lat:15.5,lng:-90.2,info:"Mittelamerika, bergig und tropisch. Weltgrößter Exporteur von Kardamom."},
ecuador:{n:"Ecuador",f:"🇪🇨",lat:-1.8,lng:-78.2,info:"Liegt direkt am Äquator (daher der Name). Weltgrößter Bananen-Exporteur."},
usa:{n:"USA",f:"🇺🇸",lat:39.0,lng:-98.0,info:"Sehr groß, viele Klimazonen. Kalifornien liefert fast alle Mandeln der Welt; riesige Mais- und Sojafelder im Mittleren Westen."},
uk:{n:"Großbritannien",f:"🇬🇧",lat:54.0,lng:-2.5,info:"Kühles, feuchtes Inselklima. Tee wird viel getrunken, aber nicht angebaut."},
sudan:{n:"Sudan",f:"🇸🇩",lat:14.0,lng:30.0,info:"Heißes, trockenes Land in Nordostafrika. Einer der größten Sesam-Produzenten."},
suedkorea:{n:"Südkorea",f:"🇰🇷",lat:36.5,lng:127.8,info:"Halbinsel mit langer Küste. Wichtiger Produzent von Seetang (Nori) und Reis."},
oesterreich:{n:"Österreich",f:"🇦🇹",lat:47.6,lng:14.2,info:"Binnenland in den Alpen kein Meereszugang. Milch, Getreide, Äpfel, aber kein Seetang."},
schweiz:{n:"Schweiz",f:"🇨🇭",lat:46.8,lng:8.2,info:"Alpines Binnenland ohne Meer. Bekannt für Milch und Käse, nicht für Meeresalgen."},
mexiko:{n:"Mexiko",f:"🇲🇽",lat:23.6,lng:-102.5,info:"Mittelamerika, viel Sonne und Wärme. Heimat von Avocado, Mais, Chili und Limette."},
};
/* ====================== ZUTATEN-BAUSTEINE ====================== */
let ING = {
pfeffer:{nm:"Schwarzer Pfeffer",ic:"🧂",correct:["vietnam","indien"],choices:["vietnam","indien","island","kanada"],
econ:{leader:"Vietnam",share:"~40 % des Welt-Pfefferexports",world:"~550.000 t / Jahr weltweit",price:"ca. 24 € / 100 g"},
note:"Pfeffer ist eine tropische Kletterpflanze und wächst nur nahe am Äquator. In Island oder Kanada ist es viel zu kalt.",
more:"Pfeffer war im Mittelalter so wertvoll, dass man ihn „schwarzes Gold“ nannte und damit sogar bezahlte."},
olivenoel:{nm:"Olivenöl",ic:"🫒",correct:["spanien","italien"],choices:["spanien","italien","schweden","polen"],
econ:{leader:"Spanien",share:"~45 % der Weltproduktion",world:"~3 Mio t Olivenöl / Jahr",price:"ca. 1 € / 100 ml"},
note:"Olivenbäume lieben das milde Mittelmeerklima. In Schweden oder Polen sind die Winter zu kalt.",
more:"Ein Olivenbaum kann über 1000 Jahre alt werden und trägt jedes Jahr aufs Neue Früchte."},
hartweizen:{nm:"Hartweizen (Teig)",ic:"🌾",correct:["kanada","italien"],choices:["kanada","italien","norwegen","saudi"],
econ:{leader:"Kanada",share:"weltgrößter Hartweizen-Exporteur (~50 % Welthandel)",world:"~770 Mio t Weizen / Jahr",price:"ca. 0,100,20 € / 100 g (Mehl)"},
note:"Weizen braucht gemäßigtes Klima mit genug Regen nicht zu kalt wie Norwegen, nicht zu trocken wie Saudi-Arabien.",
more:"Hartweizen (Durum) ist besonders eiweißreich darum hält Pasta beim Kochen ihre Form."},
tomaten:{nm:"Tomaten (Sauce)",ic:"🍅",correct:["italien","spanien"],choices:["italien","spanien","norwegen","island"],
econ:{leader:"Italien & Spanien (EU)",share:"China ist weltweit Nr. 1, in Europa führen Italien & Spanien",world:"~190 Mio t Tomaten / Jahr",price:"ca. 0,150,25 € / 100 g (Dose)"},
note:"Tomaten lieben Sonne und Wärme. Im hohen Norden wachsen sie nur im beheizten Gewächshaus.",
more:"Die berühmten Pizza- und Pastasaucen kommen meist aus süditalienischen Tomaten, etwa aus der Gegend um Neapel."},
kakao:{nm:"Kakao",ic:"🍫",correct:["elfenbein","ghana"],choices:["elfenbein","ghana","deutschland","kanada"],
econ:{leader:"Elfenbeinküste",share:"~40 % der Weltproduktion (mit Ghana ~60 %)",world:"~5 Mio t Kakaobohnen / Jahr",price:"ca. 12 € / 100 g (Kakaopulver)"},
note:"Der Kakaobaum wächst nur in heißen, feuchten Gebieten nahe am Äquator Deutschland und Kanada sind zu kalt.",
more:"Aus einer Kakaofrucht kommen ~3040 Bohnen; für 100 g Schokolade braucht man rund 100 Bohnen."},
rohrzucker:{nm:"Rohrzucker",ic:"🧊",correct:["brasilien","indien"],choices:["brasilien","indien","norwegen","finnland"],
econ:{leader:"Brasilien",share:"weltgrößter Zucker-Exporteur",world:"~180 Mio t Zucker / Jahr",price:"ca. 0,10 € / 100 g"},
note:"Zuckerrohr ist eine tropische Pflanze. (In Europa gewinnt man Zucker stattdessen aus Zuckerrüben.)",
more:"Aus Zuckerrohr macht Brasilien auch Biosprit (Ethanol) für Autos."},
vanille:{nm:"Vanille",ic:"🌼",correct:["madagaskar","indonesien"],choices:["madagaskar","indonesien","frankreich","japan"],
econ:{leader:"Madagaskar",share:"~7580 % der Weltproduktion",world:"weltweit nur ~7.000 t / Jahr",price:"ca. 3060 € / 100 g (echte Schoten)"},
note:"Vanille ist nach Safran das teuerste Gewürz der Welt jede Blüte muss von Hand bestäubt werden.",
more:"Die Vanille-Orchidee blüht nur einen einzigen Tag im Jahr darum ist echte Vanille so selten."},
reis:{nm:"Reis",ic:"🍚",correct:["indien","thailand"],choices:["indien","thailand","island","norwegen"],
econ:{leader:"Indien",share:"weltgrößter Reis-Exporteur (~40 % Welthandel)",world:"~520 Mio t / Jahr",price:"ca. 0,150,30 € / 100 g"},
note:"Reis wächst auf warmen, überfluteten Feldern. Island und Norwegen sind dafür viel zu kalt.",
more:"Für mehr als die Hälfte der Menschheit ist Reis das wichtigste Grundnahrungsmittel."},
kurkuma:{nm:"Kurkuma",ic:"🟡",correct:["indien"],choices:["indien","schweden","kanada","russland"],
econ:{leader:"Indien",share:"~7580 % der Weltproduktion",world:"fast die ganze Welternte aus Südasien",price:"ca. 12 € / 100 g"},
note:"Kurkuma gibt dem Curry die gelbe Farbe und ist eine tropische Wurzel aus Südasien.",
more:"Kurkuma färbt so stark, dass es auch als natürlicher Lebensmittelfarbstoff (E100) verwendet wird."},
kokos:{nm:"Kokosmilch",ic:"🥥",correct:["indonesien","philippinen"],choices:["indonesien","philippinen","deutschland","russland"],
econ:{leader:"Indonesien",share:"Indonesien & Philippinen ~halbe Weltproduktion",world:"~60 Mio t Kokosnüsse / Jahr",price:"ca. 0,30 € / 100 ml"},
note:"Kokospalmen brauchen tropische Küsten: Sonne, Wärme und sandigen Boden.",
more:"Eine Kokospalme trägt bis zu 100 Nüsse pro Jahr über viele Jahrzehnte hinweg."},
ingwer:{nm:"Ingwer",ic:"🫚",correct:["indien","china"],choices:["indien","china","norwegen","island"],
econ:{leader:"Indien",share:"Indien & China sind die größten Produzenten",world:"~4 Mio t / Jahr",price:"ca. 0,601,00 € / 100 g"},
note:"Ingwer ist eine tropische Wurzel und braucht Wärme und Feuchtigkeit zum Wachsen.",
more:"Ingwer wird seit über 3000 Jahren als Gewürz und Heilmittel verwendet."},
zimt:{nm:"Zimt",ic:"🪵",correct:["srilanka","indonesien"],choices:["srilanka","indonesien","schweden","deutschland"],
econ:{leader:"Sri Lanka",share:"Heimat des echten Ceylon-Zimts",world:"~250.000 t Zimt / Jahr",price:"ca. 24 € / 100 g"},
note:"Überraschung: Im schwedischen Klassiker steckt KEIN schwedischer Zimt er kommt aus Sri Lanka, vom anderen Ende der Welt!",
more:"Zimt ist die getrocknete, gerollte Rinde eines Baumes die typischen „Stangen“ sind eingerollte Rindenstücke."},
kardamom:{nm:"Kardamom",ic:"🫛",correct:["guatemala","indien"],choices:["guatemala","indien","norwegen","italien"],
econ:{leader:"Guatemala",share:"weltgrößter Kardamom-Exporteur",world:"~40.000 t / Jahr",price:"ca. 510 € / 100 g"},
note:"Kardamom ist eines der teuersten Gewürze der Welt (Platz 3 nach Safran und Vanille).",
more:"Kardamom kam erst um 1900 nach Guatemala heute exportiert das Land mehr davon als die Ursprungsregion Indien."},
banane:{nm:"Banane",ic:"🍌",correct:["ecuador","philippinen"],choices:["ecuador","philippinen","deutschland","kanada"],
econ:{leader:"Ecuador",share:"weltgrößter Bananen-Exporteur (~2530 %)",world:"~135 Mio t Bananen / Jahr",price:"ca. 0,150,25 € / 100 g"},
note:"Bananen brauchen ganzjährig tropische Wärme deshalb reisen sie tausende Kilometer zu uns.",
more:"Bananen werden grün geerntet und reifen auf dem langen Schiffsweg nach Europa nach."},
mandeln:{nm:"Mandeln",ic:"🌰",correct:["usa","spanien"],choices:["usa","spanien","norwegen","russland"],
econ:{leader:"USA",share:"Kalifornien: ~7580 % der Weltproduktion",world:"~1,4 Mio t Mandeln / Jahr",price:"ca. 1,502,50 € / 100 g"},
note:"Fast alle Mandeln wachsen im sonnigen Kalifornien und rund ums Mittelmeer.",
more:"Mandelbäume brauchen Bienen zur Bestäubung in Kalifornien werden dafür Milliarden Bienen herangekarrt."},
kaffee:{nm:"Kaffee",ic:"☕",correct:["brasilien","vietnam"],choices:["brasilien","vietnam","uk","schweden"],
econ:{leader:"Brasilien",share:"~35 % der Weltproduktion (Vietnam Nr. 2)",world:"~10 Mio t Rohkaffee / Jahr",price:"ca. 12 € / 100 g (Bohnen)"},
note:"Lustig: Schweden trinkt am meisten Kaffee pro Kopf angebaut wird dort aber keine einzige Bohne.",
more:"Kaffee ist nach Erdöl eine der meistgehandelten Rohstoff-Waren der Welt."},
orange:{nm:"Orangensaft",ic:"🍊",correct:["brasilien","spanien"],choices:["brasilien","spanien","norwegen","island"],
econ:{leader:"Brasilien",share:"weltgrößter Orangen-Produzent; in Europa führt Spanien",world:"~75 Mio t Orangen / Jahr",price:"ca. 0,150,30 € / 100 ml (Saft)"},
note:"Orangen sind Zitrusfrüchte sie brauchen warmes, frostfreies Klima.",
more:"Fast die Hälfte aller Orangen wird zu Saft verarbeitet; Brasilien ist der größte Saft-Exporteur."},
sesam:{nm:"Sesam",ic:"⚪",correct:["indien","sudan"],choices:["indien","sudan","island","norwegen"],
econ:{leader:"Sudan / Indien",share:"gehören zu den größten Produzenten weltweit",world:"~6,5 Mio t Sesam / Jahr",price:"ca. 0,801,50 € / 100 g"},
note:"Sesam liebt Hitze und Trockenheit deshalb wächst er in Afrika und Asien, nicht im kalten Norden.",
more:"Aus Sesam wird die Paste Tahini gemacht Grundzutat von Hummus."},
soja:{nm:"Sojabohnen (Sojasauce)",ic:"🫘",correct:["usa","brasilien"],choices:["usa","brasilien","island","saudi"],
econ:{leader:"Brasilien",share:"Brasilien & USA ~zwei Drittel der Weltproduktion",world:"~390 Mio t Soja / Jahr",price:"ca. 0,40 € / 100 ml (Sojasauce)"},
note:"Soja braucht viel Fläche und mildes bis warmes Klima Island ist zu kalt, Saudi-Arabien zu trocken.",
more:"Der größte Teil der Welt-Sojaernte wird gar nicht von Menschen gegessen, sondern als Tierfutter verwendet."},
nori:{nm:"Nori (Seetang)",ic:"🟢",correct:["japan","suedkorea"],choices:["japan","suedkorea","oesterreich","schweiz"],
econ:{leader:"Japan / Südkorea",share:"fast die gesamte Welt-Noriproduktion",world:"Millionen Tonnen Algen / Jahr",price:"ca. 510 € / 100 g (Noriblätter)"},
note:"Nori ist eine Meeresalge. Österreich und die Schweiz sind Binnenländer ohne Meer dort kann gar kein Seetang wachsen!",
more:"Nori wird auf Netzen im flachen Küstenwasser „angebaut“ und wie Gemüse geerntet."},
avocado:{nm:"Avocado",ic:"🥑",correct:["mexiko"],choices:["mexiko","norwegen","island","polen"],
econ:{leader:"Mexiko",share:"~30 % der Weltproduktion, weltgrößter Exporteur",world:"~9 Mio t / Jahr",price:"ca. 0,801,20 € / 100 g"},
note:"Avocados wachsen in warmen, frostfreien Regionen Mexiko ist das Avocado-Land Nr. 1.",
more:"Avocadobäume brauchen sehr viel Wasser in trockenen Anbaugebieten ist das ein Umweltproblem."},
limette:{nm:"Limette",ic:"🟢",correct:["mexiko","indien"],choices:["mexiko","indien","norwegen","finnland"],
econ:{leader:"Mexiko / Indien",share:"größte Produzenten von Limetten & Zitronen",world:"~20 Mio t Limetten & Zitronen / Jahr",price:"ca. 0,300,50 € / 100 g"},
note:"Wie alle Zitrusfrüchte braucht die Limette Wärme und verträgt keinen Frost.",
more:"Limettensaft hält angeschnittene Avocado grün die Säure bremst das Braunwerden."},
mais:{nm:"Mais (Tortilla)",ic:"🌽",correct:["usa","brasilien"],choices:["usa","brasilien","island","saudi"],
econ:{leader:"USA",share:"USA & Brasilien sind große Mais-Exporteure",world:"~1,2 Mrd t Mais / Jahr",price:"ca. 0,100,20 € / 100 g (Mehl)"},
note:"Mais stammt ursprünglich aus Mexiko, wächst heute aber weltweit nur nicht im ewigen Eis oder in der reinen Wüste.",
more:"Mais ist mengenmäßig das meistangebaute Getreide der Welt noch vor Weizen und Reis."},
erdnuss:{nm:"Erdnüsse",ic:"🥜",correct:["china","indien"],choices:["china","indien","norwegen","island"],
econ:{leader:"China",share:"China & Indien sind die größten Produzenten",world:"~50 Mio t / Jahr",price:"ca. 0,400,80 € / 100 g"},
note:"Erdnüsse reifen UNTER der Erde und brauchen warmen, lockeren Boden.",
more:"Botanisch sind Erdnüsse keine Nüsse, sondern Hülsenfrüchte verwandt mit Erbsen und Bohnen."},
chili:{nm:"Chili",ic:"🌶️",correct:["indien","china"],choices:["indien","china","norwegen","finnland"],
econ:{leader:"Indien",share:"Indien ist weltgrößter Chili-Produzent und -Exporteur",world:"~40 Mio t Chilis & Paprika / Jahr",price:"ca. 13 € / 100 g (getrocknet)"},
note:"Chilis lieben Hitze im kühlen Norden reifen sie kaum aus.",
more:"Die Schärfe kommt vom Stoff Capsaicin; gemessen wird sie in Scoville-Einheiten."},
kichererbsen:{nm:"Kichererbsen",ic:"🟤",correct:["indien"],choices:["indien","norwegen","island","schweden"],
econ:{leader:"Indien",share:"~70 % der Weltproduktion",world:"~15 Mio t / Jahr",price:"ca. 0,200,40 € / 100 g"},
note:"Kichererbsen mögen Wärme und Trockenheit im nassen, kalten Norden gedeihen sie schlecht.",
more:"Aus Kichererbsen werden Hummus und Falafel gemacht Grundnahrung im Nahen Osten."},
kumin:{nm:"Kreuzkümmel",ic:"🌿",correct:["indien"],choices:["indien","norwegen","kanada","polen"],
econ:{leader:"Indien",share:"größter Produzent und Exporteur",world:"Hauptanbau in Indien & Vorderasien",price:"ca. 12 € / 100 g"},
note:"Kreuzkümmel braucht ein heißes, trockenes Klima zum Reifen.",
more:"Vorsicht: Kreuzkümmel wird oft mit Kümmel verwechselt schmeckt aber ganz anders."},
apfel:{nm:"Äpfel",ic:"🍎",correct:["oesterreich","polen"],choices:["oesterreich","polen","saudi","island"],
econ:{leader:"Polen (EU) / China (Welt)",share:"China ist weltweit Nr. 1, Polen größter EU-Produzent",world:"~95 Mio t Äpfel / Jahr",price:"ca. 0,150,30 € / 100 g"},
note:"Äpfel mögen es gemäßigt, mit kühlem Winter. In der Wüste oder Arktis wachsen sie nicht.",
more:"Ein heimischer Apfel reist oft nur wenige Kilometer ganz anders als Vanille oder Kakao vom anderen Ende der Welt."},
};
/* ====================== GERICHTE ======================
* WICHTIG: Die 30 Gerichte werden aus assets/data/dishes.json geladen.
* Vorher 10 Inline-Gerichte → ersetzt durch async-Load (siehe loadWeltkuecheData).
* Zusätzliche Länder + Zutaten werden aus countries-extras.json + ingredients-extras.json gemerged.
*/
let DISHES = [];
let _basePath = ''; // wird vom Wrapper-PHP gesetzt (BASE_PATH/sims/weltkueche/)
/* ====================== SFX (ElevenLabs-Sounds) ======================
* Lazy-load, gecached pro Name, defensive — fällt still bei Fehlern.
* Lautstärke aus localStorage (Default 0.55); Schnellklicks via cloneNode. */
const _sfxCache = {};
function _sfxVolume() {
try {
const v = parseFloat(localStorage.getItem('wk-sfx-vol'));
return isNaN(v) ? 0.55 : Math.max(0, Math.min(1, v));
} catch (_) { return 0.55; }
}
function playSfx(name) {
try {
if (!_basePath) return;
let a = _sfxCache[name];
if (!a) {
a = new Audio(_basePath + 'sounds/' + name + '.mp3');
a.preload = 'auto';
_sfxCache[name] = a;
}
// Wenn schon läuft (z. B. schneller Doppelklick): Clone für überlappende Wiedergabe
if (!a.paused && a.currentTime > 0.05) {
const clone = a.cloneNode();
clone.volume = _sfxVolume();
clone.play().catch(()=>{});
return;
}
a.currentTime = 0;
a.volume = _sfxVolume();
a.play().catch(()=>{});
} catch (_) {}
}
window.playSfx = playSfx;
async function loadWeltkuecheData() {
const base = (window.WK_BASE || './');
const [dishesJ, countriesFull, prodMatrix, transportJ] = await Promise.all([
fetch(base + 'assets/data/dishes.json').then(r => r.json()),
fetch(base + 'assets/data/countries-full.json').then(r => r.json()),
fetch(base + 'assets/data/production-matrix.json').then(r => r.json()),
fetch(base + 'assets/data/transport.json').then(r => r.json()).catch(() => ({})),
]);
// Länder-Map komplett aus countries-full ersetzen (überschreibt frühere inline-Defaults)
Object.entries(countriesFull).forEach(([k, v]) => { C[k] = v; });
// ING-Map aus production-matrix bauen
Object.entries(prodMatrix).forEach(([k, v]) => {
ING[k] = {
nm: v.nm, ic: v.ic,
producers: v.producers || {},
climate_possible: v.climate_possible || [],
fact: v.fact || '',
more: v.more || ''
};
});
DISHES = Object.entries(dishesJ)
.filter(([k]) => !k.startsWith('_'))
.map(([id, d]) => Object.assign({id}, d));
window.WK_TRANSPORT = transportJ;
_basePath = base;
}
/* ========== KLIMA-BILANZ: pro Gericht CO₂-Berechnung der Welt-Zutaten ========== */
function calculateClimateImpact(dish) {
const T = window.WK_TRANSPORT || {};
const factors = (T._meta && T._meta.co2_factors_g_per_tkm) || {schiff:8, bahn:30, lkw:80, flugzeug:500};
let total = 0;
const details = [];
(dish.ings || []).forEach(ingKey => {
const ing = ING[ingKey];
if (!ing || !ing.correct || !ing.correct[0]) return;
const country = ing.correct[0];
const t = T[country];
if (!t) return;
const mass_kg = 0.05; // 50 g pro Welt-Zutat (didaktischer Mittelwert)
const factor = factors[t.typical] || 80;
const co2 = t.distance_km * mass_kg * factor / 1000; // → g CO₂
total += co2;
details.push({
ing: ing.nm,
country: (C[country] && C[country].n) || country,
flag: (C[country] && C[country].f) || '',
mode: t.typical,
dist: t.distance_km,
co2: Math.round(co2)
});
});
details.sort((a, b) => b.co2 - a.co2);
return { total: Math.round(total), details };
}
function climateImpactBlock(dish) {
const imp = calculateClimateImpact(dish);
if (!imp.details.length) return '';
const modeIcon = { schiff: '🚢', bahn: '🚂', lkw: '🚚', flugzeug: '✈️' };
let categoryRef = '';
if (dish.category === 'regional-at') categoryRef = '🏔 typischer AT-regionaler Wert: <b>3080 g</b>';
else if (dish.category === 'dach') categoryRef = '🏘 typischer DACH-Wert: <b>80200 g</b>';
else if (dish.category === 'europa') categoryRef = '🇪🇺 typischer Europa-Wert: <b>200500 g</b>';
else if (dish.category === 'exotisch') categoryRef = '🌍 typischer Exotik-Wert: <b>5001500 g</b>';
// Bewertung
let badge = '🌱', verdict = 'sehr klimafreundlich';
if (imp.total >= 500) { badge = '🔥'; verdict = 'klimaintensiv — viel Welthandel'; }
else if (imp.total >= 200) { badge = '⚠️'; verdict = 'mittlerer Klima-Footprint'; }
else if (imp.total >= 80) { badge = '🌤'; verdict = 'noch okay, einige Welt-Zutaten'; }
return `<div style="background:var(--green-soft);border-radius:14px;padding:14px;margin:14px 0">
<h3 style="font-size:.96rem;margin-bottom:8px">🌍 Klimabilanz der Welt-Zutaten</h3>
<div style="font-size:1.6rem;font-weight:800;color:var(--green-d);margin-bottom:4px">${badge} ${imp.total} g CO₂ pro Portion</div>
<div style="font-size:.84rem;color:var(--muted);margin-bottom:10px">${verdict}${categoryRef?' · '+categoryRef:''}</div>
<table style="width:100%;font-size:.82rem;border-collapse:collapse">
<thead><tr style="text-align:left;color:var(--muted)"><th style="padding:4px 0">Zutat</th><th>Land</th><th>Weg</th><th style="text-align:right">CO₂</th></tr></thead>
<tbody>
${imp.details.map(r => `<tr style="border-top:1px solid rgba(0,0,0,.08)">
<td style="padding:4px 0">${esc(r.ing)}</td>
<td>${r.flag} ${esc(r.country)}</td>
<td>${modeIcon[r.mode]||''} ${r.dist} km</td>
<td style="text-align:right;font-variant-numeric:tabular-nums"><b>${r.co2} g</b></td>
</tr>`).join('')}
</tbody>
</table>
<div style="margin-top:10px;font-size:.78rem;color:var(--muted)">
<b>So gerechnet:</b> 50 g pro Welt-Zutat × Strecke × Transport-Faktor (Schiff 8, Bahn 30, Lkw 80, Flugzeug 500 g CO₂ pro Tonnen-Kilometer).
</div>
${dish.category === 'exotisch' || imp.total > 300 ? `<div style="margin-top:10px;background:#fff;border-radius:10px;padding:10px 12px;font-size:.84rem">
<b>💭 Was meinst du?</b> Lokale Küche hat winzigen CO₂-Fußabdruck, aber begrenzte Vielfalt.
Globale Küche bringt Vitamin-/Geschmacksvielfalt — kostet aber 1030× mehr Klima.
Wo ist deine Balance?
</div>` : ''}
</div>`;
}
const BADGES = [
{id:"first", icon:"🌱",name:"Erste Zutat", desc:"Dein erstes richtiges Herkunftsland gefunden."},
{id:"pair", icon:"🔗",name:"Doppel-Forscher", desc:"Beide Herkunftsländer einer Zutat entdeckt."},
{id:"spice", icon:"🧂",name:"Gewürz-Kenner", desc:"8 Herkunftsländer korrekt verortet."},
{id:"climate",icon:"🌡️",name:"Klima-Detektiv", desc:"3 Zutaten komplett ohne Fehler gelöst."},
{id:"perfect",icon:"🎯",name:"Volltreffer-Koch", desc:"Ein Gericht ganz ohne Fehlklick gelöst."},
{id:"dish", icon:"🍽️",name:"Weltkoch", desc:"Dein erstes Gericht abgeschlossen."},
{id:"landlk", icon:"🗺️",name:"Geografie-Genie", desc:"Erkannt: Binnenland hat kein Meer (Nori)."},
{id:"trade", icon:"📈",name:"Welthandels-Profi",desc:"Über 3000 Punkte gesammelt."},
{id:"explorer",icon:"🌐",name:"Kontinent-Hopper",desc:"5 Gerichte abgeschlossen."},
{id:"star", icon:"⭐",name:"Sterne-Koch", desc:"Alle Gerichte abgeschlossen."},
];
/* ====================== STATE ====================== */
let state={name:"",score:0,badges:[],completed:[],totalCorrect:0,cleanIngredients:0};
let map,markerLayer,current;
const store={get(k,d){try{const v=localStorage.getItem(k);return v?JSON.parse(v):d;}catch(e){return d;}},set(k,v){try{localStorage.setItem(k,JSON.stringify(v));}catch(e){}}};
/* ====================== NAVIGATION ====================== */
let lastScreen="dishes";
function show(id){
if(id!=="badges") lastScreen=id;
document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active'));
document.getElementById('screen-'+id).classList.add('active');
if(id==='badges') renderBadges();
window.scrollTo({top:0,behavior:'smooth'});
}
function backFromSub(){show(lastScreen);}
function backToDishes(){show('dishes');}
async function startGame(){
// Erst die JSON-Daten async laden (30 Gerichte + Country/Ingredient-Extras + Transport)
if (!DISHES.length) {
try { await loadWeltkuecheData(); }
catch (e) { alert('Daten konnten nicht geladen werden: ' + e.message); return; }
}
// Name aus Plattform-Session — kein Eingabefeld mehr
state.name = (window.WK_STUDENT_NAME && window.WK_STUDENT_NAME !== 'Gast') ? window.WK_STUDENT_NAME : 'Forscher:in';
document.getElementById('statName').textContent = state.name;
document.getElementById('dishCount').textContent = DISHES.length + ' Gerichte aus aller Welt.';
renderDishes();
// Onboarding kommt IMMER — auch bei wiederholtem Aufruf
startTutorial();
}
// Auto-Start: kein "Losforschen"-Button mehr nötig.
window.addEventListener('DOMContentLoaded', () => {
// Splash-Bild aus Plattform-Assets (existiert als WebP, fällt sonst still aus)
const splash = document.getElementById('wkSplashImg');
if (splash && window.WK_API_BASE) {
const apiBase = window.WK_API_BASE.replace(/\/api$/,'');
splash.src = apiBase + '/assets/img/card-weltkueche.webp';
splash.onerror = () => { splash.style.display = 'none'; };
}
startGame();
});
/* Glossar-Link-Helfer: erzeugt einen Inline-Tooltip-Trigger (Plattform-Standard).
* Wird beim Klick durch GlossarTooltip.js zum kleinen Popup mit Kurztext + Link
* auf den vollen Glossar-Eintrag. Fallback: externer Tab auf Glossar-Page, falls
* GlossarTooltip nicht geladen wurde. */
function glossarLink(label, slug) {
return `<span class="ggs-g" data-glossar="${slug}" style="font-weight:700;color:var(--green-d)">${label}</span>`;
}
window.glossarLink = glossarLink;
/* ====================== TUTORIAL ====================== */
/* TUTORIAL_STEPS werden in startTutorial() befüllt, damit die Inline-Glossar-Links
* (window.glossarLink) zur Laufzeit auf den richtigen Plattform-Pfad gehen. */
let TUTORIAL_STEPS = [];
function buildTutorialSteps() {
const g = (label, slug) => glossarLink(label, slug);
const imgBase = (window.WK_BASE || './') + 'assets/img/onboarding/';
TUTORIAL_STEPS = [
{ img: imgBase + 'woher.webp',
title: 'Woher kommt mein Essen?',
html: 'Pizza, Sushi, Apfelstrudel, Curry, Gröstl, Tortilla — viele Mahlzeiten auf deinem Teller haben eine <b>überraschende Reise</b> hinter sich, manche bleiben fast vor der Haustür.<br><br>'
+ 'In dieser Forschungs-Werkstatt erkundest du <b>30 Gerichte aus aller Welt</b>: ihre Zutaten, ihre Rezepte und ihren ' + g('Klima-Fußabdruck', 'co2-fussabdruck-lebensmittel') + '.' },
{ img: imgBase + 'forschung.webp',
title: 'Was mache ich jetzt?',
html: '<div style="text-align:left;display:inline-block;line-height:1.55;font-size:.95em">'
+ '<b>1. Gericht wählen</b> — du bekommst zuerst das Rezept mit Schritten, Nährwerten und Zubereitungs-Zeit.<br><br>'
+ '<b>2. Welt-Zutaten erforschen</b> — pro Welt-Zutat (z. B. Pfeffer, Kakao, Vanille) wählst du auf der Weltkarte das richtige Herkunftsland an. Hast du das erste Land gefunden, kannst du das zweite wählen.</div>' },
{ img: imgBase + 'forschung.webp',
title: 'Was lerne ich dabei?',
html: '<div style="text-align:left;display:inline-block;line-height:1.55;font-size:.95em">'
+ '<b>Wirtschaft &amp; Geografie</b> — pro Treffer erfährst du, wer Weltmarktführer im ' + g('Welthandel', 'welthandel') + ' ist, wie groß die ' + g('Welternte', 'welternte') + ' ist und was es bei uns kostet.<br><br>'
+ '<b>' + g('Klimabilanz', 'co2-fussabdruck-lebensmittel') + '</b> — am Ende siehst du, wie weit die Zutaten gereist sind.</div>' },
{ img: imgBase + 'markt.webp',
title: 'Was genau ist gesucht?',
html: '<b>Gesucht ist nicht der größte Produzent der Welt</b> — sondern woher die Zutaten kommen, die wir hier bei uns auf dem Markt finden.<br><br>'
+ 'Vier Kategorien helfen dir:<br>'
+ '<div style="text-align:left;display:inline-block;margin:8px 0;line-height:1.55;font-size:.95em">'
+ '<b>🇪🇺 Hauptlieferant</b> — kommt typisch zu uns auf den Markt<br>'
+ '<b>🌐 Welt-Hauptproduzent</b> — Weltmarkt-Big-Player, kommt bei uns aber selten an<br>'
+ '<b>🌍 Off-Season-Lieferant</b> — füllt nur unsere Winter-Saison<br>'
+ '<b>🏠 Eigenkonsum / regional</b> — bleibt im eigenen Land</div><br>'
+ 'Manche Länder produzieren <i>viel</i>, sind bei uns aber nicht im Regal — etwa weil sie alles selbst verbrauchen oder weil der Transport zu lang ist.' },
{ img: imgBase + 'punkte.webp',
title: 'Punkte & Abzeichen',
html: 'Jedes richtig zugeordnete Land bringt <b>Punkte</b>. Doppel-Treffer (zweites Herkunftsland) zählen extra.<br><br>'
+ 'Es gibt 10 <b>Abzeichen</b> zum Sammeln — z. B. <b>Volltreffer-Koch</b> (Gericht ohne Fehler), <b>Geografie-Genie</b> (erkennen, dass ein Binnenland kein Meer hat) oder <b>Sterne-Koch</b> (alle 30 Gerichte).' },
{ img: imgBase + 'klima.webp',
title: 'Klimabilanz im Blick',
html: 'Bei jedem Gericht siehst du, <b>wie viel CO₂</b> die Reise der Zutaten verursacht. Transport-Wege sind ein Riesen-Unterschied: per Schiff sehr klein, per Flugzeug sehr groß. Mehr dazu: ' + g('CO₂-Fußabdruck von Lebensmitteln', 'co2-fussabdruck-lebensmittel') + '.<br><br>'
+ 'Vergleiche: Tiroler Gröstl ≈ 50 g CO₂, Sushi-Rolle ≈ 900 g.<br><br>'
+ 'Am Schluss denken wir gemeinsam nach: ' + g('Saisonal &amp; Regional', 'saisonal-regional') + ' vs. ' + g('Welthandel', 'welthandel') + ' — wo ist deine Balance?' },
{ img: imgBase + 'glossar.webp',
title: 'Begriffe nachschlagen',
html: 'Du stößt in der Simulation auf Fachbegriffe? Klick einfach drauf — sie öffnen das Glossar:<br><br>'
+ '<div style="display:flex;flex-wrap:wrap;gap:6px;justify-content:center;margin:10px 0">'
+ g('Welthandel', 'welthandel') + g('Klimazone', 'klimazone') + g('Monokultur', 'monokultur')
+ g('Fair Trade', 'fair-trade') + g('Saisonal &amp; Regional', 'saisonal-regional') + g('CO₂-Fußabdruck', 'co2-fussabdruck-lebensmittel')
+ '</div>'
+ '<div style="text-align:center;margin-top:14px"><a href="' + (window.WK_API_BASE || '').replace(/\/api$/,'') + '/pages/glossar.php?module=weltkueche" target="_blank" rel="noopener" style="display:inline-block;background:var(--green);color:#fff;text-decoration:none;border-radius:10px;padding:10px 16px;font-weight:700">📖 Alle Weltküche-Begriffe öffnen →</a></div>' }
];
}
let _tutorialIdx = 0;
function startTutorial() {
_tutorialIdx = 0;
if (!TUTORIAL_STEPS.length) buildTutorialSteps();
document.getElementById('tutorial-overlay').style.display = 'flex';
renderTutorialStep();
}
function renderTutorialStep() {
const step = TUTORIAL_STEPS[_tutorialIdx];
const imgEl = document.getElementById('tutorial-step-img');
if (imgEl) { imgEl.src = step.img; imgEl.alt = step.title; }
document.getElementById('tutorial-step-title').textContent = step.title;
document.getElementById('tutorial-step-body').innerHTML = step.html;
// Dots
const dots = TUTORIAL_STEPS.map((_, i) =>
'<span style="width:8px;height:8px;border-radius:50%;background:' + (i === _tutorialIdx ? 'var(--green)' : 'rgba(0,0,0,.15)') + '"></span>').join('');
document.getElementById('tutorial-dots').innerHTML = dots;
// Last step → "Los geht's!"
document.getElementById('tutorial-next').textContent = (_tutorialIdx >= TUTORIAL_STEPS.length - 1) ? 'Los geht\'s! →' : 'Weiter →';
}
function nextTutorialStep() {
if (_tutorialIdx >= TUTORIAL_STEPS.length - 1) {
closeTutorial();
show('dishes');
} else {
_tutorialIdx++;
renderTutorialStep();
}
}
function skipTutorial() {
closeTutorial();
show('dishes');
}
function closeTutorial() {
document.getElementById('tutorial-overlay').style.display = 'none';
store.set('wk_tutorial_done', true);
}
window.startTutorial = startTutorial;
window.nextTutorialStep = nextTutorialStep;
window.skipTutorial = skipTutorial;
// Plattform-Convention: GGS_TUTORIAL (siehe live-client.js).
window.GGS_TUTORIAL = { simId: 'weltkueche', title: 'Welt-Küche', replay: function() { startTutorial(); } };
function updateHUD(){
document.getElementById('statScore').textContent=state.score;
document.getElementById('statBadges').textContent=state.badges.length;
}
// Live-State für Lehreransicht (alle ~4 s vom Plattform-live-client.js abgefragt).
window.GGS_LIVE_STATE = function() {
if (!state) return null;
const dish = current && current.dish;
const ingTot = dish ? (dish.ings||[]).length : 0;
const ingDone = current ? (current.solved||[]).length : 0;
const completedDish = (state.completed||[]).length;
const totalDish = DISHES.length;
// progressPct = Fortschritt im aktuellen Gericht (für generische Vergleichs-Anzeige);
// wenn kein Gericht offen, Anteil der bereits gelösten Gerichte
const dishPct = ingTot > 0 ? Math.round(ingDone/ingTot*100) : 0;
const overallPct = totalDish > 0 ? Math.round(completedDish/totalDish*100) : 0;
return {
phase: dish ? (current && current.solved ? 'forschen' : ('Zutat ' + (ingDone+1) + '/' + ingTot)) : ('Auswahl · ' + completedDish + '/' + totalDish),
progressPct: dish ? dishPct : overallPct,
orderIndex: ingDone, // für generische Vergleichs-Anzeige (Zutat-Index)
totalOrders: ingTot,
dishId: dish ? dish.id : null,
dishName: dish ? dish.name : null,
dishCategory: dish ? dish.category : null,
dishOrigin: dish ? dish.origin : null,
ingTotal: ingTot,
ingSolved: ingDone,
ingCurrent: (current && current.dish && current.idx<current.dish.ings.length) ? current.dish.ings[current.idx] : null,
mistakesInDish: current ? (current.mistakesInDish||0) : 0,
score: state.score || 0,
completed: completedDish,
completedTotal: totalDish,
badgesEarned: (state.badges||[]).length,
badgesTotal: BADGES ? BADGES.length : 10,
levelWon: completedDish >= totalDish,
};
};
/* ====================== DISH GRID ====================== */
let _activeFilter = 'all';
function renderDishes(){
const g=document.getElementById('dishGrid'); g.innerHTML="";
const filtered = DISHES.filter(d => _activeFilter === 'all' || d.category === _activeFilter);
filtered.forEach(d=>{
const done=state.completed.includes(d.id);
const el=document.createElement('button');
el.className="dishcard"; el.onclick=()=>openDish(d.id);
const imgPath = d.imagePath ? (_basePath + d.imagePath) : null;
const catBadge = ({
'regional-at': '🇦🇹 AT-regional',
'dach': '🇦🇹🇩🇪🇨🇭 DACH',
'europa': '🇪🇺 Europa',
'exotisch': '🌍 Exotisch'
})[d.category] || '';
el.innerHTML=`${done?'<span class="done-flag">✓ Gelöst</span>':'<span class="diff">'+d.diff+'</span>'}
${imgPath ? `<img src="${imgPath}" alt="${d.name}" style="width:100%;height:140px;object-fit:cover;border-radius:14px;margin-bottom:10px;background:var(--bg2)" onerror="this.style.display='none'">` : `<div class="emoji">${d.emoji}</div>`}
<h3>${d.name}</h3>
<div class="origin">📍 ${d.origin}</div>
${d.diff_note?'<div style="font-size:.74rem;color:var(--muted);font-style:italic;margin-top:4px">'+d.diff_note+'</div>':''}
<div class="tags" style="margin-top:10px">
${catBadge?'<span class="tag" style="background:var(--green-soft);color:var(--green-d);font-weight:700">'+catBadge+'</span>':''}
${d.tags.map(t=>'<span class="tag">'+t+'</span>').join('')}
<span class="tag">${d.ings.length} Welt-Zutaten</span>
</div>`;
g.appendChild(el);
});
}
function setDishFilter(cat) {
_activeFilter = cat;
document.querySelectorAll('#dishFilter button').forEach(b => b.classList.toggle('active', b.dataset.cat === cat));
renderDishes();
}
window.setDishFilter = setDishFilter;
/* ====================== GAME ====================== */
function openDish(id){
const dish=DISHES.find(d=>d.id===id);
current={dish,idx:0,solved:[],dishScore:0,mistakesInDish:0,found:[],wrongHere:0,wrongStreak:0};
// Aufgabe-Cache reset pro Gericht — neue Korrekt-Auswahl + neue Distraktoren
_taskCache = {};
showRecipeScreen(dish);
}
function showRecipeScreen(d) {
// Vor-Forschungs-Ansicht: NUR Bild + Header + Nährwerte + Rezept-Schritte + Tipp.
// Lehrplan-Bezug ("Wenn dich das interessiert") + Trivia + Klimabilanz kommen
// erst NACH dem Forschen auf dem recipe-full-Screen.
const host = document.getElementById('recipe-body');
const imgPath = d.imagePath ? (_basePath + d.imagePath) : null;
const n = d.nutrition || {};
const r = d.recipe || {};
let html = '';
if (imgPath) {
html += `<img src="${imgPath}" alt="${d.name}" style="width:100%;max-height:280px;object-fit:cover;border-radius:18px;margin-bottom:18px;background:var(--bg2)" onerror="this.style.display='none'">`;
}
html += `<div style="display:flex;align-items:center;gap:12px;margin-bottom:8px">
<span style="font-size:2.4rem">${d.emoji}</span>
<div><h2 style="font-size:1.6rem;margin:0">${d.name}</h2>
<div style="color:var(--muted);font-size:.92rem">📍 ${d.origin} · <b>${d.diff}</b>${d.diff_note?' · <em>'+d.diff_note+'</em>':''}</div>
</div>
</div>`;
// Nährwerte
if (Object.keys(n).length) {
html += `<div style="background:var(--bg2);border-radius:14px;padding:14px;margin:16px 0">
<h3 style="font-size:.92rem;margin-bottom:10px">📊 Nährwerte pro Portion (${n.portion_g||'?'} g)</h3>
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:10px;font-size:.86rem">
<span>kcal: <b>${n.kcal||''}</b></span>
<span>Eiweiß: <b>${n.protein_g||''} g</b></span>
<span>Kohlenhydrate: <b>${n.carbs_g||''} g</b></span>
<span>Fett: <b>${n.fat_g||''} g</b></span>
<span>Ballaststoffe: <b>${n.fiber_g||''} g</b></span>
<span>Salz: <b>${n.salt_g||''} g</b></span>
</div>
</div>`;
}
// Rezept-Schritte (inkl. Tipp) — Schluss vor dem Forschen.
if (r.steps && r.steps.length) {
html += `<div style="margin:16px 0 0">
<h3 style="font-size:1rem;margin-bottom:8px">👩‍🍳 So machst du es (${r.portions||'?'} Portionen, ${r.time_min||'?'} min)</h3>
<ol style="padding-left:1.4rem;line-height:1.55;font-size:.92rem">${r.steps.map(s=>'<li style="margin-bottom:6px">'+esc(s)+'</li>').join('')}</ol>
${r.tipp?'<div style="background:var(--gold-soft);border-radius:10px;padding:10px 12px;font-size:.86rem;margin-top:10px"><b style="color:#8a6516">💡 Tipp:</b> '+esc(r.tipp)+'</div>':''}
</div>`;
}
host.innerHTML = html;
show('recipe');
}
function startResearch() {
const d = current.dish;
document.getElementById('gEmoji').textContent = d.emoji;
document.getElementById('gName').textContent = d.name;
document.getElementById('gOrigin').textContent = "📍 " + d.origin;
show('game'); renderIngList();
setTimeout(()=>{ initMap(); loadIngredient(); }, 80);
}
function esc(s){return String(s||'').replace(/[<>&"]/g,c=>({'<':'&lt;','>':'&gt;','&':'&amp;','"':'&quot;'}[c]));}
function initMap(){
if(!map){
map=L.map('map',{worldCopyJump:true,minZoom:1,maxZoom:6,scrollWheelZoom:true}).setView([25,15],2);
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png',
{attribution:'© OpenStreetMap-Mitwirkende · © CARTO',subdomains:'abcd',maxZoom:6}).addTo(map);
markerLayer=L.layerGroup().addTo(map);
}
setTimeout(()=>map.invalidateSize(),120);
}
function ingOf(i){return ING[current.dish.ings[i]];}
// Zählt nur die Welt-Zutaten (für Progress-Dots & Beendigungs-Logik)
function worldIngCount(dish){
let n = 0;
dish.ings.forEach(k => { if (ING[k] && isWorldIngredient(k)) n++; });
return n;
}
/* Eine Zutat zählt als "Welt-Zutat" für das Spiel, wenn sie mindestens
* 3 Producer mit nennenswerten Tonnen-Werten hat. Alles andere ist eine
* Spiesekammer-Sache (Hefe, Essig, Schmand, kulturelle Erzeugnisse) —
* wird im Game als "lokal verfügbar" angezeigt, aber nicht erforscht. */
function isWorldIngredient(key) {
const ing = ING[key]; if (!ing) return false;
const real = Object.values(ing.producers || {}).filter(p => p.t && p.t > 0);
return real.length >= 3;
}
/* ====================== AUFGABEN-GENERIERUNG ======================
* Neue Architektur (ab production-matrix.json):
*
* Jede Welt-Zutat hat:
* producers Land → {t, share, note} (nennenswerte Marktproduktion)
* climate_possible [land, …] (theoretisch möglich, kein Hauptmarkt)
*
* Pro Aufgabe wählen wir:
* - 2 ZUFÄLLIGE producers als "Spiel-Slot-Korrekt" — gewichtet nach Tonnage
* (damit Hauptproduzenten häufiger drankommen, aber nicht ausschließlich).
* - 2-4 Distraktoren aus dem Rest: gemischt aus climate_possible (für
* differenziertes Feedback) und klar-unmöglichen Ländern.
*
* Beim Klick gibt es drei Feedback-Stufen:
* 1) IM SPIEL-SLOT → "Treffer!" mit Mengen-Info
* 2) PRODUZENT ABER nicht im Slot → "Auch ein echter Produzent — gerade nicht gefragt"
* 3) climate_possible → "Theoretisch möglich (passende Klimazone), aber kein Hauptmarkt"
* 4) sonst → "Klimatisch nicht möglich — hier wächst das nicht"
*/
let _taskCache = {}; // pro ing-key → {correct:[c1,c2], choices:[…]}
function shuffleInPlace(a) {
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
function weightedSample(weighted, k) {
// weighted: [{key, weight}, …] → liefert k unterschiedliche Keys, weighted-random gezogen
const items = weighted.filter(w => w.weight > 0);
const out = [];
for (let n = 0; n < k && items.length; n++) {
const total = items.reduce((s, x) => s + x.weight, 0);
let r = Math.random() * total;
let i = 0;
while (r > items[i].weight) { r -= items[i].weight; i++; }
out.push(items[i].key);
items.splice(i, 1);
}
return out;
}
// Liefert ein Set aller in C{} bekannten Country-Slugs außer correct/excluded.
function impossibleCountries(corr, possible) {
const exclude = new Set([...corr, ...possible]);
const out = [];
Object.keys(C).forEach(c => { if (!exclude.has(c)) out.push(c); });
return out;
}
function buildTaskFor(ingKey) {
if (_taskCache[ingKey]) return _taskCache[ingKey];
const ing = ING[ingKey];
if (!ing) return null;
// HEIMAT-REGEL: In einem regionalen Gericht kommt jede Zutat, die das
// Herkunftsland selbst erzeugt, von dort — logisch nachvollziehbar (ein
// Tiroler Gröstl nutzt Tiroler Speck, Tiroler Kartoffeln …). Der Test
// „erzeugt das Heimatland diese Zutat?" filtert automatisch: Österreich
// baut Kartoffeln an (→ Österreich), aber keinen Pfeffer und kein Olivenöl
// (→ dort greift die normale Welt-Logik weiter unten). Kein Länder-Bias,
// weil die Antwort am Gericht hängt, nicht am Standort der App.
const _hDish = current && current.dish;
const heimat = _hDish && _hDish.heimat;
if (heimat && C[heimat] && ing.producers && ing.producers[heimat]) {
const correct = [heimat];
const others = Object.entries(ing.producers)
.filter(([c]) => C[c] && c !== heimat)
.sort((a, b) => (b[1].t || 0) - (a[1].t || 0))
.map(([c]) => c);
const climatePossible = (ing.climate_possible || []).filter(c => C[c] && c !== heimat);
const distr = others.slice(0, 3);
if (climatePossible.length && distr.length < 3) distr.push(climatePossible[0]);
const choices = [...correct, ...distr];
shuffleInPlace(choices);
const label = (_hDish.signature && _hDish.signature[ingKey]) || null;
_taskCache[ingKey] = { correct, choices, possible: new Set(), signature: true, heimat, label };
return _taskCache[ingKey];
}
// Alle Producer (für Klassifizierung) und EU-relevante separat (für Korrekt-Auswahl).
// Eigenkonsum-Riesen (Indien-Bananen, USA-Zitronen etc.) sind zwar echte Produzenten,
// landen aber nicht in unseren Supermärkten — also nicht als „Spiel-Slot-Korrekt" ziehen.
// Gewichts-Faktoren pro market-Kategorie:
// eu = AT-Nachbar (4×) — wird in regionalen Produkten klar bevorzugt
// global = außer-EU aber in AT-Märkten (1×) — Standard für Welthandelsprodukte
// world-far = Big Player am Weltmarkt, bei uns aber selten (0.4×) — kommt nur
// ab und zu dran, mit Zusatztext bei Auflösung
// off-season = Süd-Hemisphäre, nur EU-Winter (0× = nicht im Korrekt-Slot)
// local/us-only/asia-only = NICHT in EU-Markt (0× = nicht im Korrekt-Slot,
// kommen nur als Distraktor + producer-local-Feedback)
const WEIGHT_BY_MARKET = { 'eu': 4, 'global': 1, 'world-far': 0.4 };
const allProducers = Object.entries(ing.producers || {})
.filter(([c, info]) => C[c])
.map(([c, info]) => {
const market = info.market || 'global';
const baseWeight = Math.max(1, Math.log10((info.t || 1000) + 10));
const mult = WEIGHT_BY_MARKET[market] ?? 0;
return {key: c, weight: baseWeight * mult, market};
});
// Korrekt-Slot-Pool = alles mit positivem Gewicht (eu/global/world-far)
const euRelevant = allProducers.filter(p => p.weight > 0);
const sourcePool = euRelevant.length >= 2 ? euRelevant : allProducers;
const climatePossible = (ing.climate_possible || []).filter(c => C[c]);
// Die tatsächlichen HAUPTLIEFERANTEN für UNSEREN Markt — nicht gewürfelt.
// Stufenfolge: EU-Nachbar (kurzer Weg, kein Zoll → für uns der Hauptweg) VOR
// fernem Welt-Lieferant (nur wo es hier nicht wächst, z. B. Pfeffer) VOR
// „Big Player, der bei uns kaum ankommt". Innerhalb einer Stufe zählt die
// Menge. So bleibt DACH/EU priorisiert; Pfeffer (kein EU-Produzent) → die
// zwei größten Welt-Lieferanten Vietnam + Brasilien.
const MARKET_TIER = { 'eu': 0, 'global': 1, 'world-far': 2 };
const rankedPool = [...sourcePool].sort((a, b) =>
((MARKET_TIER[a.market] ?? 2) - (MARKET_TIER[b.market] ?? 2)) ||
(((ing.producers[b.key] || {}).t || 0) - ((ing.producers[a.key] || {}).t || 0)));
const correct = rankedPool.slice(0, Math.min(2, rankedPool.length)).map(p => p.key);
// 2-4 Distraktoren: 1-2 aus climate_possible (für didaktisches Feedback),
// Rest aus klar-unmöglichen
const distrCount = 2 + Math.floor(Math.random() * 3); // 2 bis 4
const possiblePool = climatePossible.filter(c => !correct.includes(c));
const possiblePicks = [...possiblePool];
shuffleInPlace(possiblePicks);
const possibleN = Math.min(Math.ceil(distrCount / 2), possiblePicks.length);
const fromPossible = possiblePicks.slice(0, possibleN);
const impossiblePool = impossibleCountries(correct, [...fromPossible, ...allProducers.map(p => p.key)]);
shuffleInPlace(impossiblePool);
const fromImpossible = impossiblePool.slice(0, distrCount - fromPossible.length);
const choices = [...correct, ...fromPossible, ...fromImpossible];
shuffleInPlace(choices);
_taskCache[ingKey] = { correct, choices, possible: new Set(fromPossible) };
return _taskCache[ingKey];
}
// Returns 'correct' | 'producer-eu-elsewhere' | 'producer-local' | 'climate-possible' | 'impossible'
function classifyGuess(ingKey, key) {
const task = _taskCache[ingKey];
const ing = ING[ingKey];
if (!task || !ing) return 'impossible';
if (task.correct.includes(key)) return 'correct';
if (ing.producers && ing.producers[key]) {
const m = ing.producers[key].market || 'global';
if (m === 'global' || m === 'eu') return 'producer-eu-elsewhere';
return 'producer-local'; // produziert, aber nicht für EU-Markt
}
if (ing.climate_possible && ing.climate_possible.includes(key)) return 'climate-possible';
return 'impossible';
}
function renderIngList(){
const d=current.dish,list=document.getElementById('ingList'); list.innerHTML="";
const worldIdx = [], pantryIdx = [];
d.ings.forEach((key, i) => {
if (!ING[key]) return;
if (isWorldIngredient(key)) worldIdx.push(i); else pantryIdx.push(i);
});
// Welt-Zutaten oben
worldIdx.forEach(i => {
const key = d.ings[i]; const ing=ING[key];
const solved=current.solved.includes(i); const active=i===current.idx&&!solved;
const task = active ? buildTaskFor(key) : (_taskCache[key] || null);
const targetCount = task ? task.correct.length : 2;
const twoFlag = targetCount > 1;
const li=document.createElement('li');
li.className="ing-item"+(solved?" solved":"")+(active?" active":"");
let right = solved?'<span class="chk">✓</span>':'';
if(active && twoFlag){ right='<span class="cnt">'+current.found.length+' / '+targetCount+'</span>'; }
li.innerHTML=`<span class="ic">${ing.ic}</span><span class="nm">${ing.nm}</span>${right}`;
list.appendChild(li);
});
// Spiesekammer-Block (lokal, kein Welt-Anbau)
if (pantryIdx.length) {
const hdr = document.createElement('li');
hdr.style.cssText = 'list-style:none;font-size:.7rem;color:var(--muted);font-weight:700;text-transform:uppercase;letter-spacing:.04em;padding:10px 0 4px;border-top:1px dashed var(--line);margin-top:8px';
hdr.textContent = '🏠 Lokal aus der Speisekammer';
list.appendChild(hdr);
pantryIdx.forEach(i => {
const key = d.ings[i]; const ing=ING[key];
const li=document.createElement('li');
li.className="ing-item solved";
li.style.opacity = '.65';
li.innerHTML=`<span class="ic">${ing.ic}</span><span class="nm">${ing.nm}</span><span class="pantry-badge">lokal</span>`;
list.appendChild(li);
});
}
const dots=document.getElementById('progressDots'); dots.innerHTML="";
worldIdx.forEach(i=>{const s=document.createElement('span');s.className="d"+(current.solved.includes(i)?" on":"");dots.appendChild(s);});
}
function loadIngredient(){
const d=current.dish;
// Spiesekammer-Zutaten überspringen (Hefe, Essig, Schmand, kulturelle Erzeugnisse)
while (current.idx < d.ings.length) {
if (current.solved.includes(current.idx)) { current.idx++; continue; }
const k = d.ings[current.idx];
if (!ING[k] || !isWorldIngredient(k)) {
current.solved.push(current.idx);
current.idx++;
continue;
}
break;
}
if(current.idx>=d.ings.length){ finishDish(); return; }
const ingKey = d.ings[current.idx];
const ing=ingOf(current.idx);
// Aufgabe frisch bauen (Cache leeren). Die Korrekt-Auswahl ist bewusst
// deterministisch: Heimat-Regel bzw. echte Hauptlieferanten nach Menge.
delete _taskCache[ingKey];
const task = buildTaskFor(ingKey);
current.found=[]; current.wrongHere=0; current.wrongStreak=0; current.attempted=new Set();
const two = task && task.correct.length > 1;
const frage = two
? 'finde die <b>zwei</b> Herkunftsländer aus dieser Auswahl'
: (task && task.signature
? 'aus welchem Land stammt diese Zutat in <b>diesem Gericht</b>?'
: 'aus welchem Land kommt diese Zutat häufig zu uns auf den Markt?');
document.getElementById('taskBanner').innerHTML=
`🔎 Suche: <b>${ing.nm}</b> ${frage}`;
document.getElementById('resultCard').className="result-card";
drawMarkers(ing); renderIngList();
}
function drawMarkers(ing){
if(!map||!markerLayer) initMap();
markerLayer.clearLayers(); current.markers={};
const pts=[];
const ingKey = current.dish.ings[current.idx];
const task = buildTaskFor(ingKey);
const choices = task ? task.choices : [];
choices.forEach(key=>{
const c=C[key]; if(!c) return;
pts.push([c.lat,c.lng]);
const icon=L.divIcon({className:'cm-wrap',
html:`<button class="country-btn" data-key="${key}"><span class="flag">${c.f}</span><span class="nm">${c.n}</span></button>`,
iconSize:[0,0],iconAnchor:[0,0]});
const m=L.marker([c.lat,c.lng],{icon,riseOnHover:true,zIndexOffset:200}).addTo(markerLayer);
current.markers[key]=m;
m.getElement().querySelector('.country-btn').addEventListener('click',e=>{e.stopPropagation();guess(key);});
});
if(pts.length) map.fitBounds(pts,{paddingTopLeft:[70,80],paddingBottomRight:[70,50],maxZoom:4});
}
function btnFor(key){const m=current.markers[key];return(m&&m.getElement())?m.getElement().querySelector('.country-btn'):null;}
function guess(key){
const b=btnFor(key); if(!b||b.disabled) return;
const ingKey = current.dish.ings[current.idx];
const ing=ingOf(current.idx);
const c=C[key];
const cls = classifyGuess(ingKey, key);
const task = _taskCache[ingKey];
if (current.attempted) current.attempted.add(key);
playSfx('click-country');
if (cls === 'correct') {
b.classList.add('correct');
const pts = current.wrongStreak===0?100:60;
state.score+=pts; current.dishScore+=pts;
state.totalCorrect++; current.wrongStreak=0; current.found.push(key);
map.flyTo([c.lat,c.lng],3,{duration:.7});
const targetCount = task.correct.length;
if(current.found.length >= targetCount){
let bonus=0;
if(targetCount > 1){ bonus=50; state.score+=bonus; current.dishScore+=bonus; awardLive(['pair']); }
if(current.wrongHere===0) state.cleanIngredients++;
current.solved.push(current.idx);
Object.keys(current.markers).forEach(k=>{const x=btnFor(k);if(x)x.disabled=true;});
checkLiveBadges(ing,key);
showSolved(ing,pts,bonus);
// Sound nach dem korrekten Render-Frame, damit click-country abgeklungen ist
setTimeout(()=>playSfx('ingredient-solved'), 120);
} else {
const remaining = targetCount - current.found.length;
checkLiveBadges(ing,key);
showInterim(ing,key,pts,remaining);
setTimeout(()=>playSfx('ingredient-correct'), 120);
}
updateHUD(); renderIngList();
} else {
b.classList.add('wrong'); b.disabled=true;
current.wrongStreak++; current.wrongHere++; current.mistakesInDish++;
showWrong(key, cls, ingKey);
setTimeout(()=>playSfx('ingredient-wrong'), 120);
}
}
/* ====================== ERGEBNIS-KARTEN ====================== */
function fmtTonnes(t) {
if (!t || t <= 0) return '';
// Für Sek I lieber die volle Zahl mit Tausenderpunkt (220.000 t) als „220 Tsd t"
// oder das SI-Kürzel „kt", das die Altersgruppe nicht kennt. Millionen bleiben
// als „Mio t" gut lesbar.
if (t >= 1000000) return (t/1000000).toFixed(t >= 10000000 ? 0 : 1).replace('.0','').replace('.', ',') + ' Mio t/Jahr';
return t.toLocaleString('de-DE') + ' t/Jahr';
}
function regionsHTML(keys){
return `<div class="regions"><div class="rlabel">Über die Region${keys.length>1?'en':''}</div>`+
keys.map(k=>{const c=C[k];return `<div class="region-item"><span class="rf">${c.f}</span><span><span class="rn">${c.n}</span>${c.info}</span></div>`;}).join('')+
`</div>`;
}
function producerEconHTML(ingKey, correctKeys) {
const ing = ING[ingKey];
if (!ing || !ing.producers) return '';
const all = Object.entries(ing.producers).filter(([c]) => C[c]);
all.sort((a,b) => (b[1].t||0) - (a[1].t||0));
const shown = all.slice(0, 5);
// Richtige Antwort(en) immer zeigen — auch wenn das Heimatland kein Top-5-Produzent ist
correctKeys.forEach(ck => {
if (!shown.some(([c]) => c === ck)) {
const row = all.find(([c]) => c === ck);
if (row) shown.push(row);
}
});
const sig = _taskCache[ingKey] && _taskCache[ingKey].signature;
let html = '<div class="econ-list"><div class="rlabel">' +
(sig ? 'Die größten Produzenten zum Vergleich' : 'Weltweite Hauptproduzenten') + '</div>';
shown.forEach(([c, info]) => {
const isMatch = correctKeys.includes(c);
const cm = C[c];
const amount = info.t ? fmtTonnes(info.t) : (info.note || 'kulturell verbreitet');
const share = info.share ? ' · ' + info.share : '';
html += `<div class="prod-row${isMatch ? ' match' : ''}">
<span class="rf">${cm.f}</span>
<span class="prod-nm">${cm.n}${isMatch ? ' ✓' : ''}</span>
<span class="prod-val">${amount}${share}</span>
</div>`;
});
const rest = all.length - shown.length;
if (rest > 0) {
html += `<div class="prod-more">+ ${rest} weitere Länder</div>`;
}
if (sig) {
html += `<div class="prod-more" style="color:#4a7c4e;font-weight:600">Der größte Produzent ist nicht automatisch die Antwort hier zählt, woher die Zutat für dieses Gericht kommt.</div>`;
}
html += '</div>';
return html;
}
/* Tierische vs. pflanzliche Zutaten — Heuristik aus Key-Slug. Wird für
* generische "wächst nicht / wird nicht typisch geliefert"-Texte gebraucht. */
function isAnimalIng(key){
return /^(ei|eier|milch|butter|quark|topfen|kaese|mozzarella|joghurt|sahne|rahm|sauerrahm|parmesan|honig|huhn|rind|schwein|wurst|schinken|speck|lachs|thun|fisch|garnele|krabbe|hummer|muschel|wachtel|lamm|schaf|ente|gans|truthahn|fischsauce)/.test(key);
}
/* Erklärt für eine einzelne Choice (richtig / falsch / nicht angeklickt)
* EINEN didaktischen Satz — wird in der allChoicesHTML-Übersicht gebraucht. */
function explainChoice(ingKey, key, isCorrect){
const ing = ING[ingKey];
const info = ing && ing.producers && ing.producers[key];
const amount = info && info.t ? fmtTonnes(info.t) : '';
const animal = isAnimalIng(ingKey);
// Signatur-Zutat (Heimat-Regel): Antwort folgt dem Gericht
const sTask = _taskCache[ingKey];
if (sTask && sTask.signature) {
const heimatN = C[sTask.heimat] ? C[sTask.heimat].n : '';
const was = sTask.label || ('der/die/das ' + ing.nm);
if (isCorrect) {
return {cls:'ok', badge:'✓', text:`Dieses Gericht kommt aus <b>${heimatN}</b> ${was} ist hier heimisch und wird vor Ort erzeugt.`};
}
if (info) {
return {cls:'mid', badge:'⚠️', text:`${C[key].n} erzeugt zwar viel ${ing.nm}${amount?' ('+amount+')':''} aber in einem Gericht aus ${heimatN} stammt ${was} von dort.`};
}
// Nicht-Produzent-Fehlklick → normale „unmöglich"-Logik unten
}
if (isCorrect){
if (info && info.market === 'world-far'){
return {cls:'ok', badge:'✓', text:`<b>Welt-Hauptproduzent</b> (${amount}) — kommt aber selten in den AT-Handel.`};
}
if (info && info.market === 'off-season'){
return {cls:'ok', badge:'✓', text:`<b>Off-Season-Lieferant</b> für unseren Winter (${amount}).`};
}
if (info && info.market === 'eu'){
return {cls:'ok', badge:'✓', text:`<b>Hauptlieferant für uns</b>${amount?' (' + amount + ')':''} aus dem EU-Binnenmarkt: kurzer Weg, kein Zoll. Deshalb kommt die Ware meist von hier, auch wenn anderswo mehr wächst.`};
}
return {cls:'ok', badge:'✓', text:`<b>Hauptlieferant für uns</b>${amount?' (' + amount + ')':''}.`};
}
// Falsch: weitere Klassifizierung
if (info){
const m = info.market || 'global';
if (m === 'world-far'){
return {cls:'mid', badge:'🌐', text:`Weltweit großer Produzent (${amount}) im EU-Handel aber kaum vertreten, du bekommst die Ware fast nie von dort.`};
}
if (m === 'eu' || m === 'global'){
return {cls:'mid', badge:'⚠️', text:`Auch ein echter Lieferant (${amount}) aber nicht der Hauptweg zu unserem Markt.`};
}
if (m === 'local'){
return {cls:'mid', badge:'🏠', text:`Produziert ${amount}${animal?' — Tiere werden aber überwiegend für den lokalen Markt gehalten':' — fast alles bleibt im eigenen Land'}.`};
}
if (m === 'us-only'){
return {cls:'mid', badge:'🌐', text:`US-Export geht vor allem nach Asien (Japan, Südkorea) — kaum zu uns.`};
}
if (m === 'asia-only'){
return {cls:'mid', badge:'🌐', text:`Geht in regionale Märkte Asiens — landet praktisch nicht in unseren Supermärkten.`};
}
if (m === 'off-season'){
return {cls:'mid', badge:'🌍', text:`Süd-Hemisphäre — liefert nur im EU-Winter, sonst nicht der Hauptweg zu uns.`};
}
}
if (ing && ing.climate_possible && ing.climate_possible.includes(key)){
return {cls:'mid', badge:'🌱', text:animal
? `Wäre theoretisch möglich — kommt aber nicht typisch zu uns.`
: `Theoretisch möglich (Klimazone passt) — aber kein nennenswerter Hauptlieferant.`};
}
// impossible — Text pflanzen-/tier-spezifisch
if (animal){
return {cls:'no', badge:'✗', text:`Kein typischer Lieferant für uns — Tiere werden zwar auch dort gehalten, kommen aber praktisch nie als Produkt zu uns auf den Markt.`};
}
return {cls:'no', badge:'✗', text:`Klimatisch oder logistisch kein Lieferant für uns — wächst hier nicht im relevanten Umfang oder gelangt nicht in unsere Märkte.`};
}
/* Volltabelle: jede der 45 Karten-Optionen kriegt einen erklärenden Satz.
* Schüler:innen sehen so auch, was sie NICHT angeklickt haben (Lerngewinn:
* was wäre theoretisch möglich gewesen, was wäre falsch gewesen). */
function allChoicesHTML(ingKey, task, attempted){
if (!task || !task.choices || !task.choices.length) return '';
const rows = task.choices.map(k => {
const c = C[k]; if (!c) return '';
const isCorrect = task.correct.includes(k);
const x = explainChoice(ingKey, k, isCorrect);
const wasClicked = attempted && attempted.has && attempted.has(k);
const clickTag = wasClicked ? '<span class="clicked-tag">angeklickt</span>' : '';
return `<div class="all-choice ${x.cls}">
<span class="cf">${c.f}</span>
<span class="cnm">${c.n}</span>
<span class="cb">${x.badge}</span>
<div class="ctxt">${x.text}</div>
${clickTag}
</div>`;
}).filter(Boolean).join('');
return `<div class="all-choices"><div class="rlabel">Alle Länder dieser Runde</div>${rows}</div>`;
}
/* Bei Korrekt-Klick auf world-far oder off-season: ehrlicher Zusatzhinweis,
* dass das Land zwar weltweit Big Player ist, im AT-Handel aber selten landet. */
function marketCaveatFor(info) {
if (!info || !info.market) return '';
if (info.market === 'world-far') {
return `<div class="more-line" style="background:#fff7e0;border-left:3px solid #d2a24c">
⚠️ <b>Zwar weltweit wichtiger Produzent</b> — im österreichischen Handel kommt dieses Land aber <b>kaum vor</b>. Du würdest dieses Produkt fast nie von dort bekommen.
</div>`;
}
if (info.market === 'off-season') {
return `<div class="more-line" style="background:#fff7e0;border-left:3px solid #d2a24c">
🌍 <b>Süd-Hemisphäre — Off-Season-Lieferant.</b> Kommt nur im EU-Winter (DezemberApril) in unsere Märkte, wenn die europäische Saison vorbei ist.
</div>`;
}
return '';
}
function showInterim(ing,key,pts,remaining){
const c=C[key],card=document.getElementById('resultCard');
card.className="result-card mid show";
const ingKey = current.dish.ings[current.idx];
const info = (ing.producers && ing.producers[key]) ? ing.producers[key] : null;
const amount = info && info.t ? ` · ${fmtTonnes(info.t)}` : '';
card.innerHTML=`<div class="rc-head">✓ ${c.f} ${c.n} stimmt! (+${pts})${amount}</div>
<div class="note">${c.info}</div>
${marketCaveatFor(info)}
<div class="more-line">🔎 Noch <b>${remaining}</b> weiteres Herkunftsland gesucht — finde es für <b>Bonuspunkte</b>!</div>`;
}
function showSolved(ing,pts,bonus){
const card=document.getElementById('resultCard');
const ingKey = current.dish.ings[current.idx];
const task = _taskCache[ingKey];
const flags=task.correct.map(k=>C[k].f+' '+C[k].n).join(' & ');
card.className="result-card ok show";
const factLine = ing.fact ? `<div class="more-line"><b>Wusstest du?</b> ${esc(ing.fact)}</div>` : '';
// Heimat-Regel: erklären, warum die Region die Antwort ist (nicht der größte Produzent)
const sigLine = (task.signature && C[task.heimat])
? `<div class="more-line" style="background:#eef5ec;border-left:3px solid #4a7c4e">🍽️ Das ist ein Gericht aus <b>${C[task.heimat].n}</b>. ${task.label ? ('<b>'+task.label+'</b>') : ('Der/die/das '+esc(ing.nm))} kommt von hier nur Zutaten, die bei uns gar nicht wachsen, reisen von weit her.</div>`
: '';
// Caveats für world-far/off-season Korrekt-Picks (pro Land separat)
const caveats = task.correct.map(k => {
const info = ing.producers && ing.producers[k];
if (!info || !info.market || !['world-far','off-season'].includes(info.market)) return '';
const cc = C[k]; if (!cc) return '';
const label = `${cc.f} ${cc.n}`;
if (info.market === 'world-far') {
return `<div class="more-line" style="background:#fff7e0;border-left:3px solid #d2a24c">
⚠️ <b>${label}:</b> zwar weltweit wichtiger Produzent — aber im österreichischen Handel kommt dieses Land <b>kaum vor</b>. Du würdest dieses Produkt fast nie von dort bekommen.
</div>`;
}
return `<div class="more-line" style="background:#fff7e0;border-left:3px solid #d2a24c">
🌍 <b>${label}:</b> Süd-Hemisphäre — kommt nur im EU-Winter (DezemberApril) in unsere Märkte, wenn die europäische Saison vorbei ist.
</div>`;
}).filter(Boolean).join('');
card.innerHTML=`<div class="rc-head">✓ Geschafft! ${esc(ing.nm)} kommt aus: ${flags}</div>
${sigLine}
${allChoicesHTML(ingKey, task, current.attempted)}
${producerEconHTML(ingKey, task.correct)}
${caveats}
${regionsHTML(task.correct)}
${factLine}
<div class="next-row"><button class="btn" onclick="nextIngredient()">+${pts+bonus} Punkte · Weiter →</button></div>`;
// Karte schrumpft beim Rundenabschluss, damit die Ergebnis-Übersicht mehr Platz bekommt
const mapEl = document.getElementById('map');
if (mapEl && map) {
mapEl.classList.add('solved');
setTimeout(() => {
try { map.invalidateSize(); } catch(e) {}
// Wieder auf alle Choices der Runde rauszoomen — Schüler:innen sehen die ganze Auswahl
const pts = task.choices.map(k=>{const c=C[k];return c?[c.lat,c.lng]:null;}).filter(Boolean);
if (pts.length && map.fitBounds) {
try { map.fitBounds(pts, {paddingTopLeft:[40,40], paddingBottomRight:[40,40], maxZoom:3, animate:true}); } catch(e) {}
}
}, 580);
}
}
function showWrong(key, kind, ingKey){
const c=C[key],card=document.getElementById('resultCard');
const ing = ING[ingKey];
let head, note;
// Signatur-Zutat: „Warum nicht Deutschland?" sofort beantworten
const sTask = _taskCache[ingKey];
if (sTask && sTask.signature && ing.producers && ing.producers[key]) {
card.className="result-card mid show";
const info = ing.producers[key];
const amount = info && info.t ? fmtTonnes(info.t) : 'verbreitet';
const heimatN = C[sTask.heimat] ? C[sTask.heimat].n : '';
const was = sTask.label || ('der/die/das ' + ing.nm);
card.innerHTML = `<div class="rc-head">⚠️ ${c.f} ${c.n} erzeugt zwar viel ${ing.nm} (${amount}) aber dieses Gericht kommt aus ${heimatN}, und ${was} stammt von dort.</div>`+
`<div class="note">${c.info}<br><br>🔎 Tipp: Achte auf den Namen des Gerichts er verrät oft die Herkunft.</div>`;
return;
}
if (kind === 'producer-eu-elsewhere') {
card.className="result-card mid show";
const info = ing.producers[key];
const amount = info && info.t ? fmtTonnes(info.t) : 'verbreitet';
head = `⚠️ ${c.f} ${c.n} ist auch ein echter Lieferant (${amount}) — aber nicht der Hauptweg zu unserem Markt.`;
note = c.info + '<br><br>🔎 Such weiter — du hast eines der gesuchten Herkunftsländer noch nicht entdeckt.';
} else if (kind === 'producer-local') {
// Produziert, aber bleibt im Inland / regionaler Markt
card.className="result-card mid show";
const info = ing.producers[key];
const amount = info && info.t ? fmtTonnes(info.t) : 'verbreitet';
const marketHint = {
'local': 'fast alles wird im eigenen Land verzehrt',
'us-only': 'der US-Export geht vor allem nach Asien (Japan, Südkorea)',
'asia-only': 'geht in regionale Märkte, kaum nach Europa',
'off-season': 'liefert nur im EU-Winter (DezemberApril), wenn die europäische Saison vorbei ist'
}[info?.market] || 'nicht für den EU-Markt';
head = `🏠 ${c.f} ${c.n} produziert ${amount} — aber ${marketHint}.`;
note = c.info + '<br><br>💡 Die Frage ist: <i>wo kommen die Zutaten her, die wir bei uns kaufen?</i> Such weiter.';
} else if (kind === 'climate-possible') {
card.className="result-card mid show";
const animal = isAnimalIng(ingKey);
head = `🌱 ${c.f} ${c.n} — theoretisch möglich, aber kein Hauptmarkt`;
note = c.info + (animal
? '<br><br>💡 Wäre vom Klima her möglich, ist aber kein nennenswerter Lieferant für unseren Markt.'
: '<br><br>💡 Die Pflanze würde dort klimatisch wachsen können, aber es wird kaum für den Markt produziert.');
} else {
card.className="result-card no show";
const animal = isAnimalIng(ingKey);
if (animal) {
head = `✗ ${c.f} ${c.n} — kein typischer Lieferant`;
note = c.info + '<br><br>🤔 Tierische Produkte aus dieser Region kommen praktisch nicht in unsere Märkte. Versuch ein anderes Land.';
} else {
head = `✗ ${c.f} ${c.n} — hier wächst das nicht`;
note = c.info + '<br><br>🤔 Denk ans Klima: zu kalt? zu trocken? kein Meer? Versuch ein anderes Land.';
}
}
card.innerHTML=`<div class="rc-head">${head}</div><div class="note">${note}</div>`;
}
function nextIngredient(){
document.getElementById('resultCard').className="result-card";
const mapEl = document.getElementById('map');
if (mapEl) {
mapEl.classList.remove('solved'); // Karte zurück auf Vollhöhe für die nächste Zutat
}
loadIngredient();
// iPad-Fix: zur Karte hochscrollen, damit die nächste Aufgabe gleich sichtbar ist
// (sonst rutscht der untere Result-Bereich der vorherigen Zutat über die Karte).
if (mapEl) {
setTimeout(() => { try { map && map.invalidateSize(); } catch(e) {} }, 580);
const top = mapEl.getBoundingClientRect().top + window.scrollY - 70; // 70 = sticky Header
window.scrollTo({top: Math.max(0, top), behavior: 'smooth'});
}
}
/* ====================== BADGES ====================== */
function hasBadge(id){return state.badges.includes(id);}
function awardLive(ids){const got=[];ids.forEach(id=>{if(!hasBadge(id)){state.badges.push(id);got.push(BADGES.find(b=>b.id===id));}});if(got.length){updateHUD();toastBadges(got);setTimeout(()=>playSfx('badge-earned'), 300);}return got;}
function checkLiveBadges(ing,key){
const ids=[];
if(state.totalCorrect>=1) ids.push('first');
if(state.totalCorrect>=8) ids.push('spice');
if(state.cleanIngredients>=3) ids.push('climate');
if(state.score>=3000) ids.push('trade');
if(ing.nm.indexOf('Nori')===0) ids.push('landlk');
awardLive(ids);
}
function toastBadges(list){
list.forEach((b,i)=>setTimeout(()=>{
const t=document.createElement('div');
t.style.cssText="position:fixed;left:50%;top:74px;transform:translateX(-50%);background:var(--card);border:1px solid var(--gold);border-radius:14px;padding:10px 16px;box-shadow:var(--shadow);z-index:950;font-weight:700;font-size:.9rem;display:flex;gap:9px;align-items:center";
t.innerHTML=`<span style="font-size:1.3rem">${b.icon}</span> Neues Abzeichen: ${b.name}!`;
document.body.appendChild(t);
setTimeout(()=>{t.style.opacity="0";t.style.transition="opacity .4s";},2200);
setTimeout(()=>t.remove(),2700);
},i*350));
}
/* ====================== GERICHT FERTIG ====================== */
function finishDish(){
const d=current.dish;
if(!state.completed.includes(d.id)) state.completed.push(d.id);
playSfx('dish-complete');
const perfect=current.mistakesInDish===0;
const newly=[];
if(perfect) newly.push(...awardLive(['perfect']));
newly.push(...awardLive(['dish']));
if(state.completed.length>=5) newly.push(...awardLive(['explorer']));
if(state.completed.length>=DISHES.length) newly.push(...awardLive(['star']));
if(state.score>=3000) newly.push(...awardLive(['trade']));
updateHUD(); renderDishes();
showRecipeFullScreen(d, perfect, newly);
}
/* Volle Infoseite NACH Abschluss: Erfolgs-Header + Bild + Rezept + Lehrplan-Bezug + Trivia + Klimabilanz */
function showRecipeFullScreen(d, perfect, newly) {
const host = document.getElementById('recipe-full-body');
const imgPath = d.imagePath ? (_basePath + d.imagePath) : null;
const n = d.nutrition || {};
const r = d.recipe || {};
const remaining = DISHES.length - state.completed.length;
let html = `
<div style="background:var(--green-soft);border:1px solid var(--green);border-radius:18px;padding:18px 20px;margin-bottom:18px;text-align:center">
<div style="font-size:2.6rem;line-height:1">${perfect?'🎉':'✨'}</div>
<h2 style="font-size:1.4rem;margin:6px 0;color:var(--green-d)">${perfect?'Perfekt geforscht!':'Gericht erforscht!'}</h2>
<div style="font-weight:700;color:var(--green-d)">+${current.dishScore} Punkte · Gesamt: ${state.score} ⭐</div>
${newly.length?`<div class="earned-badges" style="margin-top:12px">${newly.map(b=>`<div class="bdg"><div class="ring">${b.icon}</div><div class="lbl">${b.name}</div></div>`).join('')}</div>`:''}
</div>`;
if (imgPath) {
html += `<img src="${imgPath}" alt="${d.name}" style="width:100%;max-height:280px;object-fit:cover;border-radius:18px;margin-bottom:18px;background:var(--bg2)" onerror="this.style.display='none'">`;
}
html += `<div style="display:flex;align-items:center;gap:12px;margin-bottom:12px">
<span style="font-size:2.4rem">${d.emoji}</span>
<div><h2 style="font-size:1.6rem;margin:0">${d.name}</h2>
<div style="color:var(--muted);font-size:.92rem">📍 ${d.origin} · <b>${d.diff}</b>${d.diff_note?' · <em>'+d.diff_note+'</em>':''}</div>
</div>
</div>`;
// Nährwerte
if (Object.keys(n).length) {
html += `<div style="background:var(--bg2);border-radius:14px;padding:14px;margin:14px 0">
<h3 style="font-size:.92rem;margin-bottom:10px">📊 Nährwerte pro Portion (${n.portion_g||'?'} g)</h3>
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:10px;font-size:.86rem">
<span>kcal: <b>${n.kcal||''}</b></span>
<span>Eiweiß: <b>${n.protein_g||''} g</b></span>
<span>Kohlenhydrate: <b>${n.carbs_g||''} g</b></span>
<span>Fett: <b>${n.fat_g||''} g</b></span>
<span>Ballaststoffe: <b>${n.fiber_g||''} g</b></span>
<span>Salz: <b>${n.salt_g||''} g</b></span>
</div>
</div>`;
}
// Rezept
if (r.steps && r.steps.length) {
html += `<div style="margin:14px 0">
<h3 style="font-size:1rem;margin-bottom:8px">👩‍🍳 So machst du es (${r.portions||'?'} Portionen, ${r.time_min||'?'} min)</h3>
<ol style="padding-left:1.4rem;line-height:1.55;font-size:.92rem">${r.steps.map(s=>'<li style="margin-bottom:6px">'+esc(s)+'</li>').join('')}</ol>
${r.tipp?'<div style="background:var(--gold-soft);border-radius:10px;padding:10px 12px;font-size:.86rem;margin-top:10px"><b style="color:#8a6516">💡 Tipp:</b> '+esc(r.tipp)+'</div>':''}
</div>`;
}
// Lehrplan
if (d.lehrplan && Object.keys(d.lehrplan).length) {
html += '<div style="margin:14px 0"><h3 style="font-size:1rem;margin-bottom:8px">📚 Wenn dich das interessiert</h3>';
const labels = {eh:'Ernährung & Haushalt', gw:'Geografie und wirtschaftliche Bildung', bu:'Biologie & Umwelt', de:'Deutsch'};
Object.entries(d.lehrplan).forEach(([k, v]) => {
html += '<div style="background:var(--green-soft);border-radius:10px;padding:10px 12px;font-size:.86rem;margin-bottom:6px"><b style="color:var(--green-d)">'+(labels[k]||k.toUpperCase())+':</b> '+esc(v)+'</div>';
});
html += '</div>';
}
// Trivia
if (d.fact) {
html += '<div style="background:#fff;border:1px solid var(--line);border-radius:10px;padding:10px 12px;font-size:.86rem;font-style:italic;margin:14px 0;color:var(--ink)">💡 '+esc(d.fact)+'</div>';
}
// Klimabilanz
html += climateImpactBlock(d);
// Nav — nur ein Button. Abzeichen sind über die Top-Bar erreichbar.
html += `<div style="text-align:center;margin:18px 0 40px">
<button class="btn" style="max-width:360px;margin:0 auto" onclick="show('dishes')">${remaining>0?'Nächstes Gericht ('+remaining+' offen) →':'Alle Gerichte geschafft! 🏆'}</button>
</div>`;
host.innerHTML = html;
show('recipe-full');
}
/* Rangliste komplett entfernt (Plattform-Konvention: keine Sim hat eine eigene Rangliste). */
function renderBadges(){
document.getElementById('badgeGrid').innerHTML=BADGES.map(b=>{
const got=hasBadge(b.id);
return `<div class="badge-card${got?'':' locked'}"><div class="ring">${got?b.icon:'🔒'}</div><h4>${b.name}</h4><p>${b.desc}</p></div>`;
}).join('');
}
updateHUD();
</script>
</body>
</html>