Files
geograsim/App/sims/eu-werkstatt/game.html
T
Adminator 8ec005f889 EU-Werkstatt: Probierzuege (Karten rausziehen) erfassen + Sterne ehrlicher
Bisher konnte man Karten in Slots einsetzen und wieder herausziehen, bis
alle voll waren — erst dann wurde ausgewertet. Dieses Herumprobieren war
unsichtbar und senkte die Sterne nicht (5 Sterne trotz Trial-and-Error).

- game.html: reshufflesByStep/totalReshuffles; manuelles Herausziehen einer
  platzierten Karte zaehlt als Probierzug. Ein Schritt gilt nur ohne
  Probierzuege als 'sauber auf Anhieb' -> Sterne (firstTryHits/steps) und
  Treffsicher-Achievement spiegeln Trial-and-Error jetzt. Endbild zeigt
  '🔄 N Probierzuege'; Live-State + Submit tragen die Zahlen.
- teacher.html: needsHelp eu-werkstatt auch bei vielen Probierzuegen;
  '🔄 Probier'-Spalte in Aktuell; Run-Karte: Gesamt-Probierzuege +
  pro Schritt ('🔄 nach Probieren' statt 'auf Anhieb').

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-24 13:33:44 +02:00

5385 lines
255 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>EU-Werkstatt — Wie ein Gesetz entsteht · GeoGraSim</title>
<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=Fraunces:opsz,wght@9..144,400;9..144,600;9..144,700;9..144,900&family=Manrope:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
/* ==========================================================
FARBEN & GRUNDLAGEN Scandinavian Flat
========================================================== */
:root {
--forest: #1f4b37;
--sage: #4a7c4e;
--mustard: #e8c547;
--beige: #e8e4d8;
--cream: #f4f1e8;
--white: #ffffff;
--ink: #14201a;
--rust: #b04a3e;
--shadow: rgba(20, 32, 26, 0.12);
--display: 'Fraunces', Georgia, serif;
--body: 'Manrope', system-ui, sans-serif;
--radius: 18px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
font-family: var(--body);
color: var(--ink);
background: var(--cream);
min-height: 100vh;
overflow-x: hidden;
}
body {
background:
radial-gradient(ellipse at 20% 10%, rgba(74,124,78,0.08), transparent 50%),
radial-gradient(ellipse at 80% 90%, rgba(232,197,71,0.08), transparent 50%),
var(--cream);
display: flex;
flex-direction: column;
}
/* ==========================================================
KOPFBEREICH
========================================================== */
header.app-header {
padding: 16px 28px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid rgba(31,75,55,0.12);
background: var(--cream);
position: sticky;
top: 0;
z-index: 50;
}
.logo {
font-family: var(--display);
font-weight: 900;
font-size: 1.2rem;
color: var(--forest);
display: flex;
align-items: center;
gap: 12px;
}
.logo-mark {
width: 32px; height: 32px;
background: var(--forest);
border-radius: 50%;
display: flex; align-items: center; justify-content: center;
}
.logo-mark::before {
content: '';
width: 12px; height: 12px;
background: var(--mustard);
border-radius: 50%;
}
.header-left {
display: flex; gap: 8px; align-items: center;
}
.header-actions {
display: flex; gap: 12px; align-items: center;
}
/* Innerhalb-Sim-Button "Szenarien" am Startscreen verstecken — man ist schon dort */
body[data-screen="screen-start"] .header-btn-scenarios { display: none; }
@media (max-width: 640px) {
/* Schmälere Buttons auf Mobile */
.header-left .header-btn { padding: 6px 10px; font-size: 0.74rem; }
.header-left { gap: 4px; }
}
.header-btn {
font-family: var(--body);
font-weight: 600;
font-size: 0.88rem;
background: transparent;
border: 2px solid rgba(31,75,55,0.2);
color: var(--forest);
padding: 8px 16px;
border-radius: 999px;
cursor: pointer;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 6px;
}
.header-btn:hover { border-color: var(--forest); background: var(--white); }
.progress-bar {
display: flex; gap: 10px; align-items: center;
}
.progress-bar .progress-label {
font-family: var(--body);
font-weight: 700;
font-size: 0.78rem;
letter-spacing: 0.04em;
color: var(--forest);
white-space: nowrap;
padding: 4px 10px;
background: rgba(74,124,78,0.10);
border-radius: 999px;
}
.progress-bar .progress-dots {
display: flex; gap: 6px; align-items: center;
}
.progress-dot {
width: 9px; height: 9px;
border-radius: 50%;
background: rgba(31,75,55,0.18);
transition: all 0.4s ease;
}
.progress-dot.done { background: var(--sage); }
.progress-dot.active { background: var(--mustard); transform: scale(1.4); }
@media (max-width: 540px) {
.progress-bar .progress-dots { display: none; }
}
/* ==========================================================
LAYOUT
========================================================== */
main {
flex: 1;
display: flex;
align-items: flex-start;
justify-content: center;
padding: 32px 20px;
}
.screen {
width: 100%;
max-width: 1040px;
display: none;
animation: fadeIn 0.4s ease;
}
.screen.active { display: block; }
@keyframes fadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
/* ==========================================================
STARTSCREEN
========================================================== */
.start-hero { text-align: center; padding: 16px 16px 32px; }
.start-illustration { width: 100%; max-width: 720px; margin: 0 auto 28px; display: block; }
.eyebrow {
font-weight: 600;
letter-spacing: 0.18em;
text-transform: uppercase;
font-size: 0.74rem;
color: var(--sage);
margin-bottom: 12px;
}
.start-title {
font-family: var(--display);
font-weight: 700;
font-size: clamp(2.2rem, 6vw, 3.8rem);
color: var(--forest);
line-height: 1.05;
letter-spacing: -0.02em;
margin-bottom: 16px;
}
.start-title em { font-style: italic; color: var(--sage); }
.start-subtitle {
font-size: 1.1rem;
color: var(--ink);
opacity: 0.78;
max-width: 560px;
margin: 0 auto 32px;
line-height: 1.55;
}
.start-toolbar {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 24px;
margin: 18px 4px 14px;
flex-wrap: wrap;
}
.start-toolbar-block { flex: 1 1 280px; min-width: 0; }
.start-block-heading {
font-family: var(--display);
color: var(--forest);
margin: 0 0 4px;
font-size: 1.25rem;
font-weight: 700;
}
.start-block-sub {
color: var(--ink);
opacity: 0.7;
margin: 0;
font-size: 0.92rem;
line-height: 1.45;
}
.diff-toggle {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 6px;
flex-shrink: 0;
}
.diff-toggle-label {
font-size: 0.7rem;
letter-spacing: 0.18em;
text-transform: uppercase;
font-weight: 700;
color: var(--sage);
}
.diff-toggle-buttons {
display: inline-flex;
background: var(--white);
border-radius: 999px;
padding: 4px;
box-shadow: 0 2px 10px var(--shadow);
border: 1px solid rgba(31,75,55,0.10);
gap: 2px;
}
.diff-btn {
display: inline-flex;
align-items: center;
gap: 8px;
background: transparent;
border: none;
padding: 8px 14px;
border-radius: 999px;
cursor: pointer;
font-family: var(--body);
color: var(--ink);
opacity: 0.65;
transition: all 0.2s ease;
text-align: left;
}
.diff-btn-emoji { font-size: 1.1rem; line-height: 1; }
.diff-btn-text {
display: flex; flex-direction: column;
font-weight: 700; font-size: 0.86rem; line-height: 1.1;
}
.diff-btn-text small {
font-size: 0.66rem; font-weight: 500; opacity: 0.7;
letter-spacing: 0.02em;
}
.diff-btn:hover { opacity: 0.95; background: rgba(31,75,55,0.05); }
.diff-btn.active {
background: var(--forest);
color: var(--mustard);
opacity: 1;
box-shadow: 0 2px 6px rgba(20,32,26,0.20);
}
.diff-btn.active .diff-btn-text small { opacity: 0.7; color: var(--cream); }
@media (max-width: 640px) {
.start-toolbar { flex-direction: column; align-items: stretch; }
.diff-toggle { align-items: flex-start; }
}
.achievements-strip {
margin: 36px 4px 16px;
padding: 20px 22px;
background: var(--white);
border-radius: var(--radius);
box-shadow: 0 2px 12px var(--shadow);
display: none;
}
.achievements-strip.has-any { display: block; }
.achievements-strip-head {
display: flex; align-items: center; justify-content: space-between;
gap: 12px; margin-bottom: 14px;
}
.achievements-strip-title {
font-family: var(--display);
font-size: 1.05rem;
font-weight: 700;
color: var(--forest);
}
.achievements-strip-count {
font-size: 0.78rem;
font-weight: 700;
color: var(--sage);
background: rgba(74,124,78,0.12);
padding: 4px 10px;
border-radius: 999px;
}
.achievements-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 10px;
}
.ach-badge {
background: var(--beige);
border-radius: 12px;
padding: 12px 12px;
text-align: center;
font-size: 0.78rem;
line-height: 1.3;
color: var(--ink);
opacity: 0.4;
transition: all 0.2s ease;
cursor: default;
border: 1px solid transparent;
}
.ach-badge.unlocked {
opacity: 1;
background: linear-gradient(135deg, rgba(232,197,71,0.18), rgba(74,124,78,0.10));
border-color: rgba(232,197,71,0.5);
}
.ach-badge-emoji {
font-size: 1.6rem;
display: block;
margin-bottom: 6px;
filter: grayscale(1);
}
.ach-badge.unlocked .ach-badge-emoji { filter: none; }
.ach-badge-name {
font-weight: 700;
color: var(--forest);
margin-bottom: 2px;
font-size: 0.78rem;
}
.ach-badge-desc {
font-size: 0.7rem;
color: var(--ink);
opacity: 0.7;
}
.scenario-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 18px;
margin-top: 14px;
}
.scenario-card {
background: var(--white);
border-radius: var(--radius);
padding: 22px;
cursor: pointer;
border: 2px solid transparent;
transition: all 0.3s ease;
text-align: left;
position: relative;
box-shadow: 0 2px 12px var(--shadow);
}
.scenario-card:hover:not(.locked) {
transform: translateY(-4px);
border-color: var(--mustard);
box-shadow: 0 12px 32px var(--shadow);
}
.scenario-card.locked { opacity: 0.5; cursor: not-allowed; }
.scenario-card .icon {
width: 52px; height: 52px;
border-radius: 14px;
background: var(--beige);
display: flex; align-items: center; justify-content: center;
font-size: 1.7rem;
margin-bottom: 14px;
}
.scenario-card h3 {
font-family: var(--display);
font-size: 1.35rem;
color: var(--forest);
margin-bottom: 8px;
}
.scenario-card p {
color: var(--ink);
opacity: 0.7;
font-size: 0.93rem;
line-height: 1.5;
}
.scenario-card .badge {
position: absolute;
top: 14px; right: 14px;
font-size: 0.66rem;
letter-spacing: 0.08em;
text-transform: uppercase;
font-weight: 700;
padding: 4px 10px;
border-radius: 999px;
background: var(--sage);
color: var(--white);
}
.scenario-card.failed .badge { background: var(--rust); }
.scenario-card.locked .badge { background: rgba(20,32,26,0.3); }
.scenario-card-stars {
display: flex;
align-items: center;
gap: 8px;
margin-top: 14px;
padding-top: 12px;
border-top: 1px solid rgba(31,75,55,0.08);
font-size: 0.78rem;
color: var(--ink);
opacity: 0.78;
}
.scenario-card-stars .stars-glyph {
color: var(--mustard);
letter-spacing: 0.06em;
font-size: 0.95rem;
line-height: 1;
}
.scenario-card-stars .stars-glyph .star-empty { opacity: 0.25; }
.scenario-card-played-marker {
margin-left: auto;
font-size: 0.72rem;
color: var(--sage);
font-weight: 700;
letter-spacing: 0.04em;
}
.scenario-card-stars.no-plays {
color: var(--ink);
opacity: 0.45;
font-style: italic;
}
/* ==========================================================
SPIELSCREEN allgemein
========================================================== */
.scene-stage {
background: var(--white);
border-radius: var(--radius);
box-shadow: 0 4px 24px var(--shadow);
overflow: hidden;
margin-bottom: 24px;
}
.scene-illustration {
width: 100%; height: 180px;
background: var(--beige);
display: block;
}
.scene-body { padding: 26px 32px; }
.scene-actor {
display: flex; align-items: center; gap: 12px;
margin-bottom: 14px;
}
.scene-actor-avatar {
width: 44px; height: 44px;
border-radius: 50%;
background: var(--mustard);
flex-shrink: 0;
}
.scene-actor-name {
font-family: var(--display);
font-size: 0.98rem;
font-weight: 600;
color: var(--forest);
}
.scene-actor-role {
font-size: 0.8rem;
color: var(--ink);
opacity: 0.6;
}
.scene-narration {
font-size: 1.05rem;
line-height: 1.6;
color: var(--ink);
}
.scene-question {
font-family: var(--display);
font-size: 1.5rem;
font-weight: 600;
color: var(--forest);
line-height: 1.3;
margin-top: 20px;
letter-spacing: -0.01em;
}
/* ==========================================================
SATZBAU-MECHANIK
========================================================== */
.sentence-template {
background: var(--white);
border-radius: var(--radius);
padding: 28px 32px;
margin-bottom: 22px;
box-shadow: 0 2px 12px var(--shadow);
font-size: 1.2rem;
line-height: 1.9;
color: var(--ink);
font-family: var(--display);
}
.slot {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 140px;
height: 52px;
padding: 0 14px;
margin: 4px 4px;
border-radius: 12px;
background: var(--beige);
border: 2px dashed rgba(31,75,55,0.3);
vertical-align: middle;
cursor: pointer;
transition: all 0.2s ease;
font-family: var(--body);
font-size: 0.95rem;
font-weight: 600;
color: var(--ink);
position: relative;
}
.slot:hover { border-color: var(--forest); }
.slot.highlight {
background: #fef6dc;
border: 2px solid var(--mustard);
border-style: solid;
animation: pulseGlow 1.4s ease-in-out infinite;
}
@keyframes pulseGlow {
0%, 100% { box-shadow: 0 0 0 0 rgba(232,197,71,0.6); }
50% { box-shadow: 0 0 0 8px rgba(232,197,71,0); }
}
.slot.filled {
background: var(--forest);
border: 2px solid var(--forest);
color: var(--mustard);
}
.slot.filled-correct {
background: var(--sage);
border: 2px solid var(--sage);
color: var(--white);
animation: snapIn 0.3s ease;
}
.slot.filled-wrong {
background: var(--rust);
border: 2px solid var(--rust);
color: var(--white);
animation: shake 0.4s ease;
}
@keyframes snapIn {
0% { transform: scale(0.85); }
60% { transform: scale(1.06); }
100% { transform: scale(1); }
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
20%, 60% { transform: translateX(-6px); }
40%, 80% { transform: translateX(6px); }
}
.slot .slot-card-icon {
margin-right: 8px;
font-size: 0.9rem;
}
/* ==========================================================
KARTEN-POOL
========================================================== */
.card-pool {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 14px;
margin-bottom: 22px;
}
.concept-card {
background: var(--white);
border-radius: 18px;
border: 2px solid rgba(31,75,55,0.12);
cursor: pointer;
transition: all 0.25s ease;
position: relative;
box-shadow: 0 2px 10px var(--shadow);
user-select: none;
display: flex;
flex-direction: column;
overflow: hidden;
}
.concept-card:hover {
border-color: var(--mustard);
transform: translateY(-3px);
box-shadow: 0 10px 26px var(--shadow);
}
.concept-card.selected {
border-color: var(--mustard);
background: #fffdf3;
box-shadow: 0 0 0 3px rgba(232,197,71,0.45), 0 8px 22px var(--shadow);
transform: translateY(-3px);
}
.concept-card.placed {
opacity: 0.35;
pointer-events: none;
}
.concept-card.locked {
opacity: 0.4;
pointer-events: none;
}
/* Visualisierungs-Bereich der Karte (oben, dunkel) */
.concept-card-viz {
width: 100%;
height: 130px;
background: var(--forest);
position: relative;
overflow: hidden;
}
.concept-card-viz svg {
width: 100%;
height: 100%;
display: block;
}
/* Info-Button oben rechts in der Visualisierung */
.concept-card-info-btn {
position: absolute;
top: 8px; right: 8px;
width: 28px; height: 28px;
border-radius: 50%;
background: rgba(244,241,232,0.92);
color: var(--forest);
border: none;
cursor: pointer;
font-weight: 700;
font-family: var(--display);
font-size: 0.9rem;
display: flex;
align-items: center;
justify-content: center;
line-height: 1;
padding: 0;
z-index: 5;
transition: all 0.2s ease;
box-shadow: 0 2px 6px rgba(20,32,26,0.25);
}
.concept-card-info-btn:hover {
background: var(--mustard);
transform: scale(1.1);
}
/* Textbereich der Karte (unten, hell) */
.concept-card-body {
padding: 14px 16px 16px;
flex: 1;
display: flex;
flex-direction: column;
}
.concept-card-label {
font-family: var(--display);
font-weight: 700;
font-size: 1.08rem;
color: var(--forest);
margin-bottom: 10px;
line-height: 1.18;
letter-spacing: -0.01em;
}
.concept-card-tags {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: auto;
}
.concept-card-tag {
background: var(--beige);
color: var(--forest);
font-size: 0.74rem;
font-weight: 600;
padding: 4px 10px;
border-radius: 999px;
white-space: nowrap;
line-height: 1.3;
}
.concept-card.selected .concept-card-tag {
background: var(--mustard);
}
/* ==========================================================
POOL-GRUPPIERUNG (für Satzbau mit Kategorien)
========================================================== */
.pool-group {
margin-bottom: 18px;
}
.pool-group-label {
font-size: 0.74rem;
letter-spacing: 0.16em;
text-transform: uppercase;
font-weight: 700;
color: var(--sage);
margin-bottom: 8px;
padding-left: 4px;
}
/* ==========================================================
FEEDBACK-LEISTE
========================================================== */
.feedback-bar {
margin: 22px 0 0;
padding: 18px 22px;
border-radius: 14px;
background: var(--beige);
font-size: 0.96rem;
line-height: 1.55;
display: none;
}
.feedback-bar.active { display: block; animation: fadeIn 0.3s ease; }
.feedback-bar.success { background: #eaf3ea; border-left: 4px solid var(--sage); }
.feedback-bar.partial { background: #fef6dc; border-left: 4px solid var(--mustard); }
.feedback-bar strong { color: var(--forest); }
/* ==========================================================
FUSSZEILE / BUTTONS
========================================================== */
.actions-row {
margin-top: 22px;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 12px;
}
.btn {
font-family: var(--body);
font-weight: 600;
font-size: 0.96rem;
padding: 12px 22px;
border: none;
border-radius: 999px;
cursor: pointer;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn-primary { background: var(--forest); color: var(--cream); }
.btn-primary:hover { background: var(--sage); transform: translateY(-2px); }
.btn-secondary {
background: transparent;
color: var(--forest);
border: 2px solid rgba(31,75,55,0.2);
}
.btn-secondary:hover { border-color: var(--forest); }
.btn:disabled { opacity: 0.4; cursor: not-allowed; transform: none; }
/* Inline-Weiter-Button neben dem richtigen Ergebnis — kein Scroll nötig */
.inline-next-btn {
margin-left: 12px !important;
margin-top: 8px;
vertical-align: middle;
animation: inlineNextPop 0.4s cubic-bezier(.18,1.4,.6,1);
position: relative; z-index: 2;
}
@keyframes inlineNextPop {
0% { transform: scale(0.6) translateX(-8px); opacity: 0; }
60% { transform: scale(1.08); opacity: 1; }
100% { transform: scale(1) translateX(0); opacity: 1; }
}
/* Multichoice-Inline-Button: kompakter, weil im Grid */
.mc-btn + .inline-next-btn {
display: block;
margin: 8px 0 0 0 !important;
width: 100%;
}
/* ==========================================================
OVERLAYS Mülleimer, Detail-Ansicht, Glossar
========================================================== */
.overlay {
position: fixed; inset: 0;
background: rgba(20,32,26,0.55);
display: none;
align-items: center;
justify-content: center;
z-index: 200;
padding: 20px;
}
.overlay.active {
display: flex;
animation: fadeIn 0.25s ease;
}
.overlay-card {
background: var(--cream);
border-radius: 24px;
padding: 32px;
max-width: 540px;
width: 100%;
text-align: center;
animation: popIn 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
max-height: 90vh;
overflow-y: auto;
}
@keyframes popIn {
from { transform: scale(0.85); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
.overlay-illustration { margin: 0 auto 18px; display: block; }
.overlay-title {
font-family: var(--display);
font-size: 1.7rem;
font-weight: 700;
color: var(--forest);
margin-bottom: 10px;
}
.overlay-text {
font-size: 1rem;
line-height: 1.55;
color: var(--ink);
opacity: 0.85;
margin-bottom: 24px;
}
.btn-row {
display: flex; gap: 12px; justify-content: center; flex-wrap: wrap;
}
/* ==========================================================
KONZEPT-DETAIL-MODAL
========================================================== */
.concept-detail {
background: var(--white);
border-radius: 24px;
padding: 0;
max-width: 580px;
width: 100%;
text-align: left;
animation: popIn 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
max-height: 90vh;
overflow-y: auto;
}
.concept-detail-visual {
width: 100%;
height: 220px;
background: var(--forest);
display: flex;
align-items: center;
justify-content: center;
border-top-left-radius: 24px;
border-top-right-radius: 24px;
position: relative;
overflow: hidden;
cursor: zoom-in;
transition: height 0.3s ease;
}
.concept-detail-visual svg {
width: 100%; height: 100%; max-width: 580px;
}
.concept-detail-zoom-hint {
position: absolute;
bottom: 8px; right: 12px;
background: rgba(244,241,232,0.9);
color: var(--forest);
font-size: 0.7rem;
font-weight: 700;
letter-spacing: 0.04em;
padding: 4px 9px;
border-radius: 999px;
pointer-events: none;
opacity: 0.85;
}
/* Tablet/Desktop: größere Detail-Ansicht */
@media (min-width: 768px) {
.concept-detail { max-width: 720px; }
.concept-detail-visual { height: 320px; }
}
/* Zoom-Modus: Vollbild-Visual */
.concept-detail-visual.zoomed {
position: fixed;
inset: 0;
height: 100vh;
width: 100vw;
z-index: 400;
border-radius: 0;
cursor: zoom-out;
background: var(--ink);
}
.concept-detail-visual.zoomed svg {
max-width: min(95vw, 1200px);
max-height: 95vh;
height: auto;
width: auto;
}
.concept-detail-visual.zoomed .concept-detail-zoom-hint {
background: rgba(255,255,255,0.92);
font-size: 0.82rem;
padding: 6px 14px;
}
.concept-detail-body { padding: 24px 28px 28px; }
.concept-detail-label {
font-size: 0.74rem;
letter-spacing: 0.16em;
text-transform: uppercase;
font-weight: 700;
color: var(--sage);
margin-bottom: 6px;
}
.concept-detail-title {
font-family: var(--display);
font-size: 1.7rem;
font-weight: 700;
color: var(--forest);
margin-bottom: 12px;
line-height: 1.15;
}
.concept-detail-summary {
font-size: 1rem;
line-height: 1.55;
color: var(--ink);
opacity: 0.85;
margin-bottom: 18px;
}
.facts-list {
list-style: none;
padding: 0; margin: 0 0 18px;
}
.facts-list li {
padding: 10px 0 10px 30px;
position: relative;
font-size: 0.95rem;
line-height: 1.5;
border-top: 1px solid rgba(31,75,55,0.1);
}
.facts-list li:first-child { border-top: none; }
.facts-list li::before {
content: '';
position: absolute;
left: 4px; top: 16px;
width: 8px; height: 8px;
background: var(--mustard);
border-radius: 50%;
}
/* Long-form body content within the concept detail */
.concept-detail-body-text {
margin: 6px 0 22px;
font-size: 0.96rem;
line-height: 1.62;
color: var(--ink);
}
.concept-detail-body-text h4 {
font-family: var(--display);
font-weight: 700;
font-size: 1.05rem;
color: var(--forest);
margin: 18px 0 6px;
letter-spacing: -0.01em;
}
.concept-detail-body-text h4:first-child { margin-top: 4px; }
.concept-detail-body-text p {
margin: 0 0 10px;
}
.concept-detail-body-text strong { color: var(--forest); font-weight: 700; }
/* Section heading inside detail */
.concept-detail-section-heading {
font-family: var(--display);
font-weight: 700;
font-size: 0.78rem;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--sage);
margin: 22px 0 10px;
}
/* Case-specific context box */
.case-context-box {
background: var(--beige);
border-radius: 14px;
padding: 18px 20px 14px;
margin: 22px 0 4px;
border-left: 4px solid var(--mustard);
}
.case-context-box .label {
font-family: var(--body);
font-size: 0.72rem;
letter-spacing: 0.14em;
text-transform: uppercase;
font-weight: 700;
color: var(--sage);
margin-bottom: 10px;
}
.case-context-box h4 {
font-family: var(--display);
font-weight: 700;
font-size: 1.05rem;
color: var(--forest);
margin: 6px 0 6px;
}
.case-context-box h4:first-of-type { margin-top: 0; }
.case-context-box p {
font-size: 0.94rem;
line-height: 1.55;
margin: 0 0 8px;
}
.case-context-box strong { color: var(--forest); font-weight: 700; }
.case-context-box.empty { display: none; }
.concept-detail-close {
width: 36px; height: 36px;
position: absolute;
top: 12px; right: 12px;
border-radius: 50%;
background: rgba(244,241,232,0.9);
border: none;
cursor: pointer;
font-size: 1.4rem;
color: var(--forest);
display: flex;
align-items: center;
justify-content: center;
z-index: 5;
}
/* ==========================================================
GLOSSAR-MODAL
========================================================== */
.glossary-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 12px;
margin-top: 16px;
max-height: 65vh;
overflow-y: auto;
padding: 4px;
}
/* ==========================================================
KARTEN-VISUALS einheitliche Größe, Animationen
========================================================== */
.viz-figure { animation: fadeFig 0.6s ease forwards; opacity: 0; }
@keyframes fadeFig {
to { opacity: 1; }
}
.viz-doc { transform-origin: center; }
.concept-card:hover .viz-doc { animation: docFly 1.4s ease-in-out infinite; }
.concept-detail-visual .viz-doc { animation: docFly 2.2s ease-in-out infinite; }
@keyframes docFly {
0%, 100% { transform: translate(0,0) rotate(0deg); }
50% { transform: translate(8px,-4px) rotate(4deg); }
}
.viz-counter { animation: countTick 0.8s ease; }
@keyframes countTick {
0% { transform: scale(0.8); opacity: 0.3; }
100% { transform: scale(1); opacity: 1; }
}
.viz-pulse { animation: vizPulse 1.8s ease-in-out infinite; }
@keyframes vizPulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.08); }
}
.viz-spin { transform-origin: center; animation: vizSpin 12s linear infinite; }
@keyframes vizSpin {
to { transform: rotate(360deg); }
}
.viz-bounce { animation: vizBounce 1.6s ease-in-out infinite; }
@keyframes vizBounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-4px); }
}
/* ==========================================================
ENDSCREEN
========================================================== */
.end-card {
background: var(--white);
border-radius: var(--radius);
padding: 44px 36px;
text-align: center;
box-shadow: 0 4px 24px var(--shadow);
position: relative;
}
.end-illustration { margin: 0 auto 22px; display: block; }
/* === Akten-Stempel-Animation am Endscreen === */
.end-stamp {
position: absolute;
top: 28px; right: 32px;
font-family: var(--display);
font-weight: 900;
font-size: 1.1rem;
letter-spacing: 0.18em;
text-transform: uppercase;
padding: 10px 16px;
border: 4px solid currentColor;
border-radius: 6px;
transform: rotate(-12deg) scale(0);
opacity: 0;
pointer-events: none;
animation: stampIn 0.65s cubic-bezier(.18,1.4,.6,1) 0.45s forwards;
text-shadow: 1px 1px 0 rgba(255,255,255,0.4);
}
.end-stamp.stamp-success { color: var(--forest); }
.end-stamp.stamp-mixed { color: var(--mustard); }
.end-stamp.stamp-rejected { color: var(--rust); }
@keyframes stampIn {
0% { transform: rotate(-30deg) scale(2.5); opacity: 0; }
60% { transform: rotate(-8deg) scale(0.92); opacity: 1; }
100% { transform: rotate(-12deg) scale(1); opacity: 0.9; }
}
/* === Mülleimer-Shake === */
@keyframes muelleimerShake {
0%, 100% { transform: translateX(0) rotate(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-6px) rotate(-1.2deg); }
20%, 40%, 60%, 80% { transform: translateX(6px) rotate(1.2deg); }
}
.scene-stage.shaking { animation: muelleimerShake 0.55s ease-in-out; }
/* === Polaroid-Frame um Avatar === */
.scene-actor-avatar {
background: var(--white);
padding: 4px 4px 12px;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(20,32,26,0.18), 0 0 0 1px rgba(20,32,26,0.06);
transform: rotate(-2deg);
transition: transform 0.2s ease;
}
.scene-actor-avatar:hover { transform: rotate(0deg) scale(1.05); }
.end-card.end-success .end-title { color: var(--forest); }
.end-card.end-mixed .end-title { color: var(--mustard); }
.end-card.end-rejected .end-title { color: var(--rust); }
.end-title {
font-family: var(--display);
font-size: 2.3rem;
font-weight: 700;
color: var(--forest);
line-height: 1.1;
margin-bottom: 12px;
}
.end-subtitle {
font-size: 1.05rem;
color: var(--ink);
opacity: 0.78;
max-width: 540px;
margin: 0 auto 22px;
line-height: 1.55;
}
.real-story-box {
background: var(--beige);
border-radius: 16px;
padding: 22px;
text-align: left;
margin: 26px 0;
border-left: 4px solid var(--mustard);
}
.real-story-box .label {
font-size: 0.74rem;
letter-spacing: 0.16em;
text-transform: uppercase;
font-weight: 700;
color: var(--sage);
margin-bottom: 8px;
}
.real-story-box p { font-size: 0.98rem; line-height: 1.55; }
.end-stats {
margin: 18px 0 8px;
text-align: center;
}
.end-stats .stars {
font-size: 2.2rem;
letter-spacing: 0.1em;
color: var(--mustard);
text-shadow: 0 2px 4px rgba(232,197,71,0.25);
margin-bottom: 8px;
}
.end-stats .stats-row {
display: inline-flex;
flex-wrap: wrap;
gap: 18px;
justify-content: center;
font-size: 0.86rem;
color: var(--ink);
opacity: 0.78;
}
.end-stats .stats-row strong {
color: var(--forest);
font-weight: 700;
}
.end-achievements {
margin: 24px auto 0;
max-width: 480px;
padding: 18px 22px;
background: linear-gradient(135deg, rgba(232,197,71,0.18), rgba(74,124,78,0.10));
border: 1px solid rgba(232,197,71,0.5);
border-radius: 14px;
text-align: left;
animation: achPop 0.6s cubic-bezier(.18,1.4,.6,1) 0.45s both;
}
@keyframes achPop {
from { transform: scale(0.94); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
.end-ach-label {
font-family: var(--display);
font-weight: 700;
color: var(--forest);
margin-bottom: 8px;
font-size: 1.02rem;
text-align: center;
}
.end-ach-list {
list-style: none;
padding: 0;
margin: 0;
}
.end-ach-list li {
font-size: 0.9rem;
padding: 6px 0;
border-top: 1px dashed rgba(31,75,55,0.2);
line-height: 1.4;
}
.end-ach-list li:first-child { border-top: none; }
.end-ach-list .ach-emoji {
font-size: 1.15rem;
margin-right: 6px;
}
.end-ach-list strong { color: var(--forest); }
/* ==========================================================
PROZESSKETTE — Schritt für Schritt Visualisierung
========================================================== */
.process-flow {
display: flex;
align-items: stretch;
gap: 0;
margin: 0 auto 22px;
padding: 14px 16px;
background: var(--white);
border-radius: var(--radius);
box-shadow: 0 2px 12px var(--shadow);
overflow-x: auto;
scrollbar-width: thin;
}
.process-flow::-webkit-scrollbar { height: 4px; }
.process-flow::-webkit-scrollbar-thumb { background: rgba(31,75,55,0.2); border-radius: 2px; }
.pf-node {
display: flex;
flex-direction: column;
align-items: center;
flex: 1 1 0;
min-width: 78px;
text-align: center;
position: relative;
padding: 0 4px;
}
.pf-node + .pf-node::before {
content: '';
position: absolute;
top: 22px;
left: -50%;
right: 50%;
height: 3px;
background: rgba(31,75,55,0.14);
z-index: 0;
}
.pf-node.done + .pf-node::before,
.pf-node.active::before { background: var(--sage); }
.pf-circle {
width: 44px; height: 44px;
border-radius: 50%;
background: var(--beige);
border: 2px solid rgba(31,75,55,0.18);
display: flex; align-items: center; justify-content: center;
font-size: 1.25rem;
position: relative;
z-index: 1;
flex-shrink: 0;
transition: all 0.3s ease;
}
.pf-node.done .pf-circle {
background: var(--sage);
border-color: var(--sage);
color: var(--white);
}
.pf-node.done .pf-circle::after {
content: '✓';
position: absolute;
top: -4px; right: -4px;
width: 18px; height: 18px;
background: var(--mustard);
color: var(--forest);
border-radius: 50%;
font-size: 0.72rem;
font-weight: 900;
display: flex; align-items: center; justify-content: center;
border: 2px solid var(--white);
}
.pf-node.active .pf-circle {
background: var(--mustard);
border-color: var(--forest);
box-shadow: 0 0 0 4px rgba(232,197,71,0.35);
animation: pfPulse 1.6s ease-in-out infinite;
}
@keyframes pfPulse {
0%, 100% { box-shadow: 0 0 0 4px rgba(232,197,71,0.35); }
50% { box-shadow: 0 0 0 10px rgba(232,197,71,0); }
}
.pf-node.outcome .pf-circle {
width: 52px; height: 52px;
font-size: 1.45rem;
border-width: 3px;
}
.pf-node.outcome.outcome-success .pf-circle { background: var(--sage); border-color: var(--sage); color: var(--white); }
.pf-node.outcome.outcome-mixed .pf-circle { background: var(--mustard); border-color: var(--mustard); color: var(--forest); }
.pf-node.outcome.outcome-rejected .pf-circle { background: var(--rust); border-color: var(--rust); color: var(--white); }
.pf-label {
font-size: 0.68rem;
font-weight: 700;
color: var(--forest);
margin-top: 8px;
line-height: 1.15;
letter-spacing: 0.01em;
hyphens: auto;
}
.pf-node:not(.done):not(.active):not(.outcome) .pf-label { color: rgba(31,75,55,0.45); }
/* Sackgassen / Mülleimer-Abzweigungen unter dem Knoten */
.pf-detours {
margin-top: 10px;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
position: relative;
}
.pf-detours::before {
content: '';
position: absolute;
top: -8px;
left: 50%;
transform: translateX(-50%);
width: 2px;
height: 8px;
background: rgba(20,32,26,0.25);
border-radius: 1px;
}
.pf-detour {
border-radius: 10px;
padding: 3px 9px;
font-size: 0.62rem;
font-weight: 700;
white-space: nowrap;
display: inline-flex;
align-items: center;
gap: 4px;
max-width: 130px;
overflow: hidden;
text-overflow: ellipsis;
animation: detourPop 0.35s cubic-bezier(.18,1.4,.6,1);
}
.pf-detour.kind-muelleimer {
background: var(--rust);
color: var(--white);
box-shadow: 0 1px 3px rgba(176,74,62,0.35);
}
.pf-detour.kind-soft {
background: #fff3cf;
color: var(--forest);
border: 1px solid var(--mustard);
box-shadow: 0 1px 2px rgba(232,197,71,0.30);
}
.pf-detour-icon { font-size: 0.72rem; line-height: 1; }
@keyframes detourPop {
from { transform: scale(0.6) translateY(-4px); opacity: 0; }
to { transform: scale(1) translateY(0); opacity: 1; }
}
/* Compact-Variante: kompaktere Sackgassen */
.process-flow.compact .pf-detour {
font-size: 0.58rem;
padding: 2px 7px;
max-width: 100px;
}
/* Kommende Schritte verstecken Inhalt — kein Spoiler */
.pf-node.future .pf-circle {
background: transparent;
border: 2px dashed rgba(31,75,55,0.28);
color: rgba(31,75,55,0.32);
font-weight: 700;
}
.pf-node.future .pf-label,
.pf-node.future .pf-sub {
visibility: hidden;
}
.pf-sub {
font-size: 0.62rem;
font-weight: 500;
color: var(--ink);
opacity: 0.7;
margin-top: 3px;
line-height: 1.2;
max-width: 92px;
}
/* Compact-Variante (im Spielscreen) */
.process-flow.compact { padding: 10px 12px; margin-bottom: 18px; }
.process-flow.compact .pf-circle { width: 36px; height: 36px; font-size: 1.05rem; }
.process-flow.compact .pf-node + .pf-node::before { top: 18px; }
.process-flow.compact .pf-label { font-size: 0.64rem; margin-top: 6px; }
.process-flow.compact .pf-sub { display: none; }
/* Full-Variante (im Endscreen) */
.process-flow.full {
padding: 22px 18px 18px;
margin-top: 8px;
background: linear-gradient(135deg, rgba(244,241,232,0.6), rgba(232,228,216,0.4));
border: 1px solid rgba(31,75,55,0.08);
}
.process-flow-heading {
font-family: var(--display);
font-weight: 700;
font-size: 1.02rem;
color: var(--forest);
margin: 22px 0 10px;
text-align: center;
}
.process-flow-heading-sub {
font-size: 0.84rem;
color: var(--ink);
opacity: 0.7;
margin-bottom: 14px;
text-align: center;
}
@media (max-width: 640px) {
.process-flow { padding: 10px 8px; }
.pf-node { min-width: 64px; }
.pf-circle { width: 36px; height: 36px; font-size: 1rem; }
.pf-node + .pf-node::before { top: 18px; }
.pf-label { font-size: 0.62rem; }
.pf-sub { display: none; }
}
/* ==========================================================
WUSSTEST-DU-OVERLAY (InfoCard zwischen Steps)
========================================================== */
.fun-fact-card {
background: linear-gradient(160deg, #fef6dc, #f4f1e8);
border: 1px solid rgba(232,197,71,0.55);
border-radius: 22px;
padding: 28px 28px 26px;
position: relative;
}
.fun-fact-icon {
font-size: 2.6rem;
line-height: 1;
margin: 0 auto 10px;
width: 76px; height: 76px;
border-radius: 50%;
background: var(--white);
display: flex; align-items: center; justify-content: center;
box-shadow: 0 4px 14px rgba(232,197,71,0.4);
animation: ffPop 0.55s cubic-bezier(.18,1.4,.6,1);
}
@keyframes ffPop {
from { transform: scale(0.5) rotate(-12deg); opacity: 0; }
to { transform: scale(1) rotate(0); opacity: 1; }
}
.fun-fact-label {
font-size: 0.74rem;
letter-spacing: 0.18em;
text-transform: uppercase;
font-weight: 700;
color: var(--sage);
margin-bottom: 6px;
}
.fun-fact-card .overlay-text { font-size: 1.04rem; }
/* ==========================================================
MINI-SPIEL: MEP-ANIMATION
========================================================== */
.anim-stage {
position: relative;
background: #f4f1e8;
border-radius: var(--radius);
padding: 18px;
box-shadow: 0 2px 12px var(--shadow);
margin-bottom: 22px;
}
.anim-map { width: 100%; max-width: 640px; display: block; margin: 0 auto; }
.anim-counter {
position: absolute; top: 18px; right: 22px;
text-align: right;
background: rgba(255,255,255,0.92);
border-radius: 10px;
padding: 8px 14px;
box-shadow: 0 2px 6px var(--shadow);
}
.anim-counter-num {
display: block;
font-family: var(--display);
font-weight: 900;
font-size: 2.2rem;
color: var(--forest);
line-height: 1;
}
.anim-counter-label {
display: block;
font-size: 0.72rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--sage);
margin-top: 2px;
}
.mep-dot {
animation: mepFly 2.6s cubic-bezier(.55,.05,.35,1) forwards;
transform-box: fill-box;
transform-origin: center;
}
@keyframes mepFly {
0% { transform: translate(0, 0) scale(1); opacity: 0; }
10% { opacity: 1; }
60% { transform: translate(calc(var(--dx) * 0.85), calc(var(--dy) * 0.85)) scale(0.9); opacity: 0.95; }
100% { transform: translate(var(--dx), var(--dy)) scale(0.6); opacity: 0.7; }
}
.anim-parliament {
opacity: 0.55;
animation: parliamentFill 5.5s ease-out forwards;
}
@keyframes parliamentFill {
0% { opacity: 0.55; }
70% { opacity: 0.85; }
100% { opacity: 1; }
}
/* ==========================================================
MULTICHOICE-BUTTONS
========================================================== */
.mc-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-bottom: 22px;
}
.mc-btn {
background: var(--white);
border: 2px solid rgba(31,75,55,0.18);
border-radius: 12px;
padding: 16px 18px;
font-family: var(--body);
font-weight: 600;
font-size: 0.98rem;
color: var(--ink);
cursor: pointer;
text-align: left;
transition: all 0.18s ease;
min-height: 56px;
box-shadow: 0 1px 4px var(--shadow);
}
.mc-btn:hover:not(:disabled):not(.mc-correct) {
border-color: var(--mustard);
background: #fffbe9;
transform: translateY(-1px);
}
.mc-btn:active { transform: translateY(0); }
.mc-btn.mc-correct {
border-color: var(--sage);
background: #e6f0e6;
color: var(--forest);
font-weight: 700;
}
.mc-btn.mc-wrong {
border-color: var(--rust);
background: #fbe9e6;
color: var(--rust);
animation: mcShake 0.3s;
}
.mc-btn:disabled { opacity: 0.6; cursor: not-allowed; }
@keyframes mcShake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-4px); }
75% { transform: translateX(4px); }
}
@media (max-width: 540px) {
.mc-grid { grid-template-columns: 1fr; }
}
/* ==========================================================
RESPONSIVE
========================================================== */
@media (max-width: 640px) {
.scene-body { padding: 22px 18px; }
.scene-question { font-size: 1.25rem; }
.start-title { font-size: 2rem; }
.sentence-template { font-size: 1.05rem; padding: 22px 20px; }
.slot { min-width: 100px; height: 46px; font-size: 0.85rem; }
header.app-header { padding: 12px 18px; }
.header-btn { padding: 6px 12px; font-size: 0.8rem; }
}
</style>
</head>
<body>
<!-- =======================================================
KOPFBEREICH
======================================================= -->
<header class="app-header">
<div class="header-left">
<a class="header-btn" id="backToCockpit" href="#" style="text-decoration:none;" title="Zurück zu allen Simulationen">← Alle Simulationen</a>
<button class="header-btn header-btn-scenarios" onclick="goToStart()" title="Zur Szenario-Auswahl in der EU-Werkstatt">📜 Szenarien</button>
</div>
<div class="logo">
<div class="logo-mark"></div>
<span>EU-Werkstatt</span>
</div>
<div class="header-actions">
<button class="header-btn" onclick="openGlossary()">📖 Glossar</button>
<div class="progress-bar" id="progressBar"></div>
</div>
</header>
<main>
<!-- =======================================================
STARTSCREEN
======================================================= -->
<section class="screen active" id="screen-start">
<div class="start-hero">
<svg class="start-illustration" viewBox="0 0 720 280" xmlns="http://www.w3.org/2000/svg">
<rect width="720" height="280" fill="#f4f1e8"/>
<circle cx="600" cy="70" r="40" fill="#e8c547"/>
<path d="M0,180 L120,90 L240,150 L360,70 L480,140 L600,80 L720,150 L720,280 L0,280 Z" fill="#1f4b37"/>
<path d="M0,210 L100,160 L220,200 L340,150 L460,190 L580,160 L720,200 L720,280 L0,280 Z" fill="#4a7c4e"/>
<path d="M0,240 L720,230 L720,280 L0,280 Z" fill="#3d6841"/>
<g transform="translate(360, 235)">
<path d="M -90,0 A 90,90 0 0,1 90,0 Z" fill="#e8e4d8"/>
<path d="M -75,0 A 75,75 0 0,1 75,0" fill="none" stroke="#1f4b37" stroke-width="2"/>
<path d="M -60,0 A 60,60 0 0,1 60,0" fill="none" stroke="#1f4b37" stroke-width="2"/>
<path d="M -45,0 A 45,45 0 0,1 45,0" fill="none" stroke="#1f4b37" stroke-width="2"/>
<path d="M -30,0 A 30,30 0 0,1 30,0" fill="none" stroke="#1f4b37" stroke-width="2"/>
<circle cx="-55" cy="-25" r="4" fill="#b04a3e"/>
<circle cx="-30" cy="-45" r="4" fill="#e8c547"/>
<circle cx="0" cy="-55" r="4" fill="#4a7c4e"/>
<circle cx="30" cy="-45" r="4" fill="#1f4b37"/>
<circle cx="55" cy="-25" r="4" fill="#1f4b37"/>
</g>
<g transform="translate(80, 220)">
<rect x="-3" y="0" width="6" height="14" fill="#1f4b37"/>
<path d="M -16,2 L 0,-30 L 16,2 Z" fill="#1f4b37"/>
<path d="M -13,-12 L 0,-38 L 13,-12 Z" fill="#1f4b37"/>
</g>
<g transform="translate(630, 225)">
<rect x="-3" y="0" width="6" height="14" fill="#1f4b37"/>
<path d="M -16,2 L 0,-30 L 16,2 Z" fill="#1f4b37"/>
<path d="M -13,-12 L 0,-38 L 13,-12 Z" fill="#1f4b37"/>
</g>
</svg>
<div class="eyebrow">Demokratie zum Anfassen</div>
<h1 class="start-title">Wie macht <em>Europa</em> ein Gesetz?</h1>
<p class="start-subtitle">
Lerne durch Karten, was eine Bürgerinitiative ist, wer das EU-Parlament wählt
und warum manche Gesetze scheitern. Tippe jede Karte an, um sie zu verstehen
und baue richtige Sätze daraus.
</p>
</div>
<div class="start-toolbar">
<div class="start-toolbar-block">
<h2 class="start-block-heading">Wähle einen echten Fall</h2>
<p class="start-block-sub">Alle Szenarien basieren auf echten EU-Vorgängen. Manche wurden Gesetz, andere scheiterten.</p>
</div>
<div class="diff-toggle" role="group" aria-label="Schwierigkeitsstufe">
<span class="diff-toggle-label">Schwierigkeit</span>
<div class="diff-toggle-buttons">
<button type="button" class="diff-btn diff-btn-easy active" data-diff="easy" onclick="setDifficulty('easy')">
<span class="diff-btn-emoji">🌱</span>
<span class="diff-btn-text">Einfach<small>4 Optionen · Hilfen</small></span>
</button>
<button type="button" class="diff-btn diff-btn-expert" data-diff="expert" onclick="setDifficulty('expert')">
<span class="diff-btn-emoji">🦉</span>
<span class="diff-btn-text">Profi<small>weniger Hinweise</small></span>
</button>
</div>
</div>
</div>
<div class="scenario-grid" id="scenarioGrid"></div>
<div class="achievements-strip" id="achievementsStrip"></div>
</section>
<!-- =======================================================
SPIELSCREEN
======================================================= -->
<section class="screen" id="screen-play">
<div class="process-flow compact" id="processFlowCompact"></div>
<div class="scene-stage">
<svg class="scene-illustration" id="sceneIllustration" viewBox="0 0 720 180" xmlns="http://www.w3.org/2000/svg"></svg>
<div class="scene-body">
<div class="scene-actor">
<div class="scene-actor-avatar" id="actorAvatar"></div>
<div>
<div class="scene-actor-name" id="actorName">Lena</div>
<div class="scene-actor-role" id="actorRole">16 Jahre</div>
</div>
</div>
<p class="scene-narration" id="sceneNarration"></p>
<h2 class="scene-question" id="sceneQuestion"></h2>
</div>
</div>
<div id="answerArea"></div>
<div class="feedback-bar" id="feedbackBar"></div>
<div class="actions-row">
<button class="btn btn-secondary" onclick="goToStart()">← Menü</button>
<button class="btn btn-primary" id="continueBtn" style="display:none;" onclick="advanceStep()">Weiter →</button>
</div>
</section>
<!-- =======================================================
ENDSCREEN
======================================================= -->
<section class="screen" id="screen-end">
<div class="end-card" id="endCard">
<div class="end-stamp" id="endStamp"></div>
<div id="endIllustrationWrap"></div>
<h1 class="end-title" id="endTitle">Geschafft!</h1>
<p class="end-subtitle" id="endSubtitle"></p>
<div class="end-stats" id="endStats"></div>
<div class="process-flow-heading">📍 So lief dieser Fall — Schritt für Schritt</div>
<div class="process-flow-heading-sub">Der ganze Weg auf einen Blick.</div>
<div class="process-flow full" id="processFlowFull"></div>
<div class="end-achievements" id="endAchievements" style="display:none;"></div>
<div class="real-story-box">
<div class="label">So war es wirklich</div>
<p id="realStory"></p>
</div>
<div class="btn-row">
<button class="btn btn-secondary" onclick="goToStart()">Anderes Szenario</button>
<button class="btn btn-primary" onclick="restartScenario()">Nochmal spielen</button>
</div>
</div>
</section>
</main>
<!-- =======================================================
OVERLAYS
======================================================= -->
<!-- Mülleimer-Overlay -->
<div class="overlay" id="overlayWrong">
<div class="overlay-card">
<svg class="overlay-illustration" viewBox="0 0 160 160" width="120" height="120" xmlns="http://www.w3.org/2000/svg">
<path d="M 40,55 L 120,55 L 110,140 L 50,140 Z" fill="#1f4b37"/>
<line x1="65" y1="65" x2="62" y2="130" stroke="#4a7c4e" stroke-width="2"/>
<line x1="80" y1="65" x2="80" y2="130" stroke="#4a7c4e" stroke-width="2"/>
<line x1="95" y1="65" x2="98" y2="130" stroke="#4a7c4e" stroke-width="2"/>
<g transform="translate(80, 50) rotate(-15) translate(-80, -50)">
<rect x="32" y="42" width="96" height="10" rx="3" fill="#1f4b37"/>
<rect x="70" y="32" width="20" height="12" rx="3" fill="#1f4b37"/>
</g>
<path d="M 60,82 L 70,78 L 76,86 L 72,94 L 62,92 Z" fill="#e8e4d8"/>
</svg>
<h2 class="overlay-title">Oje im Mülleimer!</h2>
<p class="overlay-text" id="wrongText"></p>
<button class="btn btn-primary" onclick="hideOverlay('overlayWrong'); restartScenario();">
Nochmal versuchen
</button>
</div>
</div>
<!-- Wusstest-du-Overlay (InfoCard zwischen Steps) -->
<div class="overlay" id="overlayFunFact">
<div class="overlay-card fun-fact-card">
<div class="fun-fact-icon" id="funFactEmoji">💡</div>
<div class="fun-fact-label">Wusstest du?</div>
<h2 class="overlay-title" id="funFactTitle"></h2>
<p class="overlay-text" id="funFactText"></p>
<button class="btn btn-primary" onclick="hideOverlay('overlayFunFact'); nextStep();">
Weiter geht's →
</button>
</div>
</div>
<!-- Meilenstein-Overlay -->
<div class="overlay" id="overlayMilestone">
<div class="overlay-card">
<svg class="overlay-illustration" viewBox="0 0 160 160" width="120" height="120" xmlns="http://www.w3.org/2000/svg">
<path d="M 80,20 L 96,64 L 142,68 L 106,96 L 118,140 L 80,114 L 42,140 L 54,96 L 18,68 L 64,64 Z" fill="#e8c547" stroke="#1f4b37" stroke-width="3" stroke-linejoin="round"/>
</svg>
<h2 class="overlay-title" id="milestoneTitle"></h2>
<p class="overlay-text" id="milestoneText"></p>
<button class="btn btn-primary" onclick="hideOverlay('overlayMilestone'); advanceStep();">
Weiter geht's →
</button>
</div>
</div>
<!-- Konzept-Detail-Overlay -->
<div class="overlay" id="overlayConcept">
<div class="concept-detail" style="position: relative;">
<button class="concept-detail-close" onclick="hideOverlay('overlayConcept')">×</button>
<div class="concept-detail-visual" id="conceptVisual" onclick="toggleConceptZoom()">
<span class="concept-detail-zoom-hint">🔍 antippen zum Vergrößern</span>
</div>
<div class="concept-detail-body">
<div class="concept-detail-label">Begriff erklärt</div>
<h2 class="concept-detail-title" id="conceptTitle"></h2>
<p class="concept-detail-summary" id="conceptSummary"></p>
<div class="concept-detail-body-text" id="conceptBody"></div>
<div class="concept-detail-section-heading" id="conceptFactsHeading">Auf einen Blick</div>
<ul class="facts-list" id="conceptFacts"></ul>
<div class="case-context-box empty" id="conceptCaseContext"></div>
<button class="btn btn-secondary" style="width: 100%; justify-content: center; margin-top: 18px;" onclick="hideOverlay('overlayConcept')">Verstanden</button>
</div>
</div>
</div>
<!-- Glossar-Overlay -->
<div class="overlay" id="overlayGlossary">
<div class="overlay-card" style="text-align: left; max-width: 720px;">
<h2 class="overlay-title">📖 Begriffe der EU-Demokratie</h2>
<p style="font-size: 0.92rem; color: var(--ink); opacity: 0.75; margin-bottom: 6px;">
Tippe eine Karte an, um den Begriff im Detail zu sehen.
</p>
<div class="glossary-grid" id="glossaryGrid"></div>
<div style="text-align: center; margin-top: 18px;">
<button class="btn btn-secondary" onclick="hideOverlay('overlayGlossary')">Schließen</button>
</div>
</div>
</div>
<script>
/* =========================================================
KONZEPT-KARTEN: Jeder Begriff bekommt eine Karte mit
- Mini-Visual (für die Karte selbst, ~70px hoch)
- Detail-Visual (für die Detailansicht, ~220px hoch)
- Kurzbeschreibung
- Faktenliste
========================================================= */
const CONCEPTS = {
/* INSTITUTIONEN */
kommission: {
label: 'EU-Kommission',
summary: 'Die EU-Kommission ist die "Regierung" der EU. Sie hat als einzige das Recht, neue Gesetze vorzuschlagen.',
tags: ['27 Mitglieder', '1 pro Land', 'Initiativrecht'],
body: `
<h4>Was macht die EU-Kommission?</h4>
<p>Die Kommission ist so etwas wie die Regierung der EU. Sie schlägt neue Gesetze vor, kümmert sich um die tägliche Verwaltung der Union und kontrolliert, ob sich die Mitgliedsländer an die EU-Regeln halten. Wenn nicht, kann sie ein Land sogar vor dem Europäischen Gerichtshof verklagen.</p>
<h4>Wer ist dabei?</h4>
<p>Genau <strong>27 Kommissar*innen</strong> einer aus jedem EU-Land. An der Spitze steht eine Präsidentin oder ein Präsident. Aktuell ist das <strong>Ursula von der Leyen</strong> aus Deutschland (seit 2019, zweite Amtszeit seit 2024). Jede:r Kommissar*in ist für einen Bereich zuständig: Landwirtschaft, Klima, Handel, Digitales und so weiter.</p>
<h4>Wo sitzt sie?</h4>
<p>Im Berlaymont-Gebäude in Brüssel einem riesigen, sternförmigen Bürokomplex mitten in Belgiens Hauptstadt.</p>
<h4>Wie wird sie gewählt?</h4>
<p>Nicht direkt. Die Staats- und Regierungschefs schlagen eine Person für den Vorsitz vor. Das EU-Parlament muss zustimmen. Die anderen 26 Kommissar*innen werden von ihren Heimatländern nominiert. Auch das gesamte Team muss vom Parlament bestätigt werden.</p>
<h4>Was darf nur die Kommission?</h4>
<p>Das <strong>Initiativrecht</strong> also: neue Gesetze offiziell vorzuschlagen. Weder das Parlament noch der Ministerrat dürfen das. Sie können die Kommission nur auffordern, einen Vorschlag zu machen.</p>
`,
caseContext: {
cage_age: `
<h4>Im Fall „End the Cage Age"</h4>
<p>Am <strong>30. Juni 2021</strong> lud die Kommission die Initiator*innen zu einer feierlichen Anhörung ein und versprach, einen Gesetzesvorschlag bis Ende 2023 zu erarbeiten. Die damals zuständige Gesundheitskommissarin Stella Kyriakides aus Zypern verkündete das Versprechen feierlich.</p>
<p><strong>Doch der Vorschlag wurde nie eingebracht.</strong> Die Kommission verschob das Vorhaben mehrfach auf 2024, dann 2025, jetzt Ende 2026. Seit März 2024 klagt die Bürgerinitiative deshalb vor dem Europäischen Gerichtshof. Im März 2026 fand die mündliche Verhandlung statt das Urteil steht noch aus.</p>
`
},
facts: [
'27 Kommissar*innen einer aus jedem Mitgliedstaat',
'Präsidentin: Ursula von der Leyen (seit 2019)',
'Sitz im Berlaymont-Gebäude in Brüssel',
'Schlägt neue EU-Gesetze vor',
'Kontrolliert, dass alle Länder sich daran halten'
],
miniSvg: () => `
<svg viewBox="0 0 80 70">
<rect x="10" y="20" width="60" height="46" fill="#1f4b37" rx="2"/>
${[0,1,2].map(r => [0,1,2,3].map(c =>
`<rect x="${16 + c*12}" y="${26 + r*10}" width="8" height="6" fill="#e8c547" opacity="${0.6 + Math.random()*0.4}"/>`
).join('')).join('')}
<circle cx="40" cy="14" r="6" fill="#e8c547" class="viz-pulse" style="transform-origin: 40px 14px"/>
</svg>
`,
detailSvg: () => `
<svg viewBox="0 0 580 220">
<rect width="580" height="220" fill="#1f4b37"/>
<!-- Berlaymont -->
<rect x="190" y="50" width="200" height="140" fill="#14201a" rx="2"/>
${[0,1,2,3,4].map(r => [0,1,2,3,4,5,6,7,8,9].map(c =>
`<rect x="${198 + c*19}" y="${58 + r*22}" width="14" height="14" fill="#e8c547" opacity="${0.5 + Math.random()*0.5}"/>`
).join('')).join('')}
<!-- 27 Figuren davor: 1 mustard mit Krone (Präsidentin) + 26 -->
<g transform="translate(50, 175)">
${Array.from({length: 26}, (_, i) => {
const x = i * 18 + (i >= 13 ? 36 : 0);
return `
<g transform="translate(${x}, 0)" class="viz-figure" style="animation-delay: ${i * 30}ms">
<circle cx="0" cy="-12" r="5" fill="#4a7c4e"/>
<path d="M -7,-7 L -7,12 L 7,12 L 7,-7 Z" fill="#4a7c4e"/>
</g>
`;
}).join('')}
<!-- Präsidentin in der Mitte -->
<g transform="translate(244, -4)" class="viz-figure" style="animation-delay: 800ms">
<path d="M -8,-22 L -5,-30 L 0,-26 L 5,-30 L 8,-22 Z" fill="#e8c547"/>
<circle cx="0" cy="-14" r="6" fill="#e8c547"/>
<path d="M -9,-9 L -9,14 L 9,14 L 9,-9 Z" fill="#e8c547"/>
</g>
</g>
</svg>
`
},
parlament: {
label: 'EU-Parlament',
summary: 'Das EU-Parlament vertritt die Bürger*innen Europas. Es ist die einzige EU-Institution, die direkt gewählt wird.',
tags: ['720 Sitze', '20 aus AT', 'Direkt gewählt'],
body: `
<h4>Was ist das EU-Parlament?</h4>
<p>Das Europäische Parlament ist die einzige EU-Institution, die direkt von den Bürger*innen gewählt wird. Es vertritt also DICH und alle anderen EU-Bürger*innen. Zusammen mit dem Ministerrat beschließt es die Gesetze, kontrolliert die Kommission und entscheidet über den EU-Haushalt.</p>
<h4>Wie viele Abgeordnete?</h4>
<p>Insgesamt <strong>720</strong> Sitze verteilt nach Bevölkerungsgröße auf die Länder. Deutschland hat die meisten (96), Malta und Zypern die wenigsten (jeweils 6). Aus <strong>Österreich kommen 20 Abgeordnete</strong> ins Parlament.</p>
<h4>Wer wählt das Parlament?</h4>
<p>Du! Bzw. alle EU-Bürger*innen ab 16 (in Österreich) bzw. 18 (in den meisten anderen Ländern). Gewählt wird alle 5 Jahre. Die letzte Wahl war im Juni 2024 die Wahlbeteiligung lag europaweit bei rund 51%.</p>
<h4>Wie funktioniert es?</h4>
<p>Die 720 Abgeordneten sitzen NICHT nach Ländern, sondern nach politischer Richtung in <strong>Fraktionen</strong>. Die größten Fraktionen sind die christdemokratische EVP, die Sozialdemokraten S&amp;D, die Liberalen Renew und die Grünen. Vor jeder Abstimmung diskutieren die Fraktionen ihre Position.</p>
<h4>Wo arbeiten sie?</h4>
<p>Das Parlament tagt sowohl in Straßburg (Frankreich) als auch in Brüssel (Belgien). Einmal im Monat ziehen alle Abgeordneten samt Mitarbeitenden für eine Sitzungswoche nach Straßburg um das ist umstritten und teuer, aber so im EU-Vertrag festgelegt. Die Präsidentin des Parlaments ist seit 2022 <strong>Roberta Metsola</strong> aus Malta.</p>
`,
caseContext: {
cage_age: `
<h4>Im Fall „End the Cage Age"</h4>
<p>Im Juni 2021 stimmte das Parlament mit überwältigender Mehrheit für die Ziele der Bürgerinitiative ab: <strong>558 Stimmen dafür, nur 37 dagegen</strong> bei 85 Enthaltungen. Das war ein klares Signal an die Kommission, das Käfigverbot voranzutreiben.</p>
<p>Seitdem hat das Parlament wiederholt Druck gemacht und die Kommission aufgefordert, endlich den Gesetzesvorschlag vorzulegen. Bisher ohne Erfolg.</p>
`
},
facts: [
'720 Abgeordnete insgesamt',
'Davon 20 aus Österreich',
'Wird alle 5 Jahre direkt gewählt',
'Beschließt zusammen mit dem Ministerrat die EU-Gesetze',
'Tagt in Straßburg und Brüssel'
],
miniSvg: () => `
<svg viewBox="0 0 80 70">
<g transform="translate(40, 56)">
<path d="M -32,0 A 32,32 0 0,1 32,0 Z" fill="#1f4b37"/>
<path d="M -26,0 A 26,26 0 0,1 26,0" fill="none" stroke="#e8c547" stroke-width="1.5"/>
<path d="M -20,0 A 20,20 0 0,1 20,0" fill="none" stroke="#e8c547" stroke-width="1.5"/>
<path d="M -14,0 A 14,14 0 0,1 14,0" fill="none" stroke="#e8c547" stroke-width="1.5"/>
<circle cx="-18" cy="-12" r="2" fill="#b04a3e"/>
<circle cx="-8" cy="-22" r="2" fill="#e8c547"/>
<circle cx="4" cy="-25" r="2" fill="#4a7c4e"/>
<circle cx="14" cy="-20" r="2" fill="#e8c547"/>
<circle cx="22" cy="-10" r="2" fill="#1f4b37"/>
</g>
</svg>
`,
detailSvg: () => `
<svg viewBox="0 0 580 220">
<rect width="580" height="220" fill="#1f4b37"/>
<g transform="translate(290, 200)">
<path d="M -180,0 A 180,180 0 0,1 180,0 Z" fill="#14201a"/>
${[150, 130, 110, 90, 70, 50, 30].map(r =>
`<path d="M ${-r},0 A ${r},${r} 0 0,1 ${r},0" fill="none" stroke="#4a7c4e" stroke-width="1"/>`
).join('')}
<!-- 720 Abgeordnete als Punkte stilisiert: 7 Reihen à ~24 Punkte mit verschiedenen Farben -->
${(() => {
const out = [];
const rows = [{r: 160, n: 28}, {r: 140, n: 24}, {r: 120, n: 20}, {r: 100, n: 16}, {r: 80, n: 12}, {r: 60, n: 9}, {r: 40, n: 6}];
const colors = ['#b04a3e', '#e8c547', '#4a7c4e', '#1f4b37', '#7099a0', '#9b6b7c'];
let dotIdx = 0;
rows.forEach(({r, n}) => {
for (let i = 0; i < n; i++) {
const angle = Math.PI - (i / (n-1)) * Math.PI;
const x = Math.cos(angle) * r;
const y = -Math.sin(angle) * r;
const colorIdx = Math.floor(i / (n / colors.length));
const fill = colors[Math.min(colorIdx, colors.length-1)];
out.push(`<circle cx="${x}" cy="${y}" r="3.5" fill="${fill}" class="viz-figure" style="animation-delay: ${dotIdx * 8}ms"/>`);
dotIdx++;
}
});
// 20 Österreich-Punkte hervorheben (rote Umrandung)
out.push(`<circle cx="0" cy="-160" r="5" fill="none" stroke="#e8c547" stroke-width="2" class="viz-pulse" style="transform-origin: 0px -160px"/>`);
return out.join('');
})()}
<!-- Rednerpult -->
<rect x="-15" y="-12" width="30" height="14" fill="#e8c547"/>
</g>
<text x="290" y="30" text-anchor="middle" font-family="Fraunces" font-weight="700" font-size="20" fill="#e8c547">720 Sitze</text>
<text x="290" y="48" text-anchor="middle" font-family="Manrope" font-size="11" fill="#e8e4d8" opacity="0.8">davon 20 aus Österreich</text>
</svg>
`
},
ministerrat: {
label: 'Rat der EU',
summary: 'Der Rat der EU auch "Ministerrat" genannt vertritt die Regierungen der Mitgliedsländer. Je nach Thema sitzen unterschiedliche Minister*innen am Tisch.',
tags: ['27 Minister*innen', 'Wechselt je Thema', 'Vorsitz alle 6 Monate'],
body: `
<h4>Was ist der Rat der EU?</h4>
<p>Der Rat der EU auch <strong>Ministerrat</strong> genannt vertritt die Regierungen der 27 Mitgliedsländer. Zusammen mit dem EU-Parlament beschließt er alle EU-Gesetze. Beide sind gleichberechtigt: Wenn einer NEIN sagt, gibt es kein Gesetz.</p>
<h4>Wer sitzt am Tisch?</h4>
<p>Je ein:e Minister*in pro EU-Land also <strong>27 Personen</strong>. Aber: Welche Minister*innen genau, hängt vom Thema ab. Geht es um Landwirtschaft, kommen die Agrarminister*innen. Geht es um Geld, die Finanzminister*innen. Bei Außenpolitik die Außenminister*innen. Insgesamt gibt es 10 verschiedene „Rats-Formationen".</p>
<h4>Wer hat den Vorsitz?</h4>
<p>Den Vorsitz hat alle 6 Monate ein anderes Land die sogenannte „Ratspräsidentschaft". Im ersten Halbjahr 2026 hat zum Beispiel Polen den Vorsitz, im zweiten Halbjahr Dänemark.</p>
<h4>Wie wird abgestimmt?</h4>
<p>Meistens mit „qualifizierter Mehrheit": Mindestens 15 von 27 Ländern (also 55%) müssen zustimmen, und diese Länder müssen zusammen mindestens 65% der EU-Bevölkerung repräsentieren. Bei besonders heiklen Themen (Steuern, Außenpolitik) braucht es Einstimmigkeit also alle 27.</p>
<h4>Achtung: Verwechslungsgefahr!</h4>
<p>Der Rat der EU ist NICHT der „Europäische Rat". Letzterer ist das Treffen der Regierungschefs (Bundeskanzler*in, Premierminister*in usw.) die machen keine Gesetze, sondern geben nur die große Richtung vor.</p>
`,
caseContext: {
cage_age: `
<h4>Im Fall „End the Cage Age"</h4>
<p>Bevor der Ministerrat über das Käfigverbot abstimmen kann, muss die Kommission erst einen Gesetzesvorschlag einbringen und genau das ist bisher nicht passiert. Daher hat der Rat zu diesem Thema noch keine formale Position bezogen.</p>
<p>Es gibt aber Hinweise, dass mehrere Länder mit großer Geflügelindustrie (z.B. Polen, Spanien) bei einem strengen Verbot zögern würden. Andere etwa Deutschland und Österreich setzen sich für strikte Regeln ein.</p>
`
},
facts: [
'27 Mitglieder je ein:e Minister*in pro Land',
'Wechselt je nach Thema (Agrar, Finanz, Außen ...)',
'Vorsitz alle 6 Monate ein anderes Land',
'Beschließt zusammen mit dem Parlament die Gesetze',
'Nicht zu verwechseln mit dem Europäischen Rat!'
],
miniSvg: () => `
<svg viewBox="0 0 80 70">
<ellipse cx="40" cy="42" rx="28" ry="14" fill="#1f4b37"/>
<ellipse cx="40" cy="38" rx="22" ry="10" fill="#e8c547"/>
${[
[12, 36], [22, 26], [40, 22], [58, 26], [68, 36], [62, 50], [40, 56], [18, 50]
].map(([x,y], i) =>
`<circle cx="${x}" cy="${y}" r="3" fill="#4a7c4e" class="viz-figure" style="animation-delay: ${i*80}ms"/>`
).join('')}
</svg>
`,
detailSvg: () => `
<svg viewBox="0 0 580 220">
<rect width="580" height="220" fill="#1f4b37"/>
<g transform="translate(290, 110)">
<ellipse cx="0" cy="20" rx="180" ry="50" fill="#14201a"/>
<ellipse cx="0" cy="14" rx="170" ry="44" fill="#1f4b37"/>
<ellipse cx="0" cy="10" rx="155" ry="36" fill="#e8c547"/>
<text x="0" y="16" text-anchor="middle" font-family="Fraunces" font-weight="700" font-size="22" fill="#1f4b37">27 Minister*innen</text>
<!-- 27 Stühle/Figuren rundum -->
${(() => {
const figs = [];
for (let i = 0; i < 27; i++) {
const angle = (i / 27) * Math.PI * 2 + Math.PI/2;
const rx = 200, ry = 70;
const x = Math.cos(angle) * rx;
const y = Math.sin(angle) * ry + 14;
const colors = ['#b04a3e', '#e8c547', '#4a7c4e', '#7099a0'];
const c = colors[i % colors.length];
figs.push(`
<g transform="translate(${x}, ${y})" class="viz-figure" style="animation-delay: ${i*40}ms">
<circle cx="0" cy="-6" r="5" fill="${c}"/>
<path d="M -7,-2 L -7,12 L 7,12 L 7,-2 Z" fill="${c}"/>
</g>
`);
}
return figs.join('');
})()}
</g>
</svg>
`
},
europRat: {
label: 'Europäischer Rat',
summary: 'Der Europäische Rat ist das Treffen der Staats- und Regierungschefs aller EU-Länder. Er gibt die großen politischen Richtungen vor beschließt aber KEINE Gesetze.',
tags: ['27 Regierungschefs', 'Tagt 4× im Jahr', 'Macht KEINE Gesetze'],
body: `
<h4>Was ist der Europäische Rat?</h4>
<p>Der Europäische Rat ist das Gipfeltreffen der EU-Spitzen. Hier kommen die Staats- und Regierungschefs aller 27 Mitgliedsländer zusammen, um die großen politischen Weichen zu stellen.</p>
<h4>Wer ist dabei?</h4>
<p><strong>27 Staats- und Regierungschefs</strong> (zum Beispiel der österreichische Bundeskanzler oder die finnische Premierministerin), plus die Präsidentin der EU-Kommission und der/die Präsident*in des Europäischen Rates selbst. Aktuell ist das <strong>António Costa</strong> aus Portugal (seit Dezember 2024).</p>
<h4>Was machen sie da?</h4>
<p>Sie diskutieren die ganz großen Themen: Krieg in der Ukraine, Migrationspolitik, Klimaziele, Beziehungen zu China und den USA, Erweiterung der EU. Sie geben politische Richtungen vor beschließen aber selbst KEINE Gesetze.</p>
<h4>Wie oft tagen sie?</h4>
<p>Mindestens 4 Mal im Jahr, oft auch öfter, wenn eine Krise das verlangt. Die Treffen finden meist in Brüssel statt und dauern 1 2 Tage.</p>
<h4>Wichtig: Verwechslungsgefahr!</h4>
<p>Der „Europäische Rat" und der „Rat der EU" (Ministerrat) sind ZWEI VERSCHIEDENE Institutionen. Der Europäische Rat sind die Chef*innen, der Rat der EU sind die jeweiligen Fachminister*innen. Selbst Erwachsene verwechseln das ständig.</p>
`,
facts: [
'27 Staats- und Regierungschefs',
'Plus die Präsident*innen von Kommission und Europ. Rat',
'Tagt mindestens 4× im Jahr',
'Macht KEINE Gesetze legt nur die strategische Richtung fest',
'Nicht verwechseln mit dem "Rat der EU" (Ministerrat)'
],
miniSvg: () => `
<svg viewBox="0 0 80 70">
<rect x="10" y="46" width="60" height="20" fill="#1f4b37" rx="2"/>
${[0,1,2,3,4,5].map(i =>
`<g transform="translate(${16 + i*10}, 30)" class="viz-figure" style="animation-delay: ${i*100}ms">
<circle cx="0" cy="0" r="3" fill="#e8c547"/>
<path d="M -3,3 L -3,14 L 3,14 L 3,3 Z" fill="#e8c547"/>
</g>`
).join('')}
</svg>
`,
detailSvg: () => `
<svg viewBox="0 0 580 220">
<rect width="580" height="220" fill="#1f4b37"/>
<!-- Konferenztisch -->
<ellipse cx="290" cy="180" rx="220" ry="20" fill="#14201a"/>
<rect x="70" y="120" width="440" height="60" fill="#4a7c4e"/>
<rect x="70" y="120" width="440" height="8" fill="#e8c547"/>
<!-- 27 Regierungschefs als Köpfe + Flaggen-Andeutung -->
${(() => {
const out = [];
for (let i = 0; i < 27; i++) {
const x = 90 + (i % 14) * 30 + (i >= 14 ? 10 : 0);
const y = i < 14 ? 90 : 60;
const colors = ['#b04a3e', '#e8c547', '#4a7c4e', '#7099a0'];
out.push(`
<g transform="translate(${x}, ${y})" class="viz-figure" style="animation-delay: ${i*30}ms">
<rect x="-6" y="-22" width="12" height="8" fill="${colors[i%4]}"/>
<circle cx="0" cy="-6" r="6" fill="#e8c547"/>
<path d="M -8,-2 L -8,18 L 8,18 L 8,-2 Z" fill="#e8c547"/>
</g>
`);
}
return out.join('');
})()}
<text x="290" y="40" text-anchor="middle" font-family="Fraunces" font-weight="700" font-size="20" fill="#e8c547">Staats- &amp; Regierungschefs</text>
<text x="290" y="58" text-anchor="middle" font-family="Manrope" font-size="11" fill="#e8e4d8" opacity="0.8">Tagt mindestens 4× im Jahr</text>
</svg>
`
},
/* INITIATIVEN & VERFAHREN */
buergerinitiative: {
label: 'Bürgerinitiative',
summary: 'Mit der Europäischen Bürgerinitiative können EU-Bürger*innen die Kommission auffordern, ein Gesetz zu erarbeiten. Die Kommission muss antworten, aber nicht zustimmen.',
tags: ['1 Mio. Stimmen', 'Aus 7+ Ländern', 'Ab 16 in Österreich'],
body: `
<h4>Was ist eine Europäische Bürgerinitiative?</h4>
<p>Mit einer Europäischen Bürgerinitiative (EBI) können EU-Bürger*innen die EU-Kommission AUFFORDERN, sich offiziell mit einem Thema zu befassen und einen Gesetzesvorschlag zu erarbeiten. Es ist das stärkste demokratische Werkzeug, das du als einzelne Person hast stärker als jede Demonstration oder jeder Brief. Wichtig: Es ist ein <strong>Aufforderungsrecht</strong>, kein direktes Initiativrecht. Die Kommission muss reagieren aber nicht zustimmen.</p>
<h4>Wie wird sie gestartet?</h4>
<p>Mindestens 7 Bürger*innen aus 7 verschiedenen EU-Ländern bilden ein <strong>Bürgerkomitee</strong> und melden ihre Initiative offiziell bei der Kommission an. In der Praxis stehen hinter den meisten Initiativen NGOs mit professioneller Infrastruktur sie übernehmen Kampagne, Webseite und Sammelaktion.</p>
<h4>Wie funktioniert die Sammlung?</h4>
<p>Nach der Registrierung bleiben 12 Monate Zeit, um Unterschriften zu sammeln online oder auf der Straße. Insgesamt müssen <strong>1 Million Unterschriften aus mindestens 7 EU-Ländern</strong> zusammenkommen. In jedem dieser Länder gilt zusätzlich eine länderspezifische Mindestzahl.</p>
<h4>Wer darf unterschreiben?</h4>
<p>Alle EU-Bürger*innen, die alt genug sind. In Österreich darfst du schon mit <strong>16</strong> unterschreiben, in den meisten anderen EU-Ländern erst mit 18.</p>
<h4>Was passiert bei Erfolg?</h4>
<p>Die Kommission MUSS sich offiziell mit der Initiative befassen, eine Anhörung im EU-Parlament durchführen und innerhalb von 6 Monaten öffentlich antworten. Sie kann zustimmen, ablehnen oder einen Mittelweg vorschlagen muss aber alles begründen.</p>
<h4>Bekannte Beispiele</h4>
<p>Bisher waren 14 Initiativen erfolgreich. Bekannte Fälle: „Right2Water" (2014), „Stop Vivisection" (2015), „End the Cage Age" (2020), „Save Cruelty Free Cosmetics" (2022).</p>
`,
caseContext: {
cage_age: `
<h4>End the Cage Age (2018 2021)</h4>
<p>Die Initiative wurde von der britischen Tierschutzorganisation <strong>Compassion in World Farming (CIWF)</strong> ins Leben gerufen und von über 170 weiteren NGOs unterstützt. Sie startete im September 2018 und sammelte bis September 2019 stolze <strong>1.397.113 gültige Unterschriften aus 18 Ländern</strong> damit die viertbestätigte Bürgerinitiative aller Zeiten.</p>
<p>Im Bürgerkomitee saßen Tierschützer*innen aus mehreren Ländern, darunter <strong>Mahi Klosterhalfen</strong>, Präsident der Albert Schweitzer Stiftung in Deutschland. Die meisten Unterschriften kamen aus Deutschland (~360.000), Frankreich (~165.000) und Italien (~135.000). Aus Österreich kamen rund 23.000.</p>
`
},
facts: [
'Mindestens 1 Million Unterschriften',
'Aus mindestens 7 EU-Ländern',
'In Österreich darf man ab 16 unterschreiben',
'Die Kommission MUSS antworten aber nicht zustimmen',
'14 erfolgreiche Initiativen seit Einführung'
],
miniSvg: () => `
<svg viewBox="0 0 80 70">
${Array.from({length: 30}, (_, i) => {
const r = i % 6, c = Math.floor(i / 6);
return `<circle cx="${10 + r*12}" cy="${10 + c*12}" r="3" fill="${(i+c)%3===0 ? '#e8c547' : '#1f4b37'}" class="viz-figure" style="animation-delay: ${i*30}ms"/>`;
}).join('')}
</svg>
`,
detailSvg: () => `
<svg viewBox="0 0 580 220">
<rect width="580" height="220" fill="#1f4b37"/>
<!-- Karte Europas (vereinfacht) links -->
<g transform="translate(140, 110)" opacity="0.95">
<path d="M -100,-50 Q -70,-70 -40,-60 Q -10,-75 30,-65 Q 70,-70 95,-40 Q 100,0 80,40 Q 50,55 20,50 Q -10,65 -40,50 Q -85,35 -100,0 Z" fill="#4a7c4e"/>
<!-- 7 Länder als Punkte highlighted -->
${[[-60,-30],[-20,-40],[20,-30],[50,-10],[40,30],[-10,20],[-50,10]].map((p,i) =>
`<circle cx="${p[0]}" cy="${p[1]}" r="6" fill="#e8c547" class="viz-pulse" style="transform-origin: ${p[0]}px ${p[1]}px; animation-delay: ${i*120}ms"/>`
).join('')}
</g>
<!-- Großer Zähler rechts -->
<g transform="translate(400, 110)">
<rect x="-90" y="-40" width="180" height="80" rx="12" fill="#e8c547"/>
<text x="0" y="-2" text-anchor="middle" font-family="Fraunces" font-weight="900" font-size="32" fill="#1f4b37" class="viz-counter">1.000.000</text>
<text x="0" y="22" text-anchor="middle" font-family="Manrope" font-weight="700" font-size="11" fill="#1f4b37" letter-spacing="1.5">UNTERSCHRIFTEN</text>
</g>
<text x="290" y="200" text-anchor="middle" font-family="Manrope" font-size="12" fill="#e8e4d8" opacity="0.85">aus mindestens 7 EU-Ländern</text>
</svg>
`
},
petition: {
label: 'Petition',
summary: 'Eine Petition ist eine schriftliche Bitte oder Beschwerde, die jeder Mensch ans EU-Parlament richten kann. Sie ist viel kleiner als eine Bürgerinitiative.',
tags: ['1 Person reicht', 'Ans Parlament', 'Geringe Wirkung'],
body: `
<h4>Was ist eine EU-Petition?</h4>
<p>Eine Petition ist eine schriftliche Eingabe ans EU-Parlament. Du kannst dich beschweren oder etwas fordern zum Beispiel wenn du findest, dass ein EU-Gesetz nicht richtig umgesetzt wird.</p>
<h4>Wer darf?</h4>
<p>Jede einzelne Person, die in der EU lebt. Auch Vereine und Firmen. Es ist KEINE Mindestzahl an Unterschriften nötig.</p>
<h4>Was passiert dann?</h4>
<p>Der „Petitionsausschuss" des Parlaments prüft die Eingabe. Er kann sie an die Kommission weitergeben oder Stellungnahmen einholen. Politisch hat eine Petition aber viel weniger Gewicht als eine erfolgreiche Bürgerinitiative.</p>
`,
facts: [
'Eine einzelne Person reicht aus',
'Geht an das EU-Parlament',
'Wird vom Petitionsausschuss geprüft',
'Politisch weniger schlagkräftig als eine Bürgerinitiative'
],
miniSvg: () => `
<svg viewBox="0 0 80 70">
<rect x="22" y="14" width="36" height="46" fill="#e8e4d8" stroke="#1f4b37" stroke-width="2"/>
<line x1="28" y1="22" x2="52" y2="22" stroke="#1f4b37" stroke-width="1.5"/>
<line x1="28" y1="28" x2="52" y2="28" stroke="#1f4b37" stroke-width="1.5"/>
<line x1="28" y1="34" x2="44" y2="34" stroke="#1f4b37" stroke-width="1.5"/>
<path d="M 28,46 q 6,-3 12,0 q 6,3 12,0" stroke="#1f4b37" stroke-width="2" fill="none"/>
</svg>
`,
detailSvg: () => `
<svg viewBox="0 0 580 220">
<rect width="580" height="220" fill="#1f4b37"/>
<g transform="translate(290, 110)">
<rect x="-60" y="-70" width="120" height="140" fill="#e8e4d8" rx="3" stroke="#e8c547" stroke-width="3"/>
<line x1="-44" y1="-50" x2="44" y2="-50" stroke="#1f4b37" stroke-width="2"/>
<line x1="-44" y1="-36" x2="44" y2="-36" stroke="#1f4b37" stroke-width="2"/>
<line x1="-44" y1="-22" x2="30" y2="-22" stroke="#1f4b37" stroke-width="2"/>
<line x1="-44" y1="-8" x2="44" y2="-8" stroke="#1f4b37" stroke-width="2"/>
<line x1="-44" y1="6" x2="40" y2="6" stroke="#1f4b37" stroke-width="2"/>
<path d="M -44,30 q 12,-6 24,0 q 12,6 24,0 q 12,-6 24,0" stroke="#1f4b37" stroke-width="2.5" fill="none"/>
<text x="0" y="58" text-anchor="middle" font-family="Manrope" font-size="10" fill="#1f4b37" font-weight="600">Unterschrift</text>
</g>
</svg>
`
},
/* MENGEN-KARTEN */
zahl_100k: {
label: '100.000',
summary: '100.000 Unterschriften das klingt viel, reicht für eine Europäische Bürgerinitiative aber NICHT aus.',
tags: ['Zu wenig für EU', 'Reicht national'],
facts: ['Reicht NICHT für eine EU-Bürgerinitiative', 'Würde aber für viele nationale Initiativen genügen'],
miniSvg: () => `<svg viewBox="0 0 80 70"><text x="40" y="44" text-anchor="middle" font-family="Fraunces" font-weight="900" font-size="22" fill="#1f4b37">100k</text></svg>`,
detailSvg: () => `<svg viewBox="0 0 580 220"><rect width="580" height="220" fill="#1f4b37"/><text x="290" y="130" text-anchor="middle" font-family="Fraunces" font-weight="900" font-size="80" fill="#e8c547">100.000</text><text x="290" y="170" text-anchor="middle" font-family="Manrope" font-size="14" fill="#e8e4d8">Unterschriften</text></svg>`
},
zahl_1mio: {
label: '1 Million',
summary: '1 Million Unterschriften das ist die Mindesthürde für eine Europäische Bürgerinitiative.',
tags: ['Hürde der Initiative', '12 Monate Zeit'],
body: `
<h4>Warum genau 1 Million?</h4>
<p>Die Zahl wurde mit dem Vertrag von Lissabon (2009) festgelegt. Sie soll machbar sein, gleichzeitig aber zeigen, dass das Anliegen wirklich europaweit getragen wird. Bei rund 450 Millionen EU-Bürger*innen sind 1 Million etwa 0,2% der Bevölkerung.</p>
<h4>Wie lang hat man Zeit?</h4>
<p>12 Monate ab dem offiziellen Start. Das klingt lang, ist aber knapp Unterschriften brauchen Werbung, Vereinsstrukturen und engagierte Sammler*innen.</p>
`,
caseContext: {
cage_age: `
<h4>Im Fall „End the Cage Age"</h4>
<p>Die Initiative sammelte sogar <strong>1.397.113 gültige Unterschriften</strong> also fast 400.000 mehr als nötig. Damit gehört sie zu den vier erfolgreichsten Bürgerinitiativen aller Zeiten.</p>
`
},
facts: ['Hürde der EU-Bürgerinitiative', 'Muss aus mindestens 7 EU-Ländern kommen', 'Innerhalb von 12 Monaten gesammelt'],
miniSvg: () => `<svg viewBox="0 0 80 70"><text x="40" y="44" text-anchor="middle" font-family="Fraunces" font-weight="900" font-size="20" fill="#1f4b37">1 Mio</text></svg>`,
detailSvg: () => `<svg viewBox="0 0 580 220"><rect width="580" height="220" fill="#1f4b37"/><text x="290" y="130" text-anchor="middle" font-family="Fraunces" font-weight="900" font-size="80" fill="#e8c547" class="viz-counter">1.000.000</text><text x="290" y="170" text-anchor="middle" font-family="Manrope" font-size="14" fill="#e8e4d8">Unterschriften das ist die Hürde</text></svg>`
},
zahl_10mio: {
label: '10 Millionen',
summary: '10 Millionen Unterschriften das wäre eine sehr hohe und unrealistische Hürde.',
tags: ['Unrealistisch hoch', 'Nicht nötig'],
facts: ['Wäre unrealistisch hoch', 'Die EU-Hürde liegt bei 1 Million'],
miniSvg: () => `<svg viewBox="0 0 80 70"><text x="40" y="44" text-anchor="middle" font-family="Fraunces" font-weight="900" font-size="18" fill="#1f4b37">10 Mio</text></svg>`,
detailSvg: () => `<svg viewBox="0 0 580 220"><rect width="580" height="220" fill="#1f4b37"/><text x="290" y="130" text-anchor="middle" font-family="Fraunces" font-weight="900" font-size="74" fill="#e8c547">10.000.000</text><text x="290" y="170" text-anchor="middle" font-family="Manrope" font-size="14" fill="#e8e4d8">Wäre unrealistisch hoch</text></svg>`
},
/* LÄNDER-ZAHL-KARTEN */
laender_1: {
label: '1',
summary: 'Aus nur einem Land das wäre keine europäische, sondern eine nationale Initiative.',
tags: ['Nicht europäisch', 'Nur national'],
facts: ['Reicht NICHT für eine Europäische Bürgerinitiative', 'EU-Initiativen müssen länderübergreifend sein'],
miniSvg: () => `<svg viewBox="0 0 80 70"><circle cx="40" cy="35" r="20" fill="#e8c547"/><text x="40" y="44" text-anchor="middle" font-family="Fraunces" font-weight="900" font-size="28" fill="#1f4b37">1</text></svg>`,
detailSvg: () => `<svg viewBox="0 0 580 220"><rect width="580" height="220" fill="#1f4b37"/><circle cx="290" cy="110" r="80" fill="#e8c547"/><text x="290" y="138" text-anchor="middle" font-family="Fraunces" font-weight="900" font-size="100" fill="#1f4b37">1</text><text x="290" y="200" text-anchor="middle" font-family="Manrope" font-size="14" fill="#e8e4d8">Land das ist nicht europäisch</text></svg>`
},
laender_7: {
label: '7',
summary: 'Aus mindestens 7 EU-Ländern müssen die Unterschriften kommen, damit eine Bürgerinitiative wirklich europäisch ist.',
tags: ['Mindestmaß', '~1/4 der EU'],
body: `
<h4>Warum 7 Länder?</h4>
<p>7 ist etwa ein Viertel der EU (27 Mitglieder). Damit will man sicherstellen, dass das Anliegen wirklich grenzüberschreitend ist und nicht nur ein Problem in einem einzelnen Land.</p>
<h4>Mindestschwellen pro Land</h4>
<p>Außerdem muss in jedem dieser Länder eine bestimmte Mindestzahl an Unterschriften erreicht werden. Sie hängt von der Bevölkerungsgröße ab: In großen Ländern wie Deutschland sind das mehr (rund 67.000), in kleinen Ländern wie Malta nur ein paar Tausend. In <strong>Österreich liegt die Schwelle bei 14.400 Unterschriften</strong> (seit Juli 2024).</p>
`,
caseContext: {
cage_age: `
<h4>Im Fall „End the Cage Age"</h4>
<p>Die Initiative sammelte aus <strong>18 Ländern</strong> also weit mehr als die nötigen 7. Die meisten Unterschriften kamen aus Deutschland (~360.000), Frankreich (~165.000) und Italien (~135.000). Aus Österreich kamen rund <strong>23.000 Unterschriften</strong> deutlich über der nötigen Schwelle.</p>
`
},
facts: ['Hürde der EU-Bürgerinitiative', 'Stellt sicher, dass das Anliegen länderübergreifend ist', '7 ist ein Viertel der EU-Mitglieder'],
miniSvg: () => `<svg viewBox="0 0 80 70"><circle cx="40" cy="35" r="20" fill="#e8c547"/><text x="40" y="44" text-anchor="middle" font-family="Fraunces" font-weight="900" font-size="28" fill="#1f4b37">7</text></svg>`,
detailSvg: () => `<svg viewBox="0 0 580 220"><rect width="580" height="220" fill="#1f4b37"/>
<!-- Karte mit 7 leuchtenden Punkten -->
<g transform="translate(290, 110)">
<path d="M -120,-60 Q -80,-80 -50,-70 Q 0,-85 50,-70 Q 100,-75 130,-50 Q 140,0 110,50 Q 50,70 0,55 Q -50,70 -100,50 Q -130,30 -130,-10 Z" fill="#4a7c4e"/>
${[[-70,-30],[-20,-50],[30,-40],[70,-15],[50,30],[-10,40],[-60,15]].map((p,i) =>
`<circle cx="${p[0]}" cy="${p[1]}" r="10" fill="#e8c547" class="viz-pulse" style="transform-origin: ${p[0]}px ${p[1]}px; animation-delay: ${i*150}ms"/>
<text x="${p[0]}" y="${p[1]+5}" text-anchor="middle" font-family="Fraunces" font-weight="900" font-size="14" fill="#1f4b37">${i+1}</text>`
).join('')}
</g>
</svg>`
},
laender_27: {
label: '27',
summary: '27 ist die Gesamtzahl der EU-Mitgliedsländer. Eine Bürgerinitiative braucht aber nur Unterschriften aus 7 davon.',
tags: ['Alle EU-Länder', 'Gesamtzahl'],
facts: ['27 ist die Gesamtzahl der EU-Länder', 'Für die Bürgerinitiative reichen 7 Länder', '27 Länder zustimmen müssen wäre praktisch unmöglich'],
miniSvg: () => `<svg viewBox="0 0 80 70"><circle cx="40" cy="35" r="20" fill="#e8c547"/><text x="40" y="44" text-anchor="middle" font-family="Fraunces" font-weight="900" font-size="22" fill="#1f4b37">27</text></svg>`,
detailSvg: () => `<svg viewBox="0 0 580 220"><rect width="580" height="220" fill="#1f4b37"/><text x="290" y="138" text-anchor="middle" font-family="Fraunces" font-weight="900" font-size="100" fill="#e8c547">27</text><text x="290" y="180" text-anchor="middle" font-family="Manrope" font-size="14" fill="#e8e4d8">Mitgliedsstaaten der EU</text></svg>`
},
/* AKTIONEN */
brief: {
label: 'Brief schreiben',
summary: 'Ein Brief an Politiker*innen kommt zwar an, hat aber wenig Wirkung eine einzelne Stimme im großen Stapel.',
tags: ['Kommt an', 'Wenig Wirkung'],
body: `
<h4>Briefe an Politiker*innen</h4>
<p>Du darfst jederzeit Politiker*innen schreiben an die EU-Kommissarin, an deine Abgeordnete im EU-Parlament, an Minister*innen. Die Mitarbeitenden lesen die Briefe meist tatsächlich.</p>
<h4>Warum bewirkt das selten viel?</h4>
<p>Weil Politiker*innen Tausende solcher Briefe bekommen. Eine einzelne Stimme geht im Stapel unter. Wirkungsvoller wird es, wenn viele Menschen koordiniert schreiben oder wenn der Brief von einem prominenten Verein oder einer Initiative kommt.</p>
`,
facts: [
'Jeder darf jederzeit Politiker*innen schreiben',
'Wirkung ist gering einzelne Stimme im großen Stapel',
'Sammelaktionen und Initiativen wirken viel stärker'
],
miniSvg: () => `<svg viewBox="0 0 80 70">
<rect x="18" y="22" width="44" height="30" fill="#e8e4d8" stroke="#1f4b37" stroke-width="2"/>
<path d="M 18,22 L 40,40 L 62,22" fill="none" stroke="#1f4b37" stroke-width="2"/>
</svg>`,
detailSvg: () => `<svg viewBox="0 0 580 220"><rect width="580" height="220" fill="#1f4b37"/>
<g transform="translate(290, 110)">
<rect x="-90" y="-40" width="180" height="100" fill="#e8e4d8" stroke="#e8c547" stroke-width="3"/>
<path d="M -90,-40 L 0,30 L 90,-40" fill="none" stroke="#e8c547" stroke-width="3"/>
</g>
</svg>`
},
demo: {
label: 'Demonstration',
summary: 'Eine Demonstration ist Meinungsäußerung in der Öffentlichkeit. Sie kann Druck erzeugen, bringt aber für sich allein noch kein EU-Gesetz in Gang.',
tags: ['Politischer Druck', 'Kein Gesetz'],
body: `
<h4>Was ist eine Demonstration?</h4>
<p>Eine Demonstration ist eine öffentliche Meinungsäußerung meist auf einem Platz oder in Form eines Marsches. Demonstrationen sind in jeder Demokratie ein Grundrecht und werden von der Polizei geschützt.</p>
<h4>Was bewirken sie?</h4>
<p>Demos zeigen, dass viele Menschen das gleiche Anliegen haben. Sie ziehen Medienaufmerksamkeit auf sich und setzen Politiker*innen unter Druck. Aber sie sind kein direktes Werkzeug der Gesetzgebung am Ende braucht es immer noch Bürgerinitiative, Wahlen oder politische Mehrheiten.</p>
<h4>Beispiele aus der EU</h4>
<p>„Fridays for Future"-Demos haben EU-weit Druck für ehrgeizigere Klimagesetze gemacht. Der „Green Deal" wäre ohne diesen Druck wohl weniger ambitioniert ausgefallen.</p>
`,
facts: [
'Erlaubt und wichtig in einer Demokratie',
'Erzeugt politischen Druck und Medienaufmerksamkeit',
'Allein reicht es nicht für ein EU-Gesetz'
],
miniSvg: () => `<svg viewBox="0 0 80 70">
${[20, 32, 44, 56].map((x,i) =>
`<g transform="translate(${x}, 24)" class="viz-figure" style="animation-delay: ${i*100}ms">
<rect x="-1" y="-14" width="2" height="14" fill="#1f4b37"/>
<rect x="-6" y="-22" width="12" height="8" fill="${i%2===0?'#e8c547':'#4a7c4e'}"/>
<circle cx="0" cy="6" r="3" fill="#1f4b37"/>
<path d="M -4,9 L -4,22 L 4,22 L 4,9 Z" fill="#1f4b37"/>
</g>`
).join('')}
</svg>`,
detailSvg: () => `<svg viewBox="0 0 580 220"><rect width="580" height="220" fill="#1f4b37"/>
${Array.from({length: 12}, (_, i) => {
const x = 100 + i * 30;
const colors = ['#e8c547', '#4a7c4e', '#b04a3e'];
return `<g transform="translate(${x}, 130)" class="viz-figure" style="animation-delay: ${i*70}ms">
<rect x="-2" y="-50" width="3" height="50" fill="#e8e4d8"/>
<rect x="-15" y="-78" width="30" height="22" fill="${colors[i%3]}"/>
<circle cx="0" cy="14" r="8" fill="#e8c547"/>
<path d="M -10,22 L -10,60 L 10,60 L 10,22 Z" fill="#e8c547"/>
</g>`;
}).join('')}
</svg>`
},
warten: {
label: 'Abwarten',
summary: 'Wer nichts tut, bewirkt nichts. Demokratie funktioniert nur durch Beteiligung.',
tags: ['Bewirkt nichts', 'Themen sterben'],
body: `
<h4>Warum Abwarten nicht hilft</h4>
<p>Politiker*innen haben volle Schreibtische. Wenn niemand für ein Thema kämpft, verschwindet es einfach. Das nennt man auch „verhungern lassen": Themen sterben, wenn keine Lobby, keine Initiative und keine Bürger*innen sie sichtbar halten.</p>
<h4>Demokratie ist Mitarbeit</h4>
<p>Jede Demokratie lebt davon, dass Menschen sich einmischen: durch Wählen, Unterschreiben, Argumentieren, Demonstrieren, Mitdiskutieren. Wer schweigt, überlässt anderen die Bühne.</p>
`,
facts: [
'Bewirkt nichts',
'Themen verschwinden, wenn niemand sie vertritt',
'Demokratie braucht aktive Bürger*innen'
],
miniSvg: () => `<svg viewBox="0 0 80 70">
<circle cx="40" cy="35" r="20" fill="none" stroke="#1f4b37" stroke-width="3"/>
<line x1="40" y1="35" x2="40" y2="22" stroke="#1f4b37" stroke-width="3" stroke-linecap="round" class="viz-spin" style="transform-origin: 40px 35px"/>
<line x1="40" y1="35" x2="50" y2="35" stroke="#1f4b37" stroke-width="3" stroke-linecap="round" class="viz-spin" style="transform-origin: 40px 35px; animation-duration: 8s"/>
</svg>`,
detailSvg: () => `<svg viewBox="0 0 580 220"><rect width="580" height="220" fill="#1f4b37"/>
<circle cx="290" cy="110" r="60" fill="none" stroke="#e8c547" stroke-width="6"/>
<line x1="290" y1="110" x2="290" y2="60" stroke="#e8c547" stroke-width="6" stroke-linecap="round" class="viz-spin" style="transform-origin: 290px 110px; animation-duration: 4s"/>
<line x1="290" y1="110" x2="320" y2="110" stroke="#e8c547" stroke-width="6" stroke-linecap="round" class="viz-spin" style="transform-origin: 290px 110px; animation-duration: 12s"/>
<text x="290" y="200" text-anchor="middle" font-family="Manrope" font-size="14" fill="#e8e4d8">Zeit vergeht nichts passiert</text>
</svg>`
},
/* RECHTE */
initiativrecht: {
label: 'Initiativrecht',
summary: 'Das Initiativrecht ist das Recht, einen fertigen Gesetzestext einzubringen. Diese formelle Macht hat in der EU fast nur die Kommission. Andere können sie aber auffordern.',
tags: ['Kommission schreibt', 'Andere fordern auf'],
body: `
<h4>Was ist das Initiativrecht?</h4>
<p>Das Initiativrecht ist das Recht, einen fertig ausformulierten Gesetzestext offiziell ins Verfahren einzubringen. In der EU liegt dieses formelle Recht fast ausschließlich bei der EU-Kommission (Art. 17 Abs. 2 EU-Vertrag).</p>
<h4>Aber: Es gibt auch ein Aufforderungsrecht</h4>
<p>Drei Akteure können die Kommission AUFFORDERN, einen Vorschlag zu erarbeiten:</p>
<p>• Das <strong>EU-Parlament</strong> (Art. 225 AEUV)<br>
• Der <strong>Ministerrat</strong> (Art. 241 AEUV)<br>
• <strong>1 Million EU-Bürger*innen</strong> über eine Bürgerinitiative (Art. 11 Abs. 4 EUV)</p>
<p>Die Kommission muss reagieren aber sie ist nicht verpflichtet, dem zu folgen. Sagt sie Nein, muss sie ihre Entscheidung schriftlich begründen.</p>
<h4>Warum diese Trennung?</h4>
<p>Historisch sollte das Initiativmonopol der Kommission verhindern, dass sich nationale Regierungen mit konkurrierenden Vorschlägen blockieren. Heute kritisieren viele dieses Modell: Das einzige direkt gewählte Organ das Parlament darf selbst keine Gesetze einbringen. Eine Reform wird seit Jahren diskutiert.</p>
<h4>Anders als in Österreich oder Deutschland</h4>
<p>Im österreichischen Nationalrat oder im deutschen Bundestag dürfen Abgeordnete oder Fraktionen direkt Gesetze einbringen. Im EU-Parlament geht das NICHT das ist einer der wichtigsten Unterschiede zwischen nationaler und europäischer Demokratie.</p>
`,
caseContext: {
cage_age: `
<h4>Im Fall „End the Cage Age"</h4>
<p>Compassion in World Farming und das Bürgerkomitee konnten die Initiative starten aber kein Gesetz schreiben. Sie konnten nur die Kommission auffordern, eines zu schreiben. Die Kommission hat das im Juni 2021 versprochen, bis heute (Mai 2026) aber nicht geliefert.</p>
<p>Deshalb klagt das Bürgerkomitee seit März 2024 vor dem Europäischen Gerichtshof. Es ist das erste Mal, dass eine Bürgerinitiative die Kommission verklagt mit der Begründung, das Aufforderungsrecht der Bürger*innen werde entwertet, wenn die Kommission Versprechen einfach nicht einlöst.</p>
`
},
facts: [
'Formelles Initiativrecht: fast nur Kommission',
'Aufforderungsrecht: Parlament, Ministerrat, Bürgerinitiative',
'Kommission muss antworten aber nicht zustimmen',
'Bei Ablehnung muss sie eine schriftliche Begründung liefern'
],
miniSvg: () => `<svg viewBox="0 0 80 70">
<rect x="20" y="18" width="22" height="30" fill="#e8e4d8" stroke="#1f4b37" stroke-width="2"/>
<line x1="24" y1="24" x2="38" y2="24" stroke="#1f4b37"/>
<line x1="24" y1="30" x2="38" y2="30" stroke="#1f4b37"/>
<line x1="24" y1="36" x2="34" y2="36" stroke="#1f4b37"/>
<path d="M 48,32 L 62,32" stroke="#1f4b37" stroke-width="3"/>
<path d="M 62,32 L 56,28 L 56,36 Z" fill="#1f4b37"/>
</svg>`,
detailSvg: () => `<svg viewBox="0 0 580 220"><rect width="580" height="220" fill="#1f4b37"/>
<g transform="translate(180, 110)">
<rect x="-40" y="-50" width="80" height="100" fill="#e8e4d8" stroke="#e8c547" stroke-width="3"/>
<line x1="-28" y1="-30" x2="28" y2="-30" stroke="#1f4b37" stroke-width="2"/>
<line x1="-28" y1="-16" x2="28" y2="-16" stroke="#1f4b37" stroke-width="2"/>
<line x1="-28" y1="-2" x2="28" y2="-2" stroke="#1f4b37" stroke-width="2"/>
<line x1="-28" y1="12" x2="20" y2="12" stroke="#1f4b37" stroke-width="2"/>
</g>
<path d="M 240,110 L 350,110" stroke="#e8c547" stroke-width="6" stroke-linecap="round"/>
<path d="M 350,110 L 335,98 L 335,122 Z" fill="#e8c547"/>
<g transform="translate(440, 110)">
<rect x="-50" y="-50" width="100" height="100" fill="#e8c547" rx="6"/>
<text x="0" y="-10" text-anchor="middle" font-family="Fraunces" font-weight="700" font-size="18" fill="#1f4b37">EU-</text>
<text x="0" y="14" text-anchor="middle" font-family="Fraunces" font-weight="700" font-size="18" fill="#1f4b37">Kommission</text>
</g>
<text x="290" y="200" text-anchor="middle" font-family="Manrope" font-size="12" fill="#e8e4d8" opacity="0.85">Nur SIE darf Gesetze vorschlagen</text>
</svg>`
},
mitentscheidung: {
label: 'Mitentscheidung',
summary: 'Mitentscheidung heißt: Parlament UND Ministerrat müssen einem Gesetz zustimmen. Beide sind gleichberechtigt einer allein kann nichts beschließen.',
tags: ['Parlament + Rat', 'Beide gleichberechtigt'],
body: `
<h4>Was ist Mitentscheidung?</h4>
<p>Mitentscheidung auch „ordentliches Gesetzgebungsverfahren" genannt ist DER Standardweg, wie EU-Gesetze entstehen. Die Idee: Sowohl die direkt gewählten Abgeordneten (Parlament) als auch die nationalen Regierungen (Ministerrat) müssen einem Gesetz zustimmen. Beide sind gleichberechtigt.</p>
<h4>Was passiert bei Streit?</h4>
<p>Sehr häufig haben Parlament und Rat unterschiedliche Vorstellungen. Dann beginnt der sogenannte <strong>Trilog</strong>: Vertreter*innen von Parlament, Rat und Kommission setzen sich zusammen und verhandeln einen Kompromiss. Das kann Wochen oder Monate dauern.</p>
<h4>Was, wenn sie sich nicht einigen?</h4>
<p>Wenn auch nach mehreren Verhandlungsrunden keine Einigung gelingt, scheitert das Gesetz. Es gibt drei „Lesungen", also drei Versuche. Klappt es nicht, wandert der Vorschlag in den Mülleimer.</p>
<h4>Beispiele für erfolgreiche Mitentscheidung</h4>
<p>Mit Mitentscheidung wurden zum Beispiel beschlossen: das Aus der Roaming-Gebühren (2017), die Datenschutz-Grundverordnung DSGVO (2016), die einheitliche USB-C-Pflicht für Handys (2022), das Recht auf Reparatur (2024).</p>
`,
facts: [
'Parlament UND Ministerrat müssen JA sagen',
'Beide sind gleichberechtigt',
'Bei Streit: Vermittlungsverfahren ("Trilog")',
'Bis zu 3 Lesungen möglich',
'Wenn einer NEIN sagt, scheitert das Gesetz'
],
miniSvg: () => `<svg viewBox="0 0 80 70">
<circle cx="26" cy="35" r="14" fill="#4a7c4e"/>
<path d="M 20,35 L 24,40 L 32,30" stroke="#e8c547" stroke-width="2.5" fill="none" stroke-linecap="round"/>
<text x="40" y="40" text-anchor="middle" font-family="Fraunces" font-weight="900" font-size="14" fill="#1f4b37">+</text>
<circle cx="54" cy="35" r="14" fill="#4a7c4e"/>
<path d="M 48,35 L 52,40 L 60,30" stroke="#e8c547" stroke-width="2.5" fill="none" stroke-linecap="round"/>
</svg>`,
detailSvg: () => `<svg viewBox="0 0 580 220"><rect width="580" height="220" fill="#1f4b37"/>
<g transform="translate(180, 110)">
<circle r="60" fill="#4a7c4e"/>
<path d="M -25,0 L -8,18 L 25,-15" stroke="#e8c547" stroke-width="8" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
<text x="0" y="80" text-anchor="middle" font-family="Fraunces" font-weight="700" font-size="14" fill="#e8c547">Parlament</text>
</g>
<text x="290" y="120" text-anchor="middle" font-family="Fraunces" font-weight="900" font-size="50" fill="#e8c547">+</text>
<g transform="translate(400, 110)">
<circle r="60" fill="#4a7c4e"/>
<path d="M -25,0 L -8,18 L 25,-15" stroke="#e8c547" stroke-width="8" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
<text x="0" y="80" text-anchor="middle" font-family="Fraunces" font-weight="700" font-size="14" fill="#e8c547">Ministerrat</text>
</g>
</svg>`
},
volksabstimmung: {
label: 'Volksabstimmung',
summary: 'Eine Volksabstimmung also direkte Abstimmung der Bürger*innen gibt es bei EU-Gesetzen NICHT. So läuft es bei der EU nicht.',
tags: ['Nicht in EU-Gesetzen', 'Nur national üblich'],
body: `
<h4>Was ist eine Volksabstimmung?</h4>
<p>Bei einer Volksabstimmung (auch Referendum) entscheiden die Bürger*innen direkt mit JA oder NEIN über eine konkrete Frage. Das gibt es zum Beispiel in der Schweiz sehr oft, in Österreich seltener.</p>
<h4>Warum nicht bei EU-Gesetzen?</h4>
<p>EU-Gesetze werden von gewählten Vertreter*innen gemacht also vom EU-Parlament (das du wählen darfst) und von den Minister*innen der Länder. Direkte EU-weite Volksabstimmungen sind in den EU-Verträgen NICHT vorgesehen.</p>
<h4>Wann gibt es Volksabstimmungen zu EU-Themen?</h4>
<p>Manche Länder haben nationale Volksabstimmungen, bevor sie der EU beitreten oder größere EU-Verträge unterschreiben. Beispiel: Österreichs EU-Beitritt 1994 wurde per Volksabstimmung beschlossen. Aber bei einem konkreten EU-Gesetz fragt niemand das Volk direkt.</p>
`,
facts: [
'In der EU-Gesetzgebung NICHT vorgesehen',
'Bürger*innen sind über das gewählte Parlament beteiligt',
'In manchen Ländern gibt es nationale Volksabstimmungen',
'Beispiel: Österreichs EU-Beitritt 1994 per Volksabstimmung'
],
miniSvg: () => `<svg viewBox="0 0 80 70">
<rect x="22" y="22" width="36" height="30" fill="#1f4b37"/>
<rect x="28" y="14" width="6" height="14" fill="#1f4b37"/>
<rect x="46" y="14" width="6" height="14" fill="#1f4b37"/>
<rect x="32" y="32" width="16" height="14" fill="#e8c547"/>
</svg>`,
detailSvg: () => `<svg viewBox="0 0 580 220"><rect width="580" height="220" fill="#1f4b37"/>
<g transform="translate(290, 110)">
<rect x="-70" y="-40" width="140" height="100" fill="#14201a"/>
<rect x="-50" y="-70" width="20" height="40" fill="#14201a"/>
<rect x="30" y="-70" width="20" height="40" fill="#14201a"/>
<rect x="-30" y="-10" width="60" height="40" fill="#e8c547"/>
<text x="0" y="16" text-anchor="middle" font-family="Fraunces" font-weight="900" font-size="22" fill="#1f4b37">JA</text>
</g>
<text x="290" y="200" text-anchor="middle" font-family="Manrope" font-size="13" fill="#e8c547" opacity="0.9">Bei EU-Gesetzen so nicht vorgesehen</text>
</svg>`
},
eu_gesetz: {
label: 'EU-Gesetz',
summary: 'Ein EU-Gesetz gilt in allen 27 Mitgliedsländern gleichzeitig. So entstehen gemeinsame Regeln in ganz Europa.',
tags: ['27 Länder gleichzeitig', 'Verordnung/Richtlinie'],
body: `
<h4>Was ist ein EU-Gesetz?</h4>
<p>Ein EU-Gesetz ist eine Regel, die in allen 27 Mitgliedsländern gleichzeitig gilt. So entsteht ein gemeinsamer europäischer Markt mit gemeinsamen Standards egal, ob du in Wien, Madrid oder Helsinki einkaufst.</p>
<h4>Verschiedene Arten</h4>
<p>Es gibt zwei Haupttypen: <strong>Verordnungen</strong> gelten direkt in allen Ländern, ohne dass jedes Land sie in eigenes Recht „übersetzen" muss. <strong>Richtlinien</strong> geben ein Ziel vor, jedes Land muss aber selbst ein nationales Gesetz erlassen, um dieses Ziel umzusetzen. Das gibt etwas mehr Flexibilität.</p>
<h4>Beispiele aus deinem Alltag</h4>
<p>Manche EU-Gesetze sind so selbstverständlich geworden, dass viele gar nicht mehr wissen, dass sie aus Brüssel kommen: USB-C-Stecker, kein Roaming im EU-Ausland, einheitliche Lebensmittel-Kennzeichnung, der Datenschutz auf Webseiten (DSGVO).</p>
<h4>Wer kontrolliert die Einhaltung?</h4>
<p>Die EU-Kommission. Wenn ein Land sich nicht an EU-Gesetze hält, kann sie es vor dem Europäischen Gerichtshof verklagen. Das passiert tatsächlich regelmäßig auch Österreich wurde schon mehrfach verklagt.</p>
`,
facts: [
'Gilt in allen 27 EU-Ländern',
'Wird von Parlament und Ministerrat beschlossen',
'Beispiele: USB-C, kein Roaming, DSGVO',
'Die Kommission kontrolliert die Einhaltung'
],
miniSvg: () => `<svg viewBox="0 0 80 70">
<path d="M 14,30 Q 30,18 50,28 Q 66,22 70,38 Q 64,52 44,50 Q 24,56 12,46 Q 8,38 14,30 Z" fill="#4a7c4e"/>
<circle cx="40" cy="36" r="10" fill="#e8c547" class="viz-pulse" style="transform-origin: 40px 36px"/>
<path d="M 35,36 L 39,40 L 45,32" stroke="#1f4b37" stroke-width="2" fill="none" stroke-linecap="round"/>
</svg>`,
detailSvg: () => `<svg viewBox="0 0 580 220"><rect width="580" height="220" fill="#1f4b37"/>
<g transform="translate(290, 110)">
<path d="M -180,-40 Q -120,-70 -60,-50 Q 0,-80 60,-55 Q 130,-65 180,-30 Q 200,30 160,60 Q 80,80 0,65 Q -80,80 -150,55 Q -200,20 -180,-40 Z" fill="#4a7c4e"/>
${[[-130,-25],[-80,-30],[-40,-15],[20,-25],[80,-15],[130,5],[100,40],[40,30],[-30,35],[-90,20],[-140,15],[40,-50],[120,-30]].map(([x,y],i) =>
`<circle cx="${x}" cy="${y}" r="6" fill="#e8c547" class="viz-pulse" style="transform-origin: ${x}px ${y}px; animation-delay: ${i*100}ms"/>`
).join('')}
</g>
<text x="290" y="200" text-anchor="middle" font-family="Manrope" font-size="13" fill="#e8e4d8">Gilt in allen 27 Ländern</text>
</svg>`
},
/* ZUSTÄNDIGKEIT & EU-RECHT */
eu_zustaendig: {
label: 'EU-Zuständigkeit',
summary: 'Die EU darf nur Gesetze zu Themen machen, für die sie nach den Verträgen zuständig ist. Bei vielem entscheiden weiterhin die Länder allein.',
tags: ['Nicht alles ist EU-Sache', 'Steht in den Verträgen'],
body: `
<h4>Was darf die EU regeln, was nicht?</h4>
<p>Die EU ist <strong>kein Bundesstaat</strong>. Sie darf nur Gesetze zu Themen machen, für die ihr die Mitgliedsländer in den EU-Verträgen ausdrücklich Macht übertragen haben. Alles andere bleibt in der Hand der einzelnen Länder.</p>
<h4>Beispiele für EU-Zuständigkeit</h4>
<p>Klar EU-Sache sind: gemeinsamer Markt (z.B. USB-C-Pflicht, Roaming), Zollpolitik, Wettbewerbsregeln, Klimaschutz, Verbraucherschutz, Tierschutz auf Bauernhöfen.</p>
<h4>Beispiele für nationale Zuständigkeit</h4>
<p>Ländersache bleiben: Schulsystem, Steuern (meistens), Pensionen, Gesundheitssystem, Polizei, Familienrecht. Auch die Frage, ob ein Land aus der EU austritt, ist <strong>ausschließlich Sache des betreffenden Landes selbst</strong> (Artikel 50 EU-Vertrag).</p>
<h4>Was passiert bei einer Bürgerinitiative zu Nicht-EU-Themen?</h4>
<p>Die Kommission lehnt die Registrierung ab. Sie schreibt dann eine Begründung, gegen die man vor Gericht klagen kann. So endete zum Beispiel die „Stop Brexit"-Initiative: abgelehnt, weil die EU einem Mitgliedsland den Austritt nicht verbieten darf.</p>
`,
facts: [
'Die EU braucht für jede Regelung eine Rechtsgrundlage in den Verträgen',
'Themen wie Schule, Steuern, Familie bleiben national',
'Bürgerinitiativen müssen sich an EU-Themen halten',
'Sonst lehnt die Kommission die Registrierung ab'
],
miniSvg: () => `<svg viewBox="0 0 80 70">
<circle cx="40" cy="36" r="22" fill="#4a7c4e"/>
<path d="M 18,36 L 62,36 M 40,14 L 40,58" stroke="#e8c547" stroke-width="2"/>
<text x="28" y="30" font-family="Fraunces" font-size="9" font-weight="700" fill="#e8c547">EU</text>
<text x="48" y="30" font-family="Fraunces" font-size="9" font-weight="700" fill="#1f4b37">AT</text>
<text x="28" y="52" font-family="Fraunces" font-size="9" font-weight="700" fill="#1f4b37">DE</text>
<text x="48" y="52" font-family="Fraunces" font-size="9" font-weight="700" fill="#1f4b37">FR</text>
</svg>`,
detailSvg: () => `<svg viewBox="0 0 580 220">
<rect width="580" height="220" fill="#1f4b37"/>
<!-- Linke Säule: EU-Themen -->
<g transform="translate(150, 110)">
<rect x="-100" y="-80" width="200" height="160" rx="12" fill="#4a7c4e"/>
<text x="0" y="-50" text-anchor="middle" font-family="Fraunces" font-weight="700" font-size="16" fill="#e8c547">EU regelt</text>
<text x="0" y="-22" text-anchor="middle" font-family="Manrope" font-size="12" fill="#f4f1e8">• Binnenmarkt</text>
<text x="0" y="0" text-anchor="middle" font-family="Manrope" font-size="12" fill="#f4f1e8">• Klima &amp; Umwelt</text>
<text x="0" y="22" text-anchor="middle" font-family="Manrope" font-size="12" fill="#f4f1e8">• Verbraucherschutz</text>
<text x="0" y="44" text-anchor="middle" font-family="Manrope" font-size="12" fill="#f4f1e8">• Wettbewerb</text>
<text x="0" y="66" text-anchor="middle" font-family="Manrope" font-size="12" fill="#f4f1e8">• Außenhandel</text>
</g>
<!-- Rechte Säule: Nationale Themen -->
<g transform="translate(430, 110)">
<rect x="-100" y="-80" width="200" height="160" rx="12" fill="#e8c547"/>
<text x="0" y="-50" text-anchor="middle" font-family="Fraunces" font-weight="700" font-size="16" fill="#1f4b37">Land regelt</text>
<text x="0" y="-22" text-anchor="middle" font-family="Manrope" font-size="12" fill="#1f4b37">• Schulsystem</text>
<text x="0" y="0" text-anchor="middle" font-family="Manrope" font-size="12" fill="#1f4b37">• Steuern</text>
<text x="0" y="22" text-anchor="middle" font-family="Manrope" font-size="12" fill="#1f4b37">• Pensionen</text>
<text x="0" y="44" text-anchor="middle" font-family="Manrope" font-size="12" fill="#1f4b37">• Polizei</text>
<text x="0" y="66" text-anchor="middle" font-family="Manrope" font-size="12" fill="#1f4b37">• EU-Austritt</text>
</g>
</svg>`
},
/* LOBBYISMUS */
lobby: {
label: 'Lobbyismus',
summary: 'Lobbyismus heißt: Konzerne, Verbände oder Vereine versuchen, Politiker*innen zu beeinflussen. In Brüssel arbeiten tausende Lobbyist*innen.',
tags: ['~12.000 in Brüssel', 'Beeinflusst Gesetze'],
body: `
<h4>Was ist Lobbyismus?</h4>
<p>Lobbyismus ist die organisierte Einflussnahme auf Politiker*innen. Lobbyist*innen treffen sich mit Abgeordneten und Kommissar*innen, schreiben Stellungnahmen, organisieren Studien mit dem Ziel, dass Gesetze in ihrem Sinne ausfallen.</p>
<h4>Wer betreibt Lobbyismus?</h4>
<p>Alle, die ein Interesse haben: große Konzerne (Apple, Google, Volkswagen), Branchenverbände (Bauernverband, Industrieverband), aber auch Umweltorganisationen (Greenpeace, WWF), Tierschützer und Gewerkschaften.</p>
<h4>Wie viele in Brüssel?</h4>
<p>Im EU-Transparenzregister sind rund <strong>12.000 Organisationen</strong> eingetragen. Konzerne geben dabei oft Millionenbeträge aus. Die Tabakindustrie, Banken und Tech-Konzerne gehören zu den größten Ausgebern.</p>
<h4>Ist Lobbyismus erlaubt?</h4>
<p>Ja, in einer Demokratie schon jeder darf seine Interessen vertreten. Problematisch wird es, wenn nur die Reichen und Mächtigen gehört werden. Deshalb gibt es das Transparenzregister: Lobbyist*innen müssen sich eintragen und ihre Ausgaben offenlegen.</p>
`,
facts: [
'Rund 12.000 Lobbyorganisationen in Brüssel',
'Konzerne, Verbände, NGOs, Gewerkschaften',
'Müssen sich im Transparenzregister eintragen',
'Können Gesetze beschleunigen oder bremsen'
],
miniSvg: () => `<svg viewBox="0 0 80 70">
<g transform="translate(20, 36)">
<circle cx="0" cy="0" r="8" fill="#e8c547"/>
<path d="M -10,4 L -10,22 L 10,22 L 10,4 Z" fill="#e8c547"/>
</g>
<g transform="translate(60, 36)">
<circle cx="0" cy="0" r="8" fill="#4a7c4e"/>
<path d="M -10,4 L -10,22 L 10,22 L 10,4 Z" fill="#4a7c4e"/>
</g>
<path d="M 30,30 L 50,30" stroke="#e8c547" stroke-width="3" stroke-linecap="round"/>
<path d="M 50,30 L 44,26 L 44,34 Z" fill="#e8c547"/>
<text x="40" y="14" text-anchor="middle" font-family="Manrope" font-weight="700" font-size="7" fill="#1f4b37">$$$</text>
</svg>`,
detailSvg: () => `<svg viewBox="0 0 580 220">
<rect width="580" height="220" fill="#1f4b37"/>
<!-- Konzern links -->
<g transform="translate(130, 110)">
<rect x="-50" y="-50" width="100" height="100" fill="#14201a" rx="6"/>
${[0,1,2].map(r => [0,1,2].map(c =>
`<rect x="${-40 + c*30}" y="${-40 + r*30}" width="20" height="20" fill="#e8c547" opacity="${0.5 + Math.random()*0.5}"/>`
).join('')).join('')}
<text x="0" y="80" text-anchor="middle" font-family="Fraunces" font-weight="700" font-size="14" fill="#e8c547">Konzern</text>
</g>
<!-- Geld-Pfeil -->
<g transform="translate(290, 110)">
<text y="-30" text-anchor="middle" font-family="Manrope" font-weight="700" font-size="22" fill="#e8c547">€€€</text>
<path d="M -70,0 L 70,0" stroke="#e8c547" stroke-width="6" stroke-linecap="round"/>
<path d="M 70,0 L 56,-10 L 56,10 Z" fill="#e8c547"/>
<text y="34" text-anchor="middle" font-family="Manrope" font-size="12" fill="#e8e4d8">Lobbying</text>
</g>
<!-- Politiker*in rechts -->
<g transform="translate(450, 110)">
<circle cx="0" cy="-20" r="20" fill="#e8c547"/>
<path d="M -28,0 L -28,50 L 28,50 L 28,0 Z" fill="#e8c547"/>
<text x="0" y="80" text-anchor="middle" font-family="Fraunces" font-weight="700" font-size="14" fill="#e8c547">Politik</text>
</g>
</svg>`
},
freiwillig: {
label: 'Freiwillige Vereinbarung',
summary: 'Statt sofort ein Gesetz zu machen, versucht die EU oft erst, dass sich die Hersteller selbst auf Standards einigen. Klappt das nicht, kommt das Gesetz.',
tags: ['Sanfter Weg', 'Kann scheitern'],
body: `
<h4>Was ist eine freiwillige Vereinbarung?</h4>
<p>Bei einer freiwilligen Vereinbarung („Memorandum of Understanding") versprechen Unternehmen einer Branche, ein bestimmtes Ziel von selbst zu erreichen ohne dass die EU ein Gesetz macht. Klingt nett, ist aber oft schwach: Wer nicht mitmacht, hat keine Konsequenzen.</p>
<h4>Warum macht die EU das?</h4>
<p>Weil Gesetze dauern (Jahre!) und teils starken Widerstand erzeugen. Eine freiwillige Vereinbarung ist schneller und politisch einfacher. Die EU greift nur dann zum Gesetz, wenn die Selbstverpflichtung nicht funktioniert.</p>
<h4>Beispiel: Die lange Geschichte des Ladekabels</h4>
<p>Schon 2009 unterschrieben fast alle Handy-Hersteller eine freiwillige Vereinbarung: Bis 2014 sollte es nur noch einen Lade-Standard geben. Apple machte nicht mit. Andere Hersteller hielten sich nur teilweise daran. Erst 2021 zog die EU die Notbremse und schlug ein verbindliches Gesetz vor die USB-C-Pflicht.</p>
`,
facts: [
'Hersteller versprechen sich selbst, ein Ziel zu erreichen',
'Schneller und einfacher als ein Gesetz',
'Aber: Wer nicht mitmacht, hat keine Strafe zu fürchten',
'Wenn es nicht funktioniert, kommt doch ein Gesetz'
],
miniSvg: () => `<svg viewBox="0 0 80 70">
<path d="M 18,18 L 62,18 L 62,52 L 18,52 Z" fill="#e8e4d8" stroke="#1f4b37" stroke-width="2"/>
<line x1="24" y1="26" x2="56" y2="26" stroke="#1f4b37" stroke-width="1.5"/>
<line x1="24" y1="32" x2="50" y2="32" stroke="#1f4b37" stroke-width="1.5"/>
<line x1="24" y1="38" x2="56" y2="38" stroke="#1f4b37" stroke-width="1.5"/>
<path d="M 30,46 q 5,-3 10,0 q 5,3 10,0" stroke="#1f4b37" stroke-width="2" fill="none"/>
</svg>`,
detailSvg: () => `<svg viewBox="0 0 580 220">
<rect width="580" height="220" fill="#1f4b37"/>
<g transform="translate(290, 110)">
<rect x="-100" y="-70" width="200" height="140" fill="#e8e4d8" rx="6"/>
<text x="0" y="-44" text-anchor="middle" font-family="Fraunces" font-weight="700" font-size="14" fill="#1f4b37">Selbstverpflichtung</text>
<line x1="-80" y1="-26" x2="80" y2="-26" stroke="#1f4b37" stroke-width="1.5"/>
<line x1="-80" y1="-12" x2="80" y2="-12" stroke="#1f4b37" stroke-width="1.5"/>
<line x1="-80" y1="2" x2="60" y2="2" stroke="#1f4b37" stroke-width="1.5"/>
<line x1="-80" y1="16" x2="80" y2="16" stroke="#1f4b37" stroke-width="1.5"/>
<path d="M -80,40 q 12,-5 24,0 q 12,5 24,0 q 12,-5 24,0 q 12,5 24,0" stroke="#1f4b37" stroke-width="2.5" fill="none"/>
<text x="0" y="60" text-anchor="middle" font-family="Manrope" font-size="11" fill="#1f4b37">Unterschriften der Hersteller</text>
</g>
</svg>`
},
/* ABLEHNUNG / SCHEITERN */
ablehnung: {
label: 'Ablehnung',
summary: 'Auch wenn eine Bürgerinitiative erfolgreich ist, kann die Kommission am Ende NEIN sagen sie muss antworten, aber nicht zustimmen.',
tags: ['Antwort ≠ Zustimmung', 'Begründung nötig'],
body: `
<h4>Wann lehnt die Kommission eine Initiative ab?</h4>
<p>Es gibt zwei Stellen, an denen die Kommission NEIN sagen kann:</p>
<h4>1. Bei der Registrierung</h4>
<p>Wenn das Thema außerhalb der EU-Zuständigkeit liegt, gegen EU-Werte verstößt oder offensichtlich missbräuchlich ist. Beispiel: „Stop Brexit" wurde abgelehnt, weil die EU einem Land den Austritt nicht verbieten darf.</p>
<h4>2. Bei der inhaltlichen Antwort</h4>
<p>Auch wenn 1 Million Unterschriften vorliegen, MUSS die Kommission antworten aber sie muss inhaltlich nicht folgen. Beispiel: Bei „Stop Vivisection" 2015 sagte die Kommission: „Wir teilen das Ziel, Tierversuche zu beenden, aber wir gehen einen anderen Weg." Damit war die Initiative inhaltlich abgelehnt.</p>
<h4>Was bringt eine abgelehnte Initiative überhaupt?</h4>
<p>Mehr, als man denkt: Sie macht Themen sichtbar. Manche „erfolglosen" Initiativen haben am Ende mehr bewirkt als formal erfolgreiche die Roaming-Abschaffung 2017 wurde von einer gescheiterten Bürgerinitiative mitangestoßen.</p>
`,
facts: [
'Die Kommission muss antworten aber nicht zustimmen',
'Bei Registrierung: Verfahrens- und Zuständigkeits-Prüfung',
'Bei der Antwort: politische Entscheidung',
'Begründung muss schriftlich offengelegt werden'
],
miniSvg: () => `<svg viewBox="0 0 80 70">
<circle cx="40" cy="35" r="22" fill="#b04a3e"/>
<line x1="28" y1="35" x2="52" y2="35" stroke="#f4f1e8" stroke-width="6" stroke-linecap="round"/>
</svg>`,
detailSvg: () => `<svg viewBox="0 0 580 220">
<rect width="580" height="220" fill="#1f4b37"/>
<g transform="translate(290, 110)">
<circle r="70" fill="#b04a3e"/>
<line x1="-40" y1="0" x2="40" y2="0" stroke="#f4f1e8" stroke-width="14" stroke-linecap="round"/>
</g>
<text x="290" y="200" text-anchor="middle" font-family="Manrope" font-size="13" fill="#e8e4d8">Verfahren beendet Initiative abgelehnt</text>
</svg>`
}
};
/* =========================================================
SZENEN-ILLUSTRATIONEN für den Storytelling-Kontext
========================================================= */
const SCENES = {
home: `
<rect width="720" height="180" fill="#e8e4d8"/>
<rect x="0" y="150" width="720" height="30" fill="#d6d1c2"/>
<!-- Fenster -->
<rect x="60" y="30" width="160" height="100" fill="#1f4b37"/>
<rect x="66" y="36" width="148" height="88" fill="#f4f1e8"/>
<path d="M 66,90 L 110,60 L 150,80 L 200,50 L 214,70 L 214,124 L 66,124 Z" fill="#4a7c4e"/>
<circle cx="186" cy="50" r="10" fill="#e8c547"/>
<line x1="140" y1="36" x2="140" y2="124" stroke="#1f4b37" stroke-width="2"/>
<line x1="66" y1="78" x2="214" y2="78" stroke="#1f4b37" stroke-width="2"/>
<!-- Schreibtisch -->
<rect x="320" y="100" width="280" height="12" fill="#1f4b37"/>
<rect x="335" y="112" width="6" height="38" fill="#1f4b37"/>
<rect x="579" y="112" width="6" height="38" fill="#1f4b37"/>
<!-- Laptop -->
<rect x="390" y="74" width="100" height="30" rx="3" fill="#1f4b37"/>
<rect x="395" y="79" width="90" height="20" fill="#e8c547"/>
<rect x="380" y="100" width="120" height="6" rx="2" fill="#1f4b37"/>
<!-- Lampe -->
<line x1="350" y1="34" x2="350" y2="100" stroke="#1f4b37" stroke-width="3"/>
<path d="M 335,16 L 365,16 L 360,34 L 340,34 Z" fill="#e8c547"/>
<!-- Pflanze -->
<rect x="630" y="124" width="20" height="20" fill="#1f4b37"/>
<path d="M 640,124 L 635,108 M 640,124 L 640,103 M 640,124 L 645,108" stroke="#4a7c4e" stroke-width="3" stroke-linecap="round"/>
`,
signatures: `
<rect width="720" height="180" fill="#e8e4d8"/>
<g transform="translate(360, 90)" opacity="0.15">
<path d="M -140,-50 Q -90,-70 -40,-60 Q 10,-75 60,-65 Q 110,-70 130,-40 Q 140,0 120,30 Q 70,55 20,50 Q -30,70 -80,50 Q -130,30 -140,-10 Z" fill="#1f4b37"/>
</g>
<g transform="translate(140, 80)">
<rect x="-3" y="6" width="100" height="80" fill="#d6d1c2" transform="rotate(-3 47 46)"/>
<rect x="0" y="0" width="100" height="80" fill="#f4f1e8" stroke="#1f4b37" stroke-width="2"/>
<line x1="12" y1="20" x2="88" y2="20" stroke="#1f4b37" stroke-width="1.5"/>
<line x1="12" y1="32" x2="70" y2="32" stroke="#1f4b37" stroke-width="1.5"/>
<line x1="12" y1="44" x2="80" y2="44" stroke="#1f4b37" stroke-width="1.5"/>
<path d="M 12,60 Q 24,54 36,60 Q 48,66 62,60" stroke="#1f4b37" stroke-width="2" fill="none"/>
</g>
${[300, 340, 380, 420, 460].map((x, i) =>
`<g transform="translate(${x}, 86)" fill="${i%2===0?'#1f4b37':'#4a7c4e'}">
<circle cx="0" cy="0" r="9"/>
<path d="M -12,11 L -12,38 L 12,38 L 12,11 Z"/>
</g>`
).join('')}
<rect x="540" y="64" width="130" height="56" rx="10" fill="#1f4b37"/>
<text x="605" y="98" text-anchor="middle" font-family="Fraunces" font-weight="700" font-size="20" fill="#e8c547">1.400.000</text>
<text x="605" y="114" text-anchor="middle" font-family="Manrope" font-size="9" fill="#e8e4d8" letter-spacing="1">UNTERSCHRIFTEN</text>
`,
bruessel: `
<rect width="720" height="180" fill="#e8e4d8"/>
<rect x="200" y="40" width="320" height="130" fill="#1f4b37"/>
${[0,1,2,3,4].map(r =>
[0,1,2,3,4,5,6,7,8,9].map(c =>
`<rect x="${214 + c*30}" y="${52 + r*22}" width="20" height="14" fill="#e8c547" opacity="${0.4 + (((r*c)%5)/8)}"/>`
).join('')
).join('')}
<rect x="340" y="138" width="40" height="32" fill="#e8e4d8"/>
<rect x="348" y="146" width="24" height="24" fill="#1f4b37"/>
<rect x="290" y="22" width="140" height="12" rx="2" fill="#4a7c4e"/>
<rect x="0" y="160" width="720" height="20" fill="#d6d1c2"/>
<circle cx="640" cy="40" r="20" fill="#e8c547"/>
`,
trilog: `
<rect width="720" height="180" fill="#e8e4d8"/>
<rect x="0" y="150" width="720" height="30" fill="#d6d1c2"/>
<ellipse cx="360" cy="120" rx="220" ry="32" fill="#1f4b37"/>
<ellipse cx="360" cy="116" rx="200" ry="26" fill="#4a7c4e"/>
<ellipse cx="360" cy="112" rx="180" ry="20" fill="#e8c547"/>
${[180, 280, 360, 440, 540].map((x,i) =>
`<g transform="translate(${x}, 88)" class="viz-figure" style="animation-delay: ${i*80}ms">
<circle cx="0" cy="-2" r="9" fill="${['#b04a3e','#4a7c4e','#1f4b37','#4a7c4e','#b04a3e'][i]}"/>
<path d="M -11,4 L -11,28 L 11,28 L 11,4 Z" fill="${['#b04a3e','#4a7c4e','#1f4b37','#4a7c4e','#b04a3e'][i]}"/>
</g>`
).join('')}
`,
success: `
<rect width="720" height="180" fill="#e8e4d8"/>
<g transform="translate(360, 90)">
${Array.from({length: 12}, (_, i) => {
const angle = (i * 30) * Math.PI / 180;
const x1 = Math.cos(angle) * 60, y1 = Math.sin(angle) * 60;
const x2 = Math.cos(angle) * 100, y2 = Math.sin(angle) * 100;
return `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="#e8c547" stroke-width="6" stroke-linecap="round"/>`;
}).join('')}
<circle r="48" fill="#e8c547"/>
<circle r="34" fill="#f4f1e8"/>
<path d="M -16,0 L -4,14 L 18,-14" fill="none" stroke="#1f4b37" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<rect x="0" y="160" width="720" height="20" fill="#d6d1c2"/>
`,
brexit_news: `
<rect width="720" height="180" fill="#e8e4d8"/>
<rect x="0" y="160" width="720" height="20" fill="#d6d1c2"/>
<g transform="translate(360, 90)">
<rect x="-130" y="-60" width="260" height="130" fill="#1f4b37" rx="6"/>
<rect x="-120" y="-50" width="240" height="110" fill="#14201a"/>
<g transform="translate(-70, 8)">
<rect x="-8" y="-30" width="16" height="38" fill="#e8c547"/>
<circle cx="0" cy="-30" r="9" fill="#e8e4d8"/>
<circle cx="0" cy="-30" r="6" fill="#1f4b37"/>
<line x1="0" y1="-30" x2="0" y2="-36" stroke="#e8c547" stroke-width="1.5"/>
<line x1="0" y1="-30" x2="4" y2="-30" stroke="#e8c547" stroke-width="1.5"/>
<path d="M -8,-44 L 0,-52 L 8,-44 Z" fill="#e8c547"/>
</g>
<rect x="-120" y="38" width="240" height="22" fill="#b04a3e"/>
<text x="0" y="54" text-anchor="middle" font-family="Manrope" font-weight="700" font-size="10" fill="#f4f1e8" letter-spacing="1.5">BREXIT</text>
<rect x="-12" y="70" width="24" height="6" fill="#1f4b37"/>
</g>
`,
usb_chaos: `
<rect width="720" height="180" fill="#e8e4d8"/>
<rect x="0" y="160" width="720" height="20" fill="#d6d1c2"/>
<rect x="80" y="120" width="560" height="14" fill="#1f4b37"/>
<g transform="translate(180, 80)">
<path d="M 0,0 Q -10,30 0,55" stroke="#e8c547" stroke-width="3" fill="none"/>
<rect x="-8" y="-12" width="16" height="14" fill="#e8c547" rx="2"/>
<text x="0" y="-2" text-anchor="middle" font-family="Manrope" font-weight="700" font-size="8" fill="#1f4b37">L</text>
</g>
<g transform="translate(260, 70)">
<path d="M 0,0 Q 8,32 0,60" stroke="#4a7c4e" stroke-width="3" fill="none"/>
<rect x="-8" y="-12" width="16" height="14" fill="#4a7c4e" rx="2"/>
<text x="0" y="-2" text-anchor="middle" font-family="Manrope" font-weight="700" font-size="8" fill="#f4f1e8">C</text>
</g>
<g transform="translate(340, 90)">
<path d="M 0,0 Q -6,22 0,42" stroke="#b04a3e" stroke-width="3" fill="none"/>
<rect x="-8" y="-12" width="16" height="14" fill="#b04a3e" rx="2"/>
<text x="0" y="-2" text-anchor="middle" font-family="Manrope" font-weight="700" font-size="8" fill="#f4f1e8">M</text>
</g>
<g transform="translate(420, 65)">
<path d="M 0,0 Q 12,34 0,68" stroke="#1f4b37" stroke-width="3" fill="none"/>
<rect x="-8" y="-12" width="16" height="14" fill="#1f4b37" rx="2"/>
<text x="0" y="-2" text-anchor="middle" font-family="Manrope" font-weight="700" font-size="8" fill="#f4f1e8">A</text>
</g>
<g transform="translate(500, 85)">
<path d="M 0,0 Q -8,26 0,46" stroke="#e8c547" stroke-width="3" fill="none"/>
<rect x="-8" y="-12" width="16" height="14" fill="#e8c547" rx="2"/>
<text x="0" y="-2" text-anchor="middle" font-family="Manrope" font-weight="700" font-size="8" fill="#1f4b37">L</text>
</g>
<g transform="translate(570, 75)">
<path d="M 0,0 Q 6,30 0,55" stroke="#4a7c4e" stroke-width="3" fill="none"/>
<rect x="-8" y="-12" width="16" height="14" fill="#4a7c4e" rx="2"/>
<text x="0" y="-2" text-anchor="middle" font-family="Manrope" font-weight="700" font-size="8" fill="#f4f1e8">C</text>
</g>
<text x="360" y="36" text-anchor="middle" font-family="Fraunces" font-weight="900" font-size="38" fill="#1f4b37" opacity="0.4">?</text>
`,
lab_animals: `
<rect width="720" height="180" fill="#e8e4d8"/>
<rect x="0" y="160" width="720" height="20" fill="#d6d1c2"/>
<!-- Labor-Tisch -->
<rect x="60" y="115" width="600" height="14" fill="#1f4b37"/>
<!-- Reagenzgläser -->
${[120, 160, 200, 240].map((x, i) =>
`<g transform="translate(${x}, 90)">
<rect x="-6" y="0" width="12" height="25" fill="#e8e4d8" stroke="#1f4b37" stroke-width="1.5"/>
<rect x="-5" y="${15 + i*2}" width="10" height="${10 - i*2}" fill="${['#e8c547','#4a7c4e','#b04a3e','#7099a0'][i]}"/>
</g>`
).join('')}
<!-- Gegenüber: Pflanzen + Petri-Schalen (Alternativen) -->
<g transform="translate(440, 95)">
<ellipse cx="0" cy="20" rx="22" ry="6" fill="#1f4b37"/>
<ellipse cx="0" cy="18" rx="20" ry="4" fill="#4a7c4e"/>
<circle cx="-6" cy="14" r="3" fill="#e8c547"/>
<circle cx="6" cy="14" r="3" fill="#e8c547"/>
<circle cx="0" cy="14" r="3" fill="#e8c547"/>
</g>
<g transform="translate(520, 95)">
<ellipse cx="0" cy="20" rx="22" ry="6" fill="#1f4b37"/>
<ellipse cx="0" cy="18" rx="20" ry="4" fill="#4a7c4e"/>
<circle cx="-6" cy="14" r="3" fill="#e8c547"/>
<circle cx="6" cy="14" r="3" fill="#e8c547"/>
</g>
<!-- Herz-Symbol oben (für Tierschutz) -->
<g transform="translate(360, 50)">
<path d="M 0,8 C -14,-8 -28,-2 -28,12 C -28,24 0,38 0,38 C 0,38 28,24 28,12 C 28,-2 14,-8 0,8 Z" fill="#b04a3e" class="viz-pulse" style="transform-origin: 0 14px"/>
</g>
`
};
/* =========================================================
SZENARIEN
Schritt-Typen:
'pick' → eine Karte aus Pool wählen (single slot)
'sentence' → Lückentext mit mehreren Slots
'animation' → SVG-Animation läuft, dann Weiter-Button
'multichoice' → 4 Buttons mit Texten direkt (keine Karten)
========================================================= */
const SCENARIOS = {
cage_age: {
id: 'cage_age',
icon: '🐔',
title: 'Käfige abschaffen!',
teaser: 'Eine Bürgerinitiative will Käfighaltung in der EU verbieten.',
badge: 'Erfolg',
actor: { name: 'Lena', role: '14 Jahre, schaut genau hin' },
intro: 'Du heißt Lena, bist 14 und hast in einer Reportage gesehen, wie Hennen in winzigen Käfigen leben müssen. Beim Abendessen erzählst du deinen Eltern davon sie hören zu. Am nächsten Tag findet ihr gemeinsam die Europäische Bürgerinitiative „End the Cage Age", gestartet von der britischen Tierschutzorganisation <strong>Compassion in World Farming (CIWF)</strong>. Deine Eltern unterschreiben sofort. Du willst jetzt verstehen, wie es weitergeht.',
steps: [
/* SCHRITT 1 PICK: Welches Werkzeug nutzen die NGOs? */
{
type: 'pick',
scene: 'home',
narration: 'Auf der Webseite der Initiative liest du: Über 170 Tierschutzorganisationen aus ganz Europa unterstützen die Aktion. Aber wie konnten sie das überhaupt formal starten? Ein einzelner Verein darf eine EBI nicht alleine anmelden.',
question: 'Welches Werkzeug nutzen CIWF und die anderen Vereine?',
pool: ['buergerinitiative', 'brief', 'demo', 'warten'],
correct: 'buergerinitiative',
feedbackCorrect: 'Genau! Eine Europäische Bürgerinitiative und für die Anmeldung braucht es ein Bürgerkomitee aus mindestens 7 Personen aus 7 EU-Ländern. CIWF hat das organisiert, ergänzt durch Tierschützer*innen wie Mahi Klosterhalfen von der Albert Schweitzer Stiftung.',
feedbackByCard: {
brief: { isMuelleimer: false, text: 'Ein einzelner Brief reicht nicht. Welches offizielle Werkzeug nutzt eine NGO-Allianz, um die EU-Kommission zum Handeln aufzufordern?' },
demo: { isMuelleimer: false, text: 'Demos gab es auch viele. Aber sie sind kein offizieller EU-Mechanismus. Welches förmliche Werkzeug haben Bürger*innen?' },
warten: { isMuelleimer: true, text: 'Wer wartet, bewirkt nichts. Demokratie funktioniert nur, wenn Menschen aktiv werden.' }
}
},
/* SCHRITT 2 SENTENCE: Anforderungen einer Bürgerinitiative */
{
type: 'sentence',
scene: 'signatures',
narration: 'Du verstehst jetzt: Die Bürgerinitiative ist das richtige Werkzeug. Aber welche genauen Spielregeln gelten dafür? Bau den Satz mit den richtigen Karten zusammen.',
question: 'Wie lauten die Spielregeln einer Bürgerinitiative?',
template: 'Eine Europäische Bürgerinitiative braucht mindestens {0} Unterschriften aus mindestens {1} EU-Ländern.',
slots: [
{ accepts: 'menge', correct: 'zahl_1mio' },
{ accepts: 'laender', correct: 'laender_7' }
],
poolGroups: [
{ label: 'Anzahl Unterschriften', cards: ['zahl_100k', 'zahl_1mio', 'zahl_10mio'], slotKey: 'menge' },
{ label: 'Aus wie vielen Ländern?', cards: ['laender_1', 'laender_7', 'laender_27'], slotKey: 'laender' }
],
feedbackCorrect: 'Genau richtig! 1 Million Unterschriften aus mindestens 7 Ländern das ist die Hürde, damit die Kommission die Initiative ernst nehmen MUSS.',
wrongIsMuelleimer: true,
feedbackWrong: 'Die Zahlen passen noch nicht. Schau dir die Karten genauer an tippe auf die Detail-Ansicht (i-Symbol).',
funFact: {
emoji: '🏟️',
title: '1 Million — wie viel ist das?',
text: '1 Mio EBI-Unterschriften — das sind etwa so viele Menschen wie ganz <strong>Köln</strong> hat (1,07 Mio) oder <strong>München</strong> (1,49 Mio). „End the Cage Age" hat 1,4 Mio gesammelt — also ungefähr die Einwohnerzahl von <strong>München</strong>.'
}
},
/* SCHRITT 3 PICK: Wer schreibt den Gesetzesvorschlag? */
{
type: 'pick',
scene: 'bruessel',
narration: 'Nach einem Jahr ist es geschafft: 1.397.113 Unterschriften aus 18 Ländern mehr als nötig! Auch deine Eltern haben unterschrieben. Die Kommission lädt das Bürgerkomitee zu einer öffentlichen Anhörung im EU-Parlament ein. Aber dann kommt die entscheidende Frage: Wer darf jetzt eigentlich den Gesetzestext schreiben?',
milestone: {
title: '1,4 Millionen Stimmen!',
text: '<strong>End the Cage Age</strong> sammelte 2018 2019 echte 1.397.113 Unterschriften aus 18 Ländern. Damit gehört sie zu den vier erfolgreichsten Bürgerinitiativen aller Zeiten. Die Kommission MUSS sich nun befassen.'
},
question: 'Wer darf den offiziellen Gesetzestext einbringen?',
pool: ['kommission', 'parlament', 'ministerrat', 'buergerinitiative'],
correct: 'kommission',
feedbackCorrect: 'Genau! Das formelle Initiativrecht liegt allein bei der EU-Kommission. CIWF und das Bürgerkomitee können sie nur AUFFORDERN, einen Vorschlag zu machen. Schreiben muss ihn die Kommission selbst.',
feedbackByCard: {
parlament: { isMuelleimer: true, text: 'Das Parlament beschließt mit, kann aber die Kommission nur AUFFORDERN. Selbst Gesetze schreiben darf es nicht. Wer hat das formelle Initiativrecht?' },
ministerrat: { isMuelleimer: true, text: 'Der Ministerrat hat ebenfalls nur ein Aufforderungsrecht. Wer darf in der EU offiziell Gesetzestexte einbringen?' },
buergerinitiative:{ isMuelleimer: true, text: 'Auch CIWF mit allen 170 Vereinen darf keinen Gesetzestext einbringen. Sie können die Kommission nur auffordern. Wer schreibt den Text?' }
}
},
/* SCHRITT 4 SENTENCE: Wer beschließt? */
{
type: 'sentence',
scene: 'trilog',
narration: 'Die Kommission verspricht am 30. Juni 2021, einen Gesetzesvorschlag zu erarbeiten. Wenn sie ihn einbringt, geht es weiter im normalen Gesetzgebungsverfahren. Wer muss zustimmen?',
question: 'Wer muss einem EU-Gesetz zustimmen?',
template: 'Diese zwei Institutionen müssen einem EU-Gesetz zustimmen: {0} und {1}.',
slots: [
{ accepts: 'inst', correct: 'parlament' },
{ accepts: 'inst', correct: 'ministerrat' }
],
poolGroups: [
{ label: 'Welche Institutionen?', cards: ['parlament', 'ministerrat', 'europRat', 'volksabstimmung'], slotKey: 'inst' }
],
slotsInterchangeable: true,
correctSet: ['parlament', 'ministerrat'],
feedbackCorrect: 'Richtig! Mitentscheidungsverfahren. Parlament und Ministerrat sind gleichberechtigte Gesetzgeber. Beide müssen JA sagen. Im Juni 2021 forderte das Parlament mit 558:37 Stimmen ein Käfigverbot bis 2027.',
wrongIsMuelleimer: true,
feedbackWrong: 'Das passt nicht. Der Europäische Rat (Regierungschefs) macht keine Gesetze nur die großen Linien. Eine Volksabstimmung gibt es bei EU-Gesetzen nicht.'
}
],
end: {
type: 'mixed',
title: 'Du hast es geschafft …',
subtitle: 'Das Bürgerkomitee hat den Weg von der Idee bis zur Mitentscheidung richtig durchlaufen. Aber: Auch der lehrbuchmäßig richtige Weg garantiert kein Gesetz.',
realStory: 'Die Bürgerinitiative „End the Cage Age" wurde von der britischen Organisation Compassion in World Farming gestartet und sammelte 2018 2019 rund 1,4 Millionen Unterschriften aus 18 EU-Ländern. Im Juni 2021 versprach die Kommission feierlich, einen Gesetzesvorschlag bis Ende 2023 vorzulegen. Doch bis heute (Mai 2026) ist das nicht passiert. Das Bürgerkomitee darunter Mahi Klosterhalfen von der Albert Schweitzer Stiftung klagt deshalb seit März 2024 vor dem Europäischen Gerichtshof. Im März 2026 fand die mündliche Verhandlung statt, das Urteil steht noch aus. Der Fall zeigt: Auch wenn alle demokratischen Schritte richtig gemacht werden, garantiert das noch kein Gesetz. Demokratie ist ein langer Atem.'
}
},
/* ===================================================
SZENARIO 2: USB-C Top-Down ohne Bürgerinitiative
=================================================== */
usb_c: {
id: 'usb_c',
icon: '🔌',
title: 'Ein Kabel für alle',
teaser: 'Wie kam es zur EU-weiten USB-C-Pflicht ganz ohne Bürgerinitiative?',
badge: 'Erfolg',
actor: { name: 'Marta', role: '13 Jahre, hat ein neues Handy bekommen' },
intro: 'Du heißt Marta, bist 13 und hast zu Weihnachten ein neues Handy bekommen aber dein altes Ladekabel passt nicht mehr! Genervt fragst du deine Eltern: „Warum gibt es so viele verschiedene Stecker?" Sie zeigen dir einen Zeitungsartikel: Die EU-Kommission hat 2021 vorgeschlagen, dass alle Handys denselben Stecker haben sollen USB-C. Du willst verstehen, wie das funktioniert.',
steps: [
/* SCHRITT 1 PICK: Wer hat angestoßen? */
{
type: 'pick',
scene: 'usb_chaos',
narration: 'Du liest weiter: Hier gab es keine Bürgerinitiative. Trotzdem ist ein Gesetz entstanden. Wer hat den ersten Anstoß gegeben?',
question: 'Wer hat den USB-C-Vorschlag offiziell eingebracht?',
pool: ['kommission', 'parlament', 'ministerrat', 'buergerinitiative'],
correct: 'kommission',
feedbackCorrect: 'Genau! Das formelle Initiativrecht liegt bei der EU-Kommission. Auch ohne Bürgerinitiative kann sie selbst ein Gesetz vorschlagen. Hier waren Kommissarin Margrethe Vestager und Binnenmarkt-Kommissar Thierry Breton federführend.',
feedbackByCard: {
parlament: { isMuelleimer: false, text: 'Das Parlament hat 2014 zwar gefordert, dass etwas passiert aber selbst kann es kein Gesetz einbringen. Wer hatte das Initiativrecht?' },
ministerrat: { isMuelleimer: false, text: 'Der Ministerrat beschließt mit, hat aber kein Initiativrecht. Wer darf in der EU offiziell Gesetzestexte einbringen?' },
buergerinitiative:{ isMuelleimer: false, text: 'Es gab keine Bürgerinitiative zu USB-C. Wer wurde stattdessen tätig?' }
}
},
/* SCHRITT 2 PICK: Sanfter Weg vor dem Gesetz */
{
type: 'pick',
scene: 'usb_chaos',
narration: 'Im Artikel steht: Schon 2009 hatte die Kommission versucht, eine einheitliche Lösung zu erreichen ohne Gesetz. Fast alle Hersteller unterschrieben damals. Apple aber nicht. Wie nennt man diesen sanften Weg, bevor man eine Verpflichtung macht?',
question: 'Welcher Weg wird oft VOR einem Gesetz versucht?',
pool: ['freiwillig', 'volksabstimmung', 'demo', 'eu_gesetz'],
correct: 'freiwillig',
feedbackCorrect: 'Richtig! Die EU bevorzugt zunächst freiwillige Vereinbarungen. 2009 unterschrieben fast alle Hersteller eine Selbstverpflichtung. Apple machte nicht mit und nach Jahren war klar: Es klappt nicht ohne Gesetz.',
feedbackByCard: {
volksabstimmung: { isMuelleimer: false, text: 'Volksabstimmungen gibt es bei EU-Gesetzen nicht. Es gibt einen sanfteren, branchen-internen Weg welchen?' },
demo: { isMuelleimer: false, text: 'Demonstrationen sind zwar erlaubt, aber kein offizieller Schritt vor einem Gesetz. Welcher freiwillige Weg wird oft erst probiert?' },
eu_gesetz: { isMuelleimer: false, text: 'Ein Gesetz ist die HARTE Variante. Was kommt oft erst davor ein sanfterer Versuch?' }
}
},
/* SCHRITT 3 PICK: Lobbyismus erklären */
{
type: 'pick',
scene: 'bruessel',
milestone: {
title: 'Vorschlag eingebracht!',
text: 'Im <strong>September 2021</strong> schlug die EU-Kommission die USB-C-Pflicht offiziell vor. Apple lehnte das öffentlich ab und schickte Lobbyist*innen nach Brüssel.'
},
narration: 'Apple ist gegen die USB-C-Pflicht und investiert viel Geld, um sie zu verhindern. Auch Verbraucherschutz-Organisationen mischen mit. Wie nennt man diese organisierte Einflussnahme?',
question: 'Was machen Apple und andere Konzerne hier?',
pool: ['lobby', 'volksabstimmung', 'demo', 'petition'],
correct: 'lobby',
feedbackCorrect: 'Genau! Lobbyismus ist in einer Demokratie erlaubt Konzerne wie Apple und auch NGOs versuchen, EU-Gesetze in ihrem Sinne zu beeinflussen. In Brüssel sind über 12.000 Lobby-Organisationen registriert.',
feedbackByCard: {
volksabstimmung: { isMuelleimer: false, text: 'Volksabstimmungen gibt es bei EU-Gesetzen nicht. Wie nennt man die organisierte Beeinflussung von Politik durch Konzerne?' },
demo: { isMuelleimer: false, text: 'Demonstrationen sind ein Bürger-Werkzeug. Wenn Konzerne mit Geld und Treffen Politiker*innen beeinflussen wie heißt das?' },
petition: { isMuelleimer: false, text: 'Eine Petition ist ein offizielles Bürger-Werkzeug. Konzerne nutzen einen anderen, organisierten Weg welchen?' }
},
funFact: {
emoji: '🏢',
title: 'Lobby-Hauptstadt Brüssel',
text: 'In Brüssel sind über <strong>12.000 Lobby-Organisationen</strong> registriert — mehr als in jeder anderen Stadt Europas, fast so viele wie in Washington D.C. Allein Apple hat dort mehrere ständige Vertreter. Auch Umweltverbände, Verbraucherschutz und Gewerkschaften lobbyieren — Lobby ist nicht nur böse.'
}
},
/* SCHRITT 4 SENTENCE: Beschluss durch beide Gesetzgeber */
{
type: 'sentence',
scene: 'trilog',
narration: 'Trotz Apples Widerstand verhandeln Parlament und Ministerrat. Im Juni 2022 einigen sie sich. Wer muss bei einem EU-Gesetz zustimmen?',
question: 'Wer muss einem EU-Gesetz zustimmen?',
template: 'Diese zwei Institutionen müssen einem EU-Gesetz zustimmen: {0} und {1}.',
slots: [
{ accepts: 'inst', correct: 'parlament' },
{ accepts: 'inst', correct: 'ministerrat' }
],
poolGroups: [
{ label: 'Welche Institutionen?', cards: ['parlament', 'ministerrat', 'europRat', 'volksabstimmung'], slotKey: 'inst' }
],
slotsInterchangeable: true,
correctSet: ['parlament', 'ministerrat'],
feedbackCorrect: 'Richtig! Mitentscheidungsverfahren. Im Oktober 2022 wurde das Gesetz beschlossen mit 602 Ja-Stimmen im Parlament gegen nur 13 Nein-Stimmen.',
wrongIsMuelleimer: true,
feedbackWrong: 'Das passt nicht. Der Europäische Rat (Regierungschefs) macht keine Gesetze. Eine Volksabstimmung gibt es bei EU-Gesetzen nicht.'
}
],
end: {
type: 'success',
title: 'USB-C ist Pflicht!',
subtitle: 'Seit dem 28. Dezember 2024 müssen alle neuen Smartphones, Tablets und Kopfhörer in der EU einen USB-C-Anschluss haben.',
realStory: 'Die EU-Kommission federführend Binnenmarkt-Kommissar Thierry Breton schlug die USB-C-Pflicht im September 2021 vor. Nach intensiven Verhandlungen einigten sich Parlament und Rat im Juni 2022 das Gesetz wurde im Oktober 2022 beschlossen. Apple, das jahrelang gegen die Vereinheitlichung gekämpft hatte, stellte im September 2023 mit dem iPhone 15 erstmals USB-C vor drei Monate vor Inkrafttreten der Pflicht. Die Geschichte zeigt: Auch ohne Bürgerinitiative kann die EU Verbraucherschutz durchsetzen sogar gegen den Widerstand des größten Konzerns der Welt.'
}
},
/* ===================================================
SZENARIO 3: Stop Brexit Verfahrensfehler/Sackgasse
=================================================== */
stop_brexit: {
id: 'stop_brexit',
icon: '🇬🇧',
title: 'Stop Brexit!',
teaser: 'Eine echte Bürgerinitiative wollte 2017 den Brexit stoppen. Was passierte?',
badge: 'Abgelehnt',
badgeType: 'failed',
actor: { name: 'Tom', role: '15 Jahre, Familie aus UK in Belgien' },
intro: 'Du heißt Tom, bist 15 und Brite. Deine Familie lebt seit Jahren in Brüssel, weil dein Vater bei einer EU-Behörde arbeitet. Im Juni 2016 hat die Mehrheit in Großbritannien für den Brexit gestimmt deine Familie ist verzweifelt. Im Frühjahr 2017 erfährt dein Vater, dass britische Bürger*innen in der EU eine Bürgerinitiative starten wollen: „Stop Brexit". Er und deine Mutter überlegen, ob sie sich beteiligen sollen. Du willst verstehen, ob das überhaupt funktionieren kann.',
steps: [
/* SCHRITT 1 PICK: Welcher Weg? */
{
type: 'pick',
scene: 'brexit_news',
narration: 'Beim Abendessen recherchiert ihr gemeinsam. Welches stärkste demokratische Werkzeug haben EU-Bürger*innen?',
question: 'Welches Werkzeug ist das stärkste für Bürger*innen?',
pool: ['buergerinitiative', 'brief', 'demo', 'petition'],
correct: 'buergerinitiative',
feedbackCorrect: 'Richtig! Eine Europäische Bürgerinitiative ist das stärkste Werkzeug. Genau diesen Weg gingen die Aktivist*innen 2017. Aber dabei gab es ein Problem …',
feedbackByCard: {
brief: { isMuelleimer: false, text: 'Ein Brief allein ändert wenig. Welches förmliche Bürger-Werkzeug hat die größte Wirkung in der EU?' },
demo: { isMuelleimer: false, text: 'Demonstrationen helfen, sind aber kein offizielles Werkzeug. Welches stärkere gibt es?' },
petition: { isMuelleimer: false, text: 'Eine Petition ans EU-Parlament hat geringes Gewicht. Was wäre stärker?' }
}
},
/* SCHRITT 2 PICK: Was prüft die Kommission? */
{
type: 'pick',
scene: 'bruessel',
narration: 'Das Bürgerkomitee reicht den Antrag bei der Kommission ein. Bevor irgendeine Unterschrift gesammelt werden darf, muss die Kommission den Antrag prüfen. Was schaut sie sich zuerst an?',
question: 'Welche Karte erklärt das wichtigste Prüfkriterium?',
pool: ['eu_zustaendig', 'zahl_1mio', 'mitentscheidung', 'eu_gesetz'],
correct: 'eu_zustaendig',
feedbackCorrect: 'Genau! Die Kommission prüft zuerst die Zuständigkeit: Darf die EU dieses Thema überhaupt regeln? Bei „Stop Brexit" lautet die Antwort … nein.',
feedbackByCard: {
zahl_1mio: { isMuelleimer: false, text: 'Die 1 Million Unterschriften kommen erst SPÄTER. Vorher prüft die Kommission etwas anderes was?' },
mitentscheidung: { isMuelleimer: false, text: 'Mitentscheidung ist viel später relevant. Die ERSTE Frage der Kommission ist eine andere welche?' },
eu_gesetz: { isMuelleimer: false, text: 'Erst muss geprüft werden, ob ein EU-Gesetz überhaupt möglich ist. Wonach richtet sich das?' }
},
funFact: {
emoji: '🚫',
title: 'Was die EU NICHT darf',
text: 'Die EU darf nicht alles regeln. Bildung, Familie, Religion, Steuersätze, Militär — das bleibt bei den Mitgliedsstaaten. Über ein <strong>Drittel</strong> aller eingereichten Bürgerinitiativen scheitert schon an dieser ersten Hürde — sie betreffen Themen, für die die EU schlicht nicht zuständig ist.'
}
},
/* SCHRITT 3 SENTENCE: Verfahrensfehler erklären */
{
type: 'sentence',
scene: 'brexit_news',
narration: 'Wenige Wochen später kommt die Antwort: Ein Brief von der Kommission. Sie verweigert die Registrierung der Initiative und begründet das ausführlich. Bau den Satz mit den richtigen Karten zusammen.',
question: 'Wie endet die Initiative — und warum?',
template: 'Es kommt zur {0} — der Grund ist die fehlende {1} (Artikel 50 EU-Vertrag).',
slots: [
{ accepts: 'was', correct: 'ablehnung' },
{ accepts: 'grund', correct: 'eu_zustaendig' }
],
poolGroups: [
{ label: 'Was passiert mit der Initiative?', cards: ['ablehnung', 'mitentscheidung', 'eu_gesetz'], slotKey: 'was' },
{ label: 'Aus welchem Grund?', cards: ['eu_zustaendig', 'lobby', 'volksabstimmung'], slotKey: 'grund' }
],
feedbackCorrect: 'Genau! Artikel 50 des EU-Vertrags sagt klar: Jeder Mitgliedstaat darf nach seinen eigenen Verfassungsregeln austreten. Die EU darf das nicht verhindern egal, wie viele Bürger*innen das fordern.',
wrongIsMuelleimer: true,
feedbackWrong: 'Die Karten passen so nicht. Lies dir „EU-Zuständigkeit" und „Ablehnung" noch einmal durch das sind die Schlüssel zum Verständnis.'
}
],
end: {
type: 'rejected',
title: 'Im Mülleimer mit Recht?',
subtitle: 'Die Initiative wurde nicht einmal zur Unterschriftensammlung zugelassen. Aber war das wirklich falsch?',
realStory: 'Die echte Initiative „Stop Brexit" wurde am 12. September 2017 von der EU-Kommission abgelehnt. Begründung: Der EU-Vertrag (Artikel 50) erlaubt jedem Mitgliedstaat ausdrücklich den Austritt und zwar nach den eigenen nationalen Regeln. Die EU hat schlicht nicht das Recht, einem Land den Austritt zu verbieten. Auch eine zweite Initiative für ein „EU-weites Brexit-Referendum" wurde 2018 aus dem gleichen Grund abgelehnt. Der Fall zeigt: Die EU ist kein Bundesstaat, sondern ein Bündnis souveräner Staaten. Manche Themen kann man nicht einfach „nach oben" delegieren auch wenn man sich noch so dafür einsetzt.'
}
},
/* ===================================================
SZENARIO 4: Stop Vivisection erfolgreiche Initiative,
aber inhaltliche Ablehnung durch die Kommission
=================================================== */
stop_vivisection: {
id: 'stop_vivisection',
icon: '🐭',
title: 'Stop Vivisection',
teaser: '1,2 Mio Unterschriften gegen Tierversuche aber die Kommission sagte trotzdem Nein.',
badge: 'Abgelehnt',
badgeType: 'failed',
actor: { name: 'Sara', role: '15 Jahre, ihre Mutter ist Biologin' },
intro: 'Du heißt Sara, bist 15. Deine Mutter arbeitet als Biologin und ist Teil eines Vereins, der Alternativen zu Tierversuchen erforscht. 2013 erzählt sie dir abends vom italienischen Verein <strong>Equivita</strong> und einer Forscherin namens <strong>Susanna Penco</strong>: Sie haben eine Europäische Bürgerinitiative gestartet „Stop Vivisection". Sie fordern ein vollständiges Verbot aller Tierversuche in der EU. Du bittest deine Mutter, mit dir zu unterschreiben.',
steps: [
/* SCHRITT 1 PICK: Anspruch der Initiative */
{
type: 'pick',
scene: 'lab_animals',
narration: 'Du verfolgst die Kampagne mit. Equivita und mehrere Tierschutzvereine bilden das Bürgerkomitee, Susanna Penco hat den Vorsitz. Welches demokratische Werkzeug nutzen sie?',
question: 'Wie heißt das Werkzeug, das hier eingesetzt wird?',
pool: ['buergerinitiative', 'petition', 'brief', 'volksabstimmung'],
correct: 'buergerinitiative',
feedbackCorrect: 'Richtig! Die Europäische Bürgerinitiative ist das passende Werkzeug. Equivita und Cruelty Free Europe organisieren die Sammelphase über drei Jahre.',
feedbackByCard: {
petition: { isMuelleimer: false, text: 'Eine Petition wäre möglich, aber viel weniger schlagkräftig. Welches Werkzeug ist stärker?' },
brief: { isMuelleimer: false, text: 'Ein Brief allein erreicht selten viel. Welches Werkzeug bringt die EU wirklich zum Reagieren?' },
volksabstimmung: { isMuelleimer: true, text: 'Volksabstimmungen gibt es bei EU-Gesetzen nicht. Welches richtige Werkzeug haben Bürger*innen?' }
}
},
/* SCHRITT 2 SENTENCE: Hürde überschritten */
{
type: 'sentence',
scene: 'signatures',
milestone: {
title: '1,17 Millionen Stimmen!',
text: 'Drei Jahre Kampagne, intensives Sammeln in vielen Ländern die Initiative überschreitet die nötige Hürde deutlich. Auch deine Mutter und du habt mit unterschrieben.'
},
narration: 'Nach drei Jahren Kampagne sind die Unterschriften zusammen. Die Kommission MUSS sich jetzt mit dem Anliegen beschäftigen. Welche Schwellen wurden überwunden?',
question: 'Welche Hürden hat „Stop Vivisection" überwunden?',
template: 'Die Initiative sammelte mehr als {0} gültige Unterschriften aus mindestens {1} EU-Ländern.',
slots: [
{ accepts: 'menge', correct: 'zahl_1mio' },
{ accepts: 'laender', correct: 'laender_7' }
],
poolGroups: [
{ label: 'Wie viele Unterschriften?', cards: ['zahl_100k', 'zahl_1mio', 'zahl_10mio'], slotKey: 'menge' },
{ label: 'Aus wie vielen Ländern?', cards: ['laender_1', 'laender_7', 'laender_27'], slotKey: 'laender' }
],
feedbackCorrect: 'Genau! 1.173.130 Unterschriften aus mehr als 7 Ländern. Damit ist alles formal in Ordnung. Die Kommission MUSS jetzt antworten aber Achtung: Antworten heißt nicht zustimmen.',
wrongIsMuelleimer: false,
feedbackWrong: 'Die Zahlen passen nicht. Schau dir die Karten zur Bürgerinitiative noch einmal an.',
funFact: {
emoji: '⏰',
title: 'Die 6-Monats-Frist',
text: 'Sobald eine Initiative die Hürden geschafft hat, hat die Kommission <strong>genau 6 Monate</strong> Zeit, schriftlich zu antworten. Ein Versprechen reicht nicht — sie muss ihre Entscheidung öffentlich begründen. Was sie aber NICHT muss: dem Anliegen folgen.'
}
},
/* SCHRITT 3 PICK: Verpflichtung der Kommission */
{
type: 'pick',
scene: 'bruessel',
narration: 'Am 11. Mai 2015 wird Susanna Penco mit dem Bürgerkomitee von Vize-Kommissionspräsident Jyrki Katainen empfangen. Eine öffentliche Anhörung im EU-Parlament findet statt. Die Kommission MUSS innerhalb von sechs Monaten begründet antworten — und hat dabei zwei Wege: einen Gesetzesvorschlag einbringen (positiver Weg) oder ablehnen.',
question: 'Welchen der beiden Wege wählte die Kommission im Fall Stop Vivisection?',
pool: ['eu_gesetz', 'ablehnung', 'volksabstimmung', 'mitentscheidung'],
correct: 'ablehnung',
feedbackCorrect: 'Genau. Die Kommission hatte zwei Optionen: einen Gesetzesvorschlag einbringen (positiver Weg) oder ablehnen. Sie wählte am 3. Juni 2015 die Ablehnung — mit dem Hinweis, dass die bestehende Tierversuchs-Richtlinie ausreiche. Innerhalb von sechs Monaten begründet zu antworten ist Pflicht — wie sie antwortet, bleibt ihr überlassen.',
feedbackByCard: {
eu_gesetz: { isMuelleimer: false, text: 'Das wäre der positive Weg gewesen — Stop Vivisection wollte das. Aber so ist es nicht gekommen. Was hat die Kommission stattdessen getan?' },
mitentscheidung: { isMuelleimer: false, text: 'Mitentscheidung kommt erst, WENN ein Gesetzesvorschlag da ist. Welchen Weg wählte die Kommission davor?' },
volksabstimmung: { isMuelleimer: true, text: 'Volksabstimmungen gibt es bei EU-Gesetzen nicht. Welche zwei echten Wege hat die Kommission?' }
}
},
/* SCHRITT 4 SENTENCE: Die Antwort */
{
type: 'sentence',
scene: 'lab_animals',
narration: 'Am 3. Juni 2015 verkündet die Kommission ihre Antwort: „Wir teilen das Ziel, Tierversuche zu beenden aber wir gehen einen anderen Weg." Konkret: Die Kommission will die bestehende Tierversuchs-Richtlinie (2010/63/EU) NICHT ändern. Was bedeutet das?',
question: 'Wie heißt das Ergebnis obwohl alles formal richtig lief?',
template: 'Trotz Erfolg bei Unterschriften gibt es eine inhaltliche {0}, weil die Kommission ihr Initiativrecht nutzt und sich gegen ein neues {1} entscheidet.',
slots: [
{ accepts: 'ergeb', correct: 'ablehnung' },
{ accepts: 'art', correct: 'eu_gesetz' }
],
poolGroups: [
{ label: 'Was ist das Ergebnis?', cards: ['ablehnung', 'mitentscheidung', 'volksabstimmung'], slotKey: 'ergeb' },
{ label: 'Was wird nicht gemacht?', cards: ['eu_gesetz', 'demo', 'petition'], slotKey: 'art' }
],
feedbackCorrect: 'Genau. Das Ergebnis ist eine inhaltliche Ablehnung. Die Kommission begründet sie ausführlich das muss sie. Aber das Ende ist: kein neues Gesetz, kein Tierversuchs-Verbot.',
wrongIsMuelleimer: false,
feedbackWrong: 'Die Karten passen so nicht. Schau dir die „Ablehnung"-Karte und „EU-Gesetz" genauer an.'
}
],
end: {
type: 'rejected',
title: 'Erfolg im Verfahren Niederlage im Inhalt',
subtitle: '1,17 Millionen Stimmen, alle formalen Hürden geschafft und am Ende doch ein Nein.',
realStory: 'Die Bürgerinitiative „Stop Vivisection" wurde 2012 vom italienischen Verein Equivita und mehreren Partnerorganisationen gestartet. Vorsitzende des Bürgerkomitees war die italienische Forscherin Susanna Penco. Bis März 2015 sammelte die Initiative 1.173.130 Unterschriften. Am 3. Juni 2015 verkündete die Kommission ihre Antwort: Sie teile das Ziel, Tierversuche zu beenden verfolge aber einen anderen Weg. Die geltende Tierversuchs-Richtlinie 2010/63/EU wurde NICHT geändert. Eine spätere Initiative „Save Cruelty Free Cosmetics" (2022, 1,2 Mio Stimmen) hatte mehr Erfolg: Die Kommission versprach einen Fahrplan zur schrittweisen Abschaffung von Tierversuchen bei Chemikalien-Tests. Lehre: Eine Bürgerinitiative kann ein Thema sichtbar machen aber wer das letzte Wort hat, ist die Kommission.'
}
},
/* ===================================================
SZENARIO 5: Roaming abschaffen — langer Erfolgsweg
=================================================== */
roaming: {
id: 'roaming',
icon: '📶',
title: 'Roaming abschaffen',
teaser: 'Warum kostet Handynutzung im EU-Ausland nichts mehr extra?',
badge: 'Erfolg',
actor: { name: 'Ben', role: '14 Jahre, fährt im Sommer nach Italien' },
intro: 'Du heißt Ben, bist 14. Im Sommer fahrt ihr mit dem Auto nach Italien — wie jedes Jahr. Auf dem Weg über die Grenze schickst du wie selbstverständlich Fotos an deine Freundin. Erst zu Hause erzählt dir dein Vater: „Vor zehn Jahren hätten wir dafür 200 Euro Roaming-Gebühr zahlen müssen." Du willst wissen, wer dafür gesorgt hat, dass das nicht mehr so ist.',
steps: [
/* SCHRITT 1 — PICK: Wer hat angestoßen? */
{
type: 'pick',
scene: 'bruessel',
narration: 'Vor 2007 zahlten Reisende riesige Beträge fürs Telefonieren im EU-Ausland — bis zu 5 Euro pro Minute. Die Mobilfunk-Konzerne verdienten gut daran. Wer konnte das ändern?',
question: 'Wer brachte das erste Roaming-Gesetz auf den Weg?',
pool: ['kommission', 'parlament', 'buergerinitiative', 'volksabstimmung'],
correct: 'kommission',
feedbackCorrect: 'Genau! Die EU-Kommission — federführend Telekommunikations-Kommissarin Viviane Reding — schlug 2007 die erste Roaming-Verordnung vor. Hier gab es keine Bürgerinitiative. Die Kommission selbst sah das Marktversagen.',
feedbackByCard: {
parlament: { isMuelleimer: false, text: 'Das Parlament hat das Gesetz später mit beschlossen — aber selbst einbringen darf es nichts. Wer hat das Initiativrecht?' },
buergerinitiative:{ isMuelleimer: false, text: 'Es gab keine Bürgerinitiative zu Roaming. Wer wurde stattdessen tätig?' },
volksabstimmung: { isMuelleimer: true, text: 'Volksabstimmungen gibt es bei EU-Gesetzen nicht. Wer hat das Initiativrecht?' }
}
},
/* SCHRITT 2 — PICK: Sanfte Variante zuerst? */
{
type: 'pick',
scene: 'bruessel',
narration: 'Kommissarin Reding versuchte zuerst, die Mobilfunk-Konzerne zu freiwilligen Preissenkungen zu bewegen. Das funktionierte — aber nur ein bisschen. Wie nennt man diesen sanften Weg, bevor man ein hartes Gesetz macht?',
question: 'Welcher Weg wird oft VOR einem Gesetz versucht?',
pool: ['freiwillig', 'demo', 'eu_gesetz', 'volksabstimmung'],
correct: 'freiwillig',
feedbackCorrect: 'Richtig! Eine freiwillige Selbstverpflichtung der Konzerne. Sie reichte aber nicht — die Preise blieben hoch. Erst die EU-Verordnung 2007 zwang die Konzerne wirklich, die Gebühren zu senken.',
feedbackByCard: {
demo: { isMuelleimer: false, text: 'Demos sind möglich, aber nicht der Standard-Weg vor einem Gesetz. Was wird oft erst probiert?' },
eu_gesetz: { isMuelleimer: false, text: 'Das Gesetz ist die HARTE Variante. Was kommt oft erst davor?' },
volksabstimmung: { isMuelleimer: true, text: 'Volksabstimmungen gibt es bei EU-Gesetzen nicht. Welcher freiwillige Weg ist üblich?' }
},
funFact: {
emoji: '📲',
title: '5 € pro Minute — heute kaum vorstellbar',
text: 'Vor 2007 kostete eine Sprachminute aus dem EU-Ausland bis zu <strong>5 €</strong>, eine SMS bis zu 1 €, und 1 MB Daten konnte über <strong>10 €</strong> verschlingen. Eine Urlaubswoche mit normalem Handygebrauch = mehrere Hundert Euro extra auf der Rechnung. Die Roaming-Verordnung sparte den EU-Bürger*innen seit 2017 geschätzt <strong>9 Mrd € pro Jahr</strong>.'
}
},
/* SCHRITT 3 — SENTENCE: Wer beschließt? */
{
type: 'sentence',
scene: 'trilog',
milestone: {
title: 'Schritt für Schritt',
text: '<strong>2007:</strong> erste Roaming-Verordnung (Preisdeckel) · <strong>2012:</strong> niedrigere Deckel · <strong>2017:</strong> „Roam Like At Home" — gar keine Aufschläge mehr im EU-Ausland.'
},
narration: 'Drei Mal in zehn Jahren wurde das Roaming-Gesetz verschärft. Jedes Mal mussten die gleichen zwei Institutionen zustimmen. Welche?',
question: 'Wer muss einem EU-Gesetz zustimmen?',
template: 'Diese zwei Institutionen müssen einem EU-Gesetz zustimmen: {0} und {1}.',
slots: [
{ accepts: 'inst', correct: 'parlament' },
{ accepts: 'inst', correct: 'ministerrat' }
],
poolGroups: [
{ label: 'Welche Institutionen?', cards: ['parlament', 'ministerrat', 'europRat', 'volksabstimmung'], slotKey: 'inst' }
],
slotsInterchangeable: true,
correctSet: ['parlament', 'ministerrat'],
feedbackCorrect: 'Richtig! Mitentscheidung — Parlament und Ministerrat sind gleichberechtigte Gesetzgeber. Beide müssen JA sagen. Das galt 2007, 2012 und 2017 jedes Mal.',
wrongIsMuelleimer: true,
feedbackWrong: 'Das passt nicht. Der Europäische Rat (Regierungschefs) macht keine Gesetze, nur die großen Linien. Eine Volksabstimmung gibt es bei EU-Gesetzen nicht.'
}
],
end: {
type: 'success',
title: 'Roam Like At Home!',
subtitle: 'Seit dem 15. Juni 2017 zahlst du im EU-Ausland nichts extra fürs Telefonieren, SMS oder Datenvolumen.',
realStory: 'Die EU-Kommission unter Viviane Reding schlug 2007 die erste Roaming-Verordnung vor — gegen heftigen Widerstand der großen Mobilfunk-Konzerne (Vodafone, T-Mobile, Orange). 2007 wurde der erste Preisdeckel beschlossen, 2012 folgte ein zweiter, 2017 der finale Schritt: „Roam Like At Home". Heute zahlt jede:r EU-Bürger*in im EU-Ausland dieselben Preise wie zu Hause. Roaming war eines der größten Bürgerrechts-Gesetze der EU — und ist im Alltag kaum noch zu spüren, weil es so selbstverständlich geworden ist. Die EU hat eine zehn Jahre lange Auseinandersetzung mit der Telekommunikations-Industrie gewonnen.'
}
},
/* ===================================================
SZENARIO 6: Glyphosat — Bürgerinitiative scheitert
inhaltlich, Stoff wird verlängert
=================================================== */
glyphosat: {
id: 'glyphosat',
icon: '🌾',
title: 'Glyphosat stoppen?',
teaser: '1,3 Mio Unterschriften gegen das Pestizid — und trotzdem darf es weiter gespritzt werden.',
badge: 'Gemischt',
badgeType: 'failed',
actor: { name: 'Mara', role: '15 Jahre, lebt am Rand eines Ackers' },
intro: 'Du heißt Mara, bist 15 und lebst am Rand eines großen Maisfelds in Niederösterreich. Im Frühjahr siehst du, wie der Bauer eine gelb-orange Flüssigkeit auf das Feld spritzt — alles Unkraut wird braun und stirbt. Deine Mutter erzählt dir, dass das „Glyphosat" ist, der weltweit meistverkaufte Unkrautvernichter. 2017 versuchte eine europäische Bürgerinitiative, ihn EU-weit zu verbieten. Wie ging das aus?',
steps: [
/* SCHRITT 1 — PICK: Werkzeug */
{
type: 'pick',
scene: 'home',
narration: 'Du recherchierst: Mehrere Umweltverbände haben sich 2017 zusammengetan und ein offizielles Werkzeug genutzt, um Glyphosat zu stoppen. Welches?',
question: 'Welches demokratische Werkzeug haben sie gewählt?',
pool: ['buergerinitiative', 'petition', 'demo', 'volksabstimmung'],
correct: 'buergerinitiative',
feedbackCorrect: 'Richtig! Eine Europäische Bürgerinitiative — getragen von Umweltvereinen wie Greenpeace und WeMove.EU. Bürgerkomitee-Mitglieder waren u.a. Franziska Achterberg von Greenpeace.',
feedbackByCard: {
petition: { isMuelleimer: false, text: 'Eine Petition ans EU-Parlament hätte weniger Druck. Welches stärkere Werkzeug nutzten die Verbände?' },
demo: { isMuelleimer: false, text: 'Demos gab es viele. Welches förmliche Werkzeug haben Bürger*innen genutzt?' },
volksabstimmung:{ isMuelleimer: true, text: 'Volksabstimmungen gibt es bei EU-Gesetzen nicht. Welches Werkzeug haben Bürger*innen?' }
}
},
/* SCHRITT 2 — SENTENCE: Hürden */
{
type: 'sentence',
scene: 'signatures',
milestone: {
title: '1,3 Millionen Stimmen!',
text: 'Die Initiative „Stop Glyphosat" sammelte 2017 über <strong>1.320.000 Unterschriften</strong> aus 22 EU-Ländern — die zweiterfolgreichste Bürgerinitiative aller Zeiten.'
},
narration: 'Innerhalb von nur sechs Monaten kamen die nötigen Unterschriften zusammen. Auch deine Mutter unterschrieb online. Welche Mindesthürden mussten überwunden werden?',
question: 'Welche Hürden mussten erfüllt werden?',
template: 'Eine Europäische Bürgerinitiative braucht mindestens {0} Unterschriften aus mindestens {1} EU-Ländern.',
slots: [
{ accepts: 'menge', correct: 'zahl_1mio' },
{ accepts: 'laender', correct: 'laender_7' }
],
poolGroups: [
{ label: 'Anzahl Unterschriften', cards: ['zahl_100k', 'zahl_1mio', 'zahl_10mio'], slotKey: 'menge' },
{ label: 'Aus wie vielen Ländern?', cards: ['laender_1', 'laender_7', 'laender_27'], slotKey: 'laender' }
],
feedbackCorrect: 'Genau! 1 Million aus mindestens 7 Ländern — Stop Glyphosat hat das deutlich überschritten.',
wrongIsMuelleimer: true,
feedbackWrong: 'Die Zahlen passen noch nicht. Schau dir die Karten zur Bürgerinitiative noch einmal an.'
},
/* SCHRITT 3 — PICK: Lobby */
{
type: 'pick',
scene: 'bruessel',
narration: 'Während die Unterschriften gesammelt wurden, gab Bayer/Monsanto, der größte Hersteller von Glyphosat, viele Millionen Euro für Brüsseler Büros, Studien und Treffen mit EU-Beamten aus. Wie nennt man diese organisierte Einflussnahme?',
question: 'Was machen Bayer/Monsanto und die Industrie hier?',
pool: ['lobby', 'volksabstimmung', 'demo', 'petition'],
correct: 'lobby',
feedbackCorrect: 'Richtig! Lobbyismus ist legal — über 12.000 Lobby-Gruppen sind in Brüssel registriert. Bei Glyphosat trafen Industrie-Lobby und NGO-Lobby aufeinander. Beide versuchten zu beeinflussen.',
feedbackByCard: {
volksabstimmung:{ isMuelleimer: true, text: 'Volksabstimmungen gibt es bei EU-Gesetzen nicht. Was tun große Konzerne, um Gesetze zu beeinflussen?' },
demo: { isMuelleimer: false, text: 'Demos machen Bürger*innen, nicht Konzerne. Was tun Konzerne stattdessen?' },
petition: { isMuelleimer: false, text: 'Eine Petition ist ein Bürger-Werkzeug. Konzerne nutzen ein anderes Wort dafür — welches?' }
},
funFact: {
emoji: '💰',
title: 'Bayer kaufte Monsanto für 63 Mrd $',
text: '2018 übernahm der deutsche Bayer-Konzern den US-Hersteller Monsanto — und damit auch dessen Hauptprodukt Glyphosat. Bayer/Monsanto soll allein 2019 über <strong>11 Millionen Euro</strong> in EU-Lobby-Aktivitäten gesteckt haben. Greenpeace und die Umwelt-NGOs zusammen etwa ein Zehntel davon.'
}
},
/* SCHRITT 4 — MULTICHOICE: Wissenschaft uneinig (IARC vs EFSA) */
{
type: 'multichoice',
scene: 'bruessel',
narration: 'Parallel zur Unterschriftensammlung tobt ein wissenschaftlicher Streit. Die <strong>IARC</strong> (Krebsforschungsagentur der WHO) stuft Glyphosat 2015 als „wahrscheinlich krebserregend" ein. Die <strong>EFSA</strong> (EU-Lebensmittelbehörde) widerspricht zwei Jahre später: „nicht krebserregend". Beide Behörden lasen dieselben Studien — und kamen zu unterschiedlichen Schlüssen.',
question: 'Auf welches Gutachten stützt sich die EU-Kommission rechtlich?',
options: [
{ label: 'IARC Krebsagentur der WHO',
correct: false,
feedback: 'IARC ist hoch angesehen, aber rechtlich nicht bindend für die EU. Welche EIGENE EU-Behörde bewertet Pestizide offiziell?' },
{ label: 'EFSA EU-Lebensmittelbehörde',
correct: true,
feedback: 'Genau! Die EFSA ist die offizielle Pestizid-Behörde der EU. Ihr Urteil ist für die Kommission bindend. NGOs werfen der EFSA bis heute vor, sich zu stark auf Industrie-Studien zu stützen, die unter Verschwiegenheit bewertet wurden.' },
{ label: 'Greenpeace-Studie',
correct: false,
feedback: 'Greenpeace ist eine NGO und keine Behörde. Sie kann Studien beauftragen, aber nicht offiziell bewerten. Welche EU-Stelle ist offiziell zuständig?' },
{ label: 'Deutscher Bauernverband',
correct: false,
feedback: 'Der Bauernverband ist Interessenvertretung der Landwirt*innen, keine wissenschaftliche Behörde. Wer ist offiziell zuständig?' }
],
funFact: {
emoji: '📑',
title: 'Wer kopiert wessen Hausaufgabe?',
text: 'Investigative Journalist*innen fanden 2017 heraus: Große Passagen des EFSA-Berichts waren <strong>wortgleich</strong> aus den Industrie-Anträgen von <strong>Monsanto</strong> übernommen. Die EFSA verteidigte das als „normal" — die Industrie liefere ja die Studien. NGOs fordern seither, dass Studien öffentlich und unabhängig sein müssen. Eine neue Verordnung von 2019 schreibt das inzwischen vor.'
}
},
/* SCHRITT 5 — MULTICHOICE: Wer kippte die Abstimmung? */
{
type: 'multichoice',
scene: 'trilog',
narration: 'Am 27. November 2017 stimmen die Mitgliedstaaten in einem speziellen Komitologie-Verfahren über die Verlängerung der Glyphosat-Zulassung ab — nicht das EU-Parlament, sondern die Regierungen. Lange Zeit gibt es keine Mehrheit. Dann fällt eine überraschende Entscheidung.',
question: 'Was kippte das Votum am Ende — gegen alle Erwartungen?',
options: [
{ label: 'Deutschland stimmte überraschend FÜR die Verlängerung',
correct: true,
feedback: 'Genau. Die deutsche Regierung war intern uneins (SPD dagegen, CSU dafür). CSU-Agrarminister Christian Schmidt stimmte AM 27.11.2017 IM ALLEINGANG für die Verlängerung — gegen die Absprache mit der SPD-Umweltministerin. Damit hatte die Verlängerung eine knappe Mehrheit. Bundeskanzlerin Merkel rügte ihn öffentlich — aber die Stimme war abgegeben.' },
{ label: 'Frankreich überzeugte alle anderen',
correct: false,
feedback: 'Frankreich war GEGEN Glyphosat (Macron versprach den Ausstieg). Welches andere große Land kippte überraschend Pro-Glyphosat?' },
{ label: 'Das EU-Parlament stimmte zu',
correct: false,
feedback: 'Das Parlament war sogar mit 355:204 Stimmen GEGEN Glyphosat — bei Pestizid-Zulassungen entscheidet es aber gar nicht mit. Wer hat hier abgestimmt?' },
{ label: 'Die Kommission entschied im Alleingang',
correct: false,
feedback: 'Die Kommission braucht die Mitgliedstaaten. Welcher Mitgliedstaat hat den Ausschlag gegeben?' }
],
funFact: {
emoji: '🇩🇪',
title: 'Ein einzelner Minister kippte das Votum',
text: 'CSU-Agrarminister <strong>Christian Schmidt</strong> stimmte gegen die ausdrückliche Absprache mit SPD-Umweltministerin <strong>Barbara Hendricks</strong>. Sie hätte sich enthalten, was die Verlängerung verhindert hätte. Eine einzelne Stimme reichte — und Glyphosat ist seither bis <strong>Dezember 2033</strong> in der EU zugelassen. Lehre: Demokratie lebt von Disziplin und Absprachen. Verstöße haben Folgen.'
}
},
/* SCHRITT 6 — SENTENCE: Ausgang */
{
type: 'sentence',
scene: 'trilog',
narration: 'Im Dezember 2017 antwortet die Kommission der Bürgerinitiative: Sie teile das Ziel des Vorsorgeprinzips, sehe aber keinen wissenschaftlichen Grund für ein vollständiges Verbot. 2023 folgt eine weitere Verlängerung um 10 Jahre. Wie heißt dieses Ergebnis?',
question: 'Wie nennt man diesen Ausgang?',
template: 'Trotz erfolgreicher Initiative gab es eine inhaltliche {0}, weil die {1} sich gegen ein Verbot entschied.',
slots: [
{ accepts: 'ergeb', correct: 'ablehnung' },
{ accepts: 'wer', correct: 'kommission' }
],
poolGroups: [
{ label: 'Was war das Ergebnis?', cards: ['ablehnung', 'mitentscheidung', 'eu_gesetz'], slotKey: 'ergeb' },
{ label: 'Wer entschied?', cards: ['kommission', 'parlament', 'buergerinitiative'], slotKey: 'wer' }
],
feedbackCorrect: 'Genau. Die Kommission lehnte das Verbot inhaltlich ab — die Initiative war formal erfolgreich, aber inhaltlich ohne Wirkung. Glyphosat darf bis mindestens Dezember 2033 weiter eingesetzt werden.',
wrongIsMuelleimer: false,
feedbackWrong: 'Die Karten passen noch nicht. Schau dir „Ablehnung" und „Kommission" genauer an.'
}
],
end: {
type: 'mixed',
title: 'Erfolg im Verfahren — Niederlage im Inhalt',
subtitle: '1,3 Mio Stimmen, alle Hürden überwunden — und trotzdem wird weiter gespritzt.',
realStory: 'Die Bürgerinitiative „Stop Glyphosat" wurde 2017 von Greenpeace, WeMove.EU und 30 weiteren Umweltorganisationen gestartet. Innerhalb von sechs Monaten unterschrieben 1.320.517 Menschen aus 22 EU-Ländern. Im November 2017 stimmten die Mitgliedstaaten dennoch einer 5-Jahres-Verlängerung der Glyphosat-Zulassung zu — wesentlich auf Druck Deutschlands, das im letzten Moment einknickte. Im Dezember 2017 antwortete die Kommission der Initiative: Verbot abgelehnt, weil die EU-Lebensmittelbehörde EFSA Glyphosat als „nicht krebserregend" eingestuft habe. NGOs werfen der EFSA bis heute vor, sich zu sehr auf Industrie-Studien gestützt zu haben. Im November 2023 wurde Glyphosat um weitere 10 Jahre verlängert — bis Dezember 2033. Lehre: Eine Bürgerinitiative kann ein Thema sichtbar machen, mehr aber nicht. Wenn Wissenschaft und Lobby gegen sie stehen, hat sie keine Chance.'
}
},
/* ===================================================
SZENARIO 7: EU-Wahl — Mini-Spiel mit Animation
=================================================== */
eu_wahl: {
id: 'eu_wahl',
icon: '🗳️',
title: 'Wer wählt das EU-Parlament?',
teaser: 'Schau zu, wie 720 Abgeordnete aus 27 Ländern zusammenkommen — und teste dein Wissen.',
badge: 'Mini-Spiel',
actor: { name: 'Lara', role: '14 Jahre, erstes Mal wählen 2029' },
intro: 'Du heißt Lara, bist 14, und 2029 wirst du erstmals wählen dürfen — auch das EU-Parlament. Du fragst dich: Wer sitzt da überhaupt drin? Wie viele sind es? Schau zu, wie sich das EU-Parlament aus den 27 Ländern bildet.',
steps: [
/* SCHRITT 1 — ANIMATION */
{
type: 'animation',
scene: 'home',
narration: 'Alle fünf Jahre wird das EU-Parlament direkt von den Bürger*innen gewählt — in jedem der 27 EU-Länder. Schau zu, wie sich die Abgeordneten aus ihren Heimatländern in Brüssel sammeln. Die Größe der Punkte zeigt, wie viele Abgeordnete jedes Land entsendet.',
question: 'Drück auf „Animation starten" und beobachte.',
durationMs: 5800
},
/* SCHRITT 2 — MULTICHOICE: Anzahl */
{
type: 'multichoice',
scene: 'bruessel',
narration: 'Die Abgeordneten haben sich versammelt. Insgesamt sind es ziemlich viele — aber wie viele genau?',
question: 'Wie viele Abgeordnete sitzen seit der Wahl 2024 im EU-Parlament?',
options: [
{ label: '500', correct: false, feedback: 'Knapp daneben — es sind seit 2024 mehr. Vor 2014 waren es 766, dann wurden es weniger, jetzt wieder mehr.' },
{ label: '720', correct: true, feedback: 'Genau! Seit der Wahl im Juni 2024 sind es 720 Abgeordnete. Vorher waren es 705 — durch das Bevölkerungswachstum kamen 15 Sitze dazu.' },
{ label: '1000', correct: false, feedback: 'So viele sind es nicht — das wäre das gesamte EU-Personal. Im Plenarsaal sitzen weniger.' },
{ label: '27 (eine pro Land)', correct: false, feedback: 'Das wären zu wenige. Jedes Land bekommt mehrere Sitze, je nach Bevölkerung.' }
]
},
/* SCHRITT 3 — MULTICHOICE: Größtes Land */
{
type: 'multichoice',
scene: 'bruessel',
narration: 'Die Sitze sind nicht gleich verteilt: Große Länder bekommen mehr, kleine weniger. Aber nicht streng proportional — sonst hätten die Großen alle Macht. Das nennt sich „degressive Proportionalität".',
question: 'Welches Land hat im EU-Parlament die meisten Abgeordneten?',
options: [
{ label: 'Frankreich (81)', correct: false, feedback: 'Nah dran — Frankreich ist Nummer zwei mit 81 Sitzen. Welches Land hat noch mehr?' },
{ label: 'Italien (76)', correct: false, feedback: 'Italien ist Nummer drei mit 76 Sitzen. Welches Land ist größer?' },
{ label: 'Deutschland (96)', correct: true, feedback: 'Genau! Deutschland hat die meisten — 96 Abgeordnete. Es ist auch das bevölkerungsreichste EU-Land mit 84 Mio. Einwohner*innen. Malta hat als kleinstes Land nur 6.' },
{ label: 'Polen (53)', correct: false, feedback: 'Polen hat 53 Sitze — viele, aber nicht die meisten. Welches Land ist noch größer?' }
]
},
/* SCHRITT 4 — MULTICHOICE: Wahlrhythmus */
{
type: 'multichoice',
scene: 'bruessel',
narration: 'Damit die EU demokratisch bleibt, muss das Parlament regelmäßig neu gewählt werden. Wie oft passiert das?',
question: 'Alle wie viele Jahre wird das EU-Parlament neu gewählt?',
options: [
{ label: 'Alle 2 Jahre', correct: false, feedback: 'Das wäre ziemlich oft — Wahlkämpfe kosten Geld und Aufmerksamkeit. Tipp: Es ist genau so oft wie der Nationalrat in Österreich, wenn er nicht aufgelöst wird.' },
{ label: 'Alle 5 Jahre', correct: true, feedback: 'Genau! Alle fünf Jahre. Die letzten Wahlen waren Juni 2024 — die nächsten 2029. Du wirst dann 17 sein und in Österreich erstmals mitwählen dürfen, weil das Wahlalter dort 16 ist.' },
{ label: 'Alle 7 Jahre', correct: false, feedback: 'Sieben Jahre wäre zu lange für eine Demokratie — die Bürger*innen sollen öfter mitbestimmen können.' },
{ label: 'Wenn es nötig ist', correct: false, feedback: 'In einer Demokratie wird in festen Abständen gewählt — sonst hätten Politiker*innen zu viel Macht über den Wahltermin.' }
]
}
],
end: {
type: 'success',
title: 'Du hast es geschafft!',
subtitle: '720 Abgeordnete, alle fünf Jahre direkt gewählt — Deutschland am meisten, Malta am wenigsten.',
realStory: 'Das EU-Parlament wird seit 1979 direkt von den EU-Bürger*innen gewählt — alle fünf Jahre. Bei der Wahl im Juni 2024 stimmten rund 182 Millionen Menschen ab (Wahlbeteiligung 51 %). Die 720 Sitze verteilen sich nach „degressiver Proportionalität": Deutschland 96, Frankreich 81, Italien 76, Spanien 61, Polen 53, Niederlande 31, Belgien/Tschechien/Griechenland/Ungarn/Portugal/Schweden je 21, Österreich 20 — bis hinunter zu Malta, Luxemburg und Zypern mit je 6 Sitzen. Das Wahlalter ist national geregelt: in Österreich, Belgien, Deutschland und Malta darfst du schon mit 16 wählen, in den meisten anderen Ländern erst mit 18. Die nächste Wahl ist im Juni 2029.'
}
},
/* ===================================================
SZENARIO 8: DSGVO — Lobby-Schlacht über Jahre
=================================================== */
dsgvo: {
id: 'dsgvo',
icon: '🔒',
title: 'DSGVO — wer schützt deine Daten?',
teaser: 'Vier Jahre Verhandlung, 4 000 Lobby-Anträge — und am Ende das schärfste Datenschutz-Gesetz der Welt.',
badge: 'Erfolg',
actor: { name: 'Hannah', role: '15 Jahre, neugierig auf Apps' },
intro: 'Du heißt Hannah, bist 15 und siehst auf deinem Handy plötzlich überall Cookie-Banner und „Datenschutz akzeptieren"-Hinweise. Im Unterricht erfährst du: Das alles geht auf die DSGVO zurück — die Datenschutz-Grundverordnung, ein EU-Gesetz aus 2016. Wie kam es zu so einem Gesetz?',
steps: [
/* SCHRITT 1 — PICK */
{
type: 'pick',
scene: 'bruessel',
narration: 'Schon 1995 gab es eine erste EU-Datenschutz-Richtlinie — aber das Internet war damals noch klein. 2012 schlug Justiz-Kommissarin Viviane Reding eine moderne, schärfere Verordnung vor. Wer hatte das Recht, das Gesetz einzubringen?',
question: 'Wer hat den DSGVO-Vorschlag offiziell eingebracht?',
pool: ['kommission', 'parlament', 'buergerinitiative', 'volksabstimmung'],
correct: 'kommission',
feedbackCorrect: 'Genau! Das Initiativrecht liegt bei der EU-Kommission. Viviane Reding war federführend. Es gab keine Bürgerinitiative — die Kommission selbst sah das Problem.',
feedbackByCard: {
parlament: { isMuelleimer: false, text: 'Das Parlament hat später viel verhandelt, aber selbst einbringen darf es nichts. Wer hat das Initiativrecht?' },
buergerinitiative:{ isMuelleimer: false, text: 'Es gab keine Bürgerinitiative zur DSGVO. Wer wurde stattdessen tätig?' },
volksabstimmung: { isMuelleimer: true, text: 'Volksabstimmungen gibt es bei EU-Gesetzen nicht. Wer hat das Initiativrecht?' }
}
},
/* SCHRITT 2 — PICK: Lobby-Rekord */
{
type: 'pick',
scene: 'bruessel',
milestone: {
title: '4 000 Änderungsanträge!',
text: 'Big-Tech-Konzerne wie Google, Facebook und Amazon investierten Millionen, um die DSGVO abzuschwächen. Auch Verbraucherschützer*innen mischten mit. Im EU-Parlament wurden über <strong>4 000 Änderungsanträge</strong> eingebracht — Lobby-Rekord.'
},
narration: 'Während das Gesetz im Parlament verhandelt wurde, kam es zum heftigsten Lobby-Streit, den Brüssel je gesehen hat. Big-Tech-Konzerne hatten viel zu verlieren — Bürgerrechts-NGOs viel zu gewinnen. Wie nennt man diese organisierte Einflussnahme?',
question: 'Was machten Google, Facebook und Co. da?',
pool: ['lobby', 'demo', 'buergerinitiative', 'volksabstimmung'],
correct: 'lobby',
feedbackCorrect: 'Richtig. Lobbyismus ist legal in der EU — über 12 000 Organisationen sind in Brüssel registriert. Bei der DSGVO trafen Industrie-Lobby und NGO-Lobby massiv aufeinander. NGOs wie EDRi und Privacy International gewannen am Ende viele Punkte.',
feedbackByCard: {
demo: { isMuelleimer: false, text: 'Demos macht man auf der Straße. Was tun Konzerne in Brüssel hinter verschlossenen Türen?' },
buergerinitiative:{ isMuelleimer: false, text: 'Bürgerinitiativen sind ein Bürger-Werkzeug. Konzerne nutzen einen anderen, organisierten Weg — welchen?' },
volksabstimmung: { isMuelleimer: true, text: 'Volksabstimmungen gibt es bei EU-Gesetzen nicht. Wie nennt man die Einflussnahme von Konzernen?' }
}
},
/* SCHRITT 3 — SENTENCE */
{
type: 'sentence',
scene: 'trilog',
narration: 'Nach vier Jahren Verhandlungen einigen sich Parlament und Rat im Dezember 2015. Im April 2016 wird die DSGVO offiziell beschlossen — und tritt am 25. Mai 2018 in Kraft. Wer musste zustimmen?',
question: 'Wer muss einem EU-Gesetz zustimmen?',
template: 'Diese zwei Institutionen müssen einem EU-Gesetz zustimmen: {0} und {1}.',
slots: [
{ accepts: 'inst', correct: 'parlament' },
{ accepts: 'inst', correct: 'ministerrat' }
],
poolGroups: [
{ label: 'Welche Institutionen?', cards: ['parlament', 'ministerrat', 'europRat', 'volksabstimmung'], slotKey: 'inst' }
],
slotsInterchangeable: true,
correctSet: ['parlament', 'ministerrat'],
feedbackCorrect: 'Richtig — Mitentscheidung. Im April 2016 stimmten Parlament (530 Ja gegen 36 Nein) und Rat zu. Die DSGVO ist seit 25. Mai 2018 in jedem EU-Land direkt anwendbar.',
wrongIsMuelleimer: true,
feedbackWrong: 'Das passt nicht. Der Europäische Rat (Regierungschefs) macht keine Gesetze. Eine Volksabstimmung gibt es bei EU-Gesetzen nicht.'
}
],
end: {
type: 'success',
title: 'Datenschutz mit Zähnen',
subtitle: 'Seit 25. Mai 2018 schützt die DSGVO deine persönlichen Daten in allen 27 EU-Ländern — mit Bußgeldern bis zu 4 % des weltweiten Jahresumsatzes.',
realStory: 'Die Datenschutz-Grundverordnung (DSGVO) ist eines der weitreichendsten Bürgerrechts-Gesetze der EU. Justiz-Kommissarin Viviane Reding schlug sie 2012 vor. Vier Jahre lang wurde verhandelt — mit dem grünen MEP Jan Philipp Albrecht als Berichterstatter im Parlament. Über 4 000 Änderungsanträge gingen ein, vor allem aus Big-Tech-Lobbys (Google, Facebook, Amazon). Die NGOs European Digital Rights (EDRi), Bits of Freedom und Privacy International argumentierten dagegen. Im April 2016 wurde die DSGVO mit 530:36 Stimmen vom Parlament beschlossen. Bußgelder bis zu 4 % des weltweiten Jahresumsatzes machten sie zum schärfsten Datenschutz-Gesetz der Welt — Google bekam allein in Frankreich 50 Mio. Euro Strafe (2019), Meta 1,2 Mrd. (2023). Heute orientieren sich Kalifornien, Brasilien und sogar China an der DSGVO. Lehre: Die EU kann Konzernen weltweit Regeln vorschreiben — wenn sie es will.'
}
},
/* ===================================================
SZENARIO 9: Right2Water — erste erfolgreiche EBI
=================================================== */
right2water: {
id: 'right2water',
icon: '💧',
title: 'Wasser ist ein Menschenrecht',
teaser: 'Die erste erfolgreiche Bürgerinitiative der EU-Geschichte — 1,9 Mio Unterschriften für sauberes Trinkwasser.',
badge: 'Erfolg',
actor: { name: 'Pablo', role: '13 Jahre, lebt in Andalusien' },
intro: 'Du heißt Pablo, bist 13 und lebst in Andalusien. Im Sommer wird das Wasser knapp — manche Gemeinden schalten es ab Mittags ab. Deine Eltern erzählen dir, dass es 2012 eine europäische Bürgerinitiative gab: „Right2Water" — Wasser ist ein Menschenrecht. Sie wollte, dass Trinkwasser nie privatisiert wird, sondern öffentliche Aufgabe bleibt. Die erste Bürgerinitiative überhaupt, die genug Stimmen sammelte.',
steps: [
/* SCHRITT 1 — PICK */
{
type: 'pick',
scene: 'home',
narration: 'Die europäische Gewerkschaft EPSU startete 2012 die Initiative. Welches Werkzeug nutzten sie?',
question: 'Welches demokratische Werkzeug haben Bürger*innen in der EU?',
pool: ['buergerinitiative', 'petition', 'demo', 'volksabstimmung'],
correct: 'buergerinitiative',
feedbackCorrect: 'Richtig! Eine Europäische Bürgerinitiative — das damals brandneue Werkzeug der EU. Right2Water war die ALLERERSTE Initiative, die genug Unterschriften sammelte.',
feedbackByCard: {
petition: { isMuelleimer: false, text: 'Eine Petition wäre möglich, aber viel weniger schlagkräftig. Welches stärkere Werkzeug haben Bürger*innen?' },
demo: { isMuelleimer: false, text: 'Demos halfen sicher, sind aber kein offizielles EU-Werkzeug. Welches förmliche Werkzeug nutzten EPSU und die NGOs?' },
volksabstimmung: { isMuelleimer: true, text: 'Volksabstimmungen gibt es bei EU-Gesetzen nicht. Welches Werkzeug haben Bürger*innen?' }
}
},
/* SCHRITT 2 — SENTENCE: Hürden */
{
type: 'sentence',
scene: 'signatures',
milestone: {
title: '1,9 Millionen Stimmen — die erste je!',
text: 'Innerhalb von 18 Monaten kamen <strong>1.884.790</strong> Unterschriften aus 13 Ländern zusammen — die erste Bürgerinitiative der EU-Geschichte, die jemals genug Stimmen sammelte.'
},
narration: 'EPSU und über 100 Wasserverbände aus ganz Europa sammelten Unterschriften. Welche Hürden mussten sie überwinden?',
question: 'Welche Mindesthürden gelten?',
template: 'Eine Europäische Bürgerinitiative braucht mindestens {0} Unterschriften aus mindestens {1} EU-Ländern.',
slots: [
{ accepts: 'menge', correct: 'zahl_1mio' },
{ accepts: 'laender', correct: 'laender_7' }
],
poolGroups: [
{ label: 'Anzahl Unterschriften', cards: ['zahl_100k', 'zahl_1mio', 'zahl_10mio'], slotKey: 'menge' },
{ label: 'Aus wie vielen Ländern?', cards: ['laender_1', 'laender_7', 'laender_27'], slotKey: 'laender' }
],
feedbackCorrect: 'Genau! 1 Million aus mindestens 7 Ländern — Right2Water hat das fast verdoppelt.',
wrongIsMuelleimer: true,
feedbackWrong: 'Die Zahlen passen nicht. Schau dir die Karten zur Bürgerinitiative noch einmal an.'
},
/* SCHRITT 3 — PICK: Was die Kommission tat */
{
type: 'pick',
scene: 'bruessel',
narration: 'Im März 2014 wird das Bürgerkomitee in Brüssel offiziell empfangen, eine öffentliche Anhörung im EU-Parlament findet statt. Die Kommission MUSS innerhalb von sechs Monaten begründet antworten — und hat dabei zwei Wege: einen Gesetzesvorschlag einbringen oder ablehnen. Welchen Weg wählte sie?',
question: 'Welchen der beiden Wege wählte die Kommission?',
pool: ['eu_gesetz', 'ablehnung', 'volksabstimmung', 'mitentscheidung'],
correct: 'eu_gesetz',
feedbackCorrect: 'Richtig! Die Kommission entschied sich für den positiven Weg — sie nahm das Anliegen ernst. Sie brachte 2017 eine Trinkwasser-Richtlinie auf den Weg, die 2020 als Richtlinie 2020/2184 in Kraft trat. Wasser blieb damit eindeutig öffentliche Aufgabe und wurde nicht privatisiert.',
feedbackByCard: {
ablehnung: { isMuelleimer: false, text: 'Eine Ablehnung wäre möglich gewesen — aber Right2Water war erfolgreicher. Was hat die Kommission stattdessen getan?' },
mitentscheidung: { isMuelleimer: false, text: 'Mitentscheidung kommt erst, WENN ein Gesetzesvorschlag da ist. Welchen Weg wählte die Kommission davor?' },
volksabstimmung: { isMuelleimer: true, text: 'Volksabstimmungen gibt es bei EU-Gesetzen nicht. Welche zwei echten Wege hat die Kommission?' }
}
}
],
end: {
type: 'success',
title: 'Wasser bleibt öffentlich',
subtitle: 'Die erste erfolgreiche Bürgerinitiative der EU-Geschichte führte tatsächlich zu einem neuen Gesetz.',
realStory: 'Die Bürgerinitiative „Right2Water" wurde 2012 von der europäischen Gewerkschaft EPSU (Public Service Union) gestartet. Auslöser war eine Sorge: In den Krisenstaaten (Griechenland, Portugal, Spanien) drohten Wasserwerke privatisiert zu werden. Innerhalb von 18 Monaten unterschrieben 1.884.790 Menschen aus 13 EU-Ländern — die erste Bürgerinitiative überhaupt, die die Hürden schaffte. Im März 2014 wurde das Komitee in Brüssel empfangen. Die Kommission antwortete teilweise positiv: Sie versprach eine neue Trinkwasser-Richtlinie. Diese kam tatsächlich — Richtlinie 2020/2184 trat am 12. Januar 2021 in Kraft. Sie schreibt vor: Trinkwasser ist eine öffentliche Aufgabe, muss in jeder EU-Stadt sauber und bezahlbar sein. Lehre: Bürgerinitiativen können wirken — wenn das Anliegen in EU-Zuständigkeit fällt UND die Kommission politisch mitzieht.'
}
},
/* ===================================================
SZENARIO 10: AI Act — top-aktuell
=================================================== */
ai_act: {
id: 'ai_act',
icon: '🤖',
title: 'AI Act — Regeln für ChatGPT & Co.',
teaser: 'Wie die EU 2024 als erste der Welt Gesetze für Künstliche Intelligenz beschloss.',
badge: 'Erfolg',
actor: { name: 'Mira', role: '14 Jahre, fragt ChatGPT bei Hausaufgaben' },
intro: 'Du heißt Mira, bist 14 und nutzt ChatGPT manchmal für Englisch-Hausaufgaben. Deine Lehrerin sagt: „Pass auf, was du da reinschreibst — die Daten landen in Amerika." Du fragst dich: Gibt es da keine Regeln? Doch — seit August 2024. Der EU AI Act ist das weltweit erste Gesetz, das KI reguliert. Wie kam es dazu?',
steps: [
/* SCHRITT 1 — PICK */
{
type: 'pick',
scene: 'bruessel',
narration: 'Bereits 2018 erkannte die EU: Künstliche Intelligenz wird die nächste große Technik-Welle. 2021 schlug die EU-Kommission als erste der Welt einen Rechtsrahmen vor. Wer hatte das Initiativrecht?',
question: 'Wer hat den AI Act offiziell eingebracht?',
pool: ['kommission', 'parlament', 'buergerinitiative', 'volksabstimmung'],
correct: 'kommission',
feedbackCorrect: 'Genau. Die EU-Kommission — federführend Margrethe Vestager — schlug den AI Act im April 2021 vor. Es gab keine Bürgerinitiative; die Kommission war selbst die Treiberin.',
feedbackByCard: {
parlament: { isMuelleimer: false, text: 'Das Parlament hat verhandelt — selbst einbringen darf es nichts. Wer hat das Initiativrecht?' },
buergerinitiative:{ isMuelleimer: false, text: 'Es gab keine Bürgerinitiative zum AI Act. Wer wurde stattdessen tätig?' },
volksabstimmung: { isMuelleimer: true, text: 'Volksabstimmungen gibt es bei EU-Gesetzen nicht. Wer hat das Initiativrecht?' }
}
},
/* SCHRITT 2 — PICK: Lobby */
{
type: 'pick',
scene: 'bruessel',
narration: 'Während verhandelt wurde, kam ChatGPT auf den Markt (Ende 2022) und wirbelte alles durcheinander. OpenAI-Chef Sam Altman flog persönlich nach Brüssel und drohte: „Wenn die EU zu streng ist, ziehen wir uns zurück." Wie nennt man diese Einflussnahme?',
question: 'Was machte OpenAI in Brüssel?',
pool: ['lobby', 'demo', 'petition', 'volksabstimmung'],
correct: 'lobby',
feedbackCorrect: 'Richtig. Auch große Tech-Konzerne wie OpenAI, Google und Microsoft betreiben Lobbyismus in Brüssel. Sam Altman traf 2023 persönlich Kommissarin Vestager. Die EU ließ sich nicht beirren — der AI Act kam trotzdem.',
feedbackByCard: {
demo: { isMuelleimer: false, text: 'Demos macht man auf der Straße. Was tun Tech-Konzerne in Brüssel direkt mit Politiker*innen?' },
petition: { isMuelleimer: false, text: 'Petitionen sind ein Bürger-Werkzeug. Was tun Konzerne stattdessen?' },
volksabstimmung: { isMuelleimer: true, text: 'Volksabstimmungen gibt es bei EU-Gesetzen nicht. Wie nennt man die Einflussnahme?' }
}
},
/* SCHRITT 3 — SENTENCE */
{
type: 'sentence',
scene: 'trilog',
milestone: {
title: 'Beschlossen — als Erstes auf der Welt!',
text: 'Im <strong>März 2024</strong> stimmte das EU-Parlament dem AI Act zu (523 Ja, 46 Nein) — danach der Rat. Die EU war damit das erste Wirtschaftsgebiet der Welt mit umfassenden KI-Regeln.'
},
narration: 'Im Dezember 2023 einigten sich Parlament und Rat im Trilog. Im März 2024 stimmten beide formal zu. Wer musste zustimmen?',
question: 'Wer muss einem EU-Gesetz zustimmen?',
template: 'Diese zwei Institutionen müssen einem EU-Gesetz zustimmen: {0} und {1}.',
slots: [
{ accepts: 'inst', correct: 'parlament' },
{ accepts: 'inst', correct: 'ministerrat' }
],
poolGroups: [
{ label: 'Welche Institutionen?', cards: ['parlament', 'ministerrat', 'europRat', 'volksabstimmung'], slotKey: 'inst' }
],
slotsInterchangeable: true,
correctSet: ['parlament', 'ministerrat'],
feedbackCorrect: 'Richtig — Mitentscheidung. Beide Gesetzgeber stimmten 2024 zu. Der AI Act trat am 1. August 2024 in Kraft, gilt schrittweise — die strengsten Regeln (für Hochrisiko-KI) ab August 2026.',
wrongIsMuelleimer: true,
feedbackWrong: 'Das passt nicht. Der Europäische Rat (Regierungschefs) macht keine Gesetze. Eine Volksabstimmung gibt es bei EU-Gesetzen nicht.'
}
],
end: {
type: 'success',
title: 'Erste KI-Regeln der Welt!',
subtitle: 'Seit 1. August 2024 ist KI in der EU reguliert — schrittweise, je nach Risiko-Stufe.',
realStory: 'Der AI Act (Verordnung 2024/1689) ist das weltweit erste umfassende Gesetz für Künstliche Intelligenz. Die EU-Kommission unter Margrethe Vestager schlug ihn im April 2021 vor — noch bevor ChatGPT existierte. Während der Verhandlungen kam Ende 2022 ChatGPT auf den Markt und veränderte das Spielfeld komplett. Das Parlament forderte plötzlich strengere Regeln auch für „General-Purpose-KI". OpenAI-Chef Sam Altman drohte 2023 mit Rückzug — die EU blieb hart. Im Dezember 2023 einigte man sich im Trilog. Im März 2024 stimmte das Parlament mit 523:46 zu. Der AI Act unterteilt KI in Risiko-Klassen: verboten (z.B. Social Scoring), Hochrisiko (z.B. KI in Justiz oder Bewerbung), begrenztes Risiko (z.B. Chatbots) und minimales Risiko. Strafen: bis zu 7 % des weltweiten Jahresumsatzes. Wirkung weltweit: Wie schon bei der DSGVO orientieren sich nun andere Staaten an den EU-Regeln. Lehre: Die EU kann auch in einer extrem schnellen Tech-Welt Standards setzen — wenn sie schnell genug ist.'
}
}
};
/* =========================================================
FLOW-DATEN — Schritt für Schritt Prozessleiste
Pro Szenario: steps[i] für jeden Schritt + outcome am Ende
========================================================= */
const SCENARIO_FLOWS = {
cage_age: {
steps: [
{ icon: '📜', label: 'Idee', sub: 'Reportage über Käfighaltung' },
{ icon: '✍️', label: '1,4 Mio Stimmen', sub: 'aus 18 Ländern' },
{ icon: '🏛️', label: 'Anhörung', sub: 'Kommission verspricht Gesetz' },
{ icon: '⚖️', label: 'Mitentscheidung', sub: 'Parlament 558:37 dafür' },
],
outcome: { icon: '⏳', label: 'Versprechen offen', sub: 'Klage am EuGH seit 2024' }
},
usb_c: {
steps: [
{ icon: '🏛️', label: 'Kommission', sub: 'schlägt USB-C vor' },
{ icon: '🤝', label: 'Freiwillig zuerst', sub: '2009 — Apple macht nicht mit' },
{ icon: '💼', label: 'Apple-Lobby', sub: 'lehnt öffentlich ab' },
{ icon: '⚖️', label: 'Mitentscheidung', sub: 'Parlament 602:13 dafür' },
],
outcome: { icon: '🔌', label: 'USB-C-Pflicht', sub: 'in Kraft seit 28.12.2024' }
},
stop_brexit: {
steps: [
{ icon: '📜', label: 'Idee', sub: 'Bürgerinitiative gegen Brexit' },
{ icon: '🔍', label: 'Prüfung', sub: 'EU schaut Zuständigkeit' },
{ icon: '🚫', label: 'Verfahrensfehler', sub: 'Artikel 50 — EU darf nicht' },
],
outcome: { icon: '❌', label: 'Nicht zugelassen', sub: 'Mülleimer — mit Recht' }
},
stop_vivisection: {
steps: [
{ icon: '📜', label: 'Idee', sub: 'Equivita startet Initiative' },
{ icon: '✍️', label: '1,17 Mio Stimmen', sub: 'aus mehr als 7 Ländern' },
{ icon: '🏛️', label: 'Anhörung', sub: 'Kommission hat 6 Monate' },
{ icon: '⚖️', label: 'Inhaltliche Ablehnung', sub: 'kein neues Gesetz' },
],
outcome: { icon: '❌', label: 'Tierversuche bleiben', sub: 'Richtlinie 2010/63 unverändert' }
},
roaming: {
steps: [
{ icon: '🏛️', label: 'Kommission', sub: 'Reding sieht Marktversagen' },
{ icon: '🤝', label: 'Freiwillig zuerst', sub: 'Konzerne machen wenig' },
{ icon: '⚖️', label: 'Mitentscheidung', sub: '3× verschärft 2007/2012/2017' },
],
outcome: { icon: '📶', label: 'Roam Like At Home', sub: 'gratis seit 15.06.2017' }
},
glyphosat: {
steps: [
{ icon: '📜', label: 'Idee', sub: 'NGO-Allianz gegen Pestizid' },
{ icon: '✍️', label: '1,3 Mio Stimmen', sub: 'aus 22 Ländern' },
{ icon: '💼', label: 'Lobby-Schlacht', sub: 'Bayer/Monsanto 11 Mio €' },
{ icon: '🔬', label: 'Wissenschaft uneinig', sub: 'IARC vs EFSA' },
{ icon: '🗳️', label: 'Mitgliedstaaten', sub: 'DE-Alleingang kippt Votum' },
{ icon: '🏛️', label: 'Inhaltliche Ablehnung', sub: 'Kommission lehnt ab' },
],
outcome: { icon: '🌾', label: 'Glyphosat verlängert', sub: 'bis Dezember 2033' }
},
eu_wahl: {
steps: [
{ icon: '🗺️', label: 'Animation', sub: 'Abgeordnete strömen nach Brüssel' },
{ icon: '🔢', label: '720 Sitze', sub: 'seit Wahl 2024' },
{ icon: '🇩🇪', label: 'DE am größten', sub: '96 Sitze · MT am kleinsten 6' },
{ icon: '🗓️', label: 'Alle 5 Jahre', sub: 'nächste Wahl Juni 2029' },
],
outcome: { icon: '🗳️', label: 'Demokratie live', sub: '182 Mio Wähler*innen 2024' }
},
dsgvo: {
steps: [
{ icon: '🏛️', label: 'Kommission', sub: 'Reding 2012, schärferes Recht' },
{ icon: '💼', label: 'Lobby-Rekord', sub: '4 000 Änderungsanträge' },
{ icon: '⚖️', label: 'Mitentscheidung', sub: 'Parlament 530:36 dafür' },
],
outcome: { icon: '🔒', label: 'DSGVO in Kraft', sub: 'seit 25.05.2018 · bis 4% Umsatzstrafe' }
},
right2water: {
steps: [
{ icon: '📜', label: 'Idee', sub: 'EPSU startet 2012' },
{ icon: '✍️', label: '1,9 Mio Stimmen', sub: 'erste erfolgreiche EBI' },
{ icon: '🏛️', label: 'Kommission baut Gesetz', sub: 'sagt positiv zu' },
],
outcome: { icon: '💧', label: 'Trinkwasser-Richtlinie', sub: 'in Kraft seit 12.01.2021' }
},
ai_act: {
steps: [
{ icon: '🏛️', label: 'Kommission', sub: 'Vestager schlägt AI Act vor' },
{ icon: '💼', label: 'OpenAI lobbyiert', sub: 'Sam Altman droht Rückzug' },
{ icon: '⚖️', label: 'Mitentscheidung', sub: 'Parlament 523:46 dafür' },
],
outcome: { icon: '🤖', label: 'Weltweit erstes KI-Gesetz', sub: 'in Kraft seit 01.08.2024' }
},
};
/* =========================================================
SPIELZUSTAND
========================================================= */
const state = {
scenario: null,
stepIndex: 0,
selectedCardId: null,
slotState: [], // pro Slot: { cardId | null }
answered: false,
difficulty: 'easy', // 'easy' | 'expert'
conceptsViewed: new Set(), // Konzept-Karten im Detail angeschaut (für Achievement)
// === Statistik-Tracking pro Szenario-Durchgang ===
firstTryHits: 0,
totalAttempts: 0,
muelleimerCount: 0,
attemptsByStep: [], // Index = stepIndex, Wert = Anzahl Versuche
detoursByStep: [], // Index = stepIndex, Wert = [{label, cardId?}, ...] — Mülleimer-Picks
reshufflesByStep: [], // Index = stepIndex, Wert = Anzahl manuell wieder herausgezogener Karten (Probierzüge)
totalReshuffles: 0,
startedAt: 0,
};
/* =========================================================
ACHIEVEMENT-KATALOG
========================================================= */
const ACHIEVEMENTS = [
{ id: 'first_try_master', emoji: '🎯', name: 'Treffsicher', desc: 'Ein Szenario komplett im ersten Versuch gelöst.' },
{ id: 'clean_sweep', emoji: '🧹', name: 'Saubere Sache', desc: 'Ein Szenario ohne Mülleimer abgeschlossen.' },
{ id: 'expert_clear', emoji: '🦉', name: 'Profi am Werk', desc: 'Ein Szenario im Profi-Modus abgeschlossen.' },
{ id: 'marathon', emoji: '🗺️', name: 'Marathon', desc: 'Alle Szenarien mindestens einmal gespielt.' },
{ id: 'all_5_stars', emoji: '⭐', name: 'Sternensammlerin', desc: 'Alle Szenarien mit 5 Sternen geschafft.' },
{ id: 'glossar_explorer', emoji: '📖', name: 'Wissensdurst', desc: '10 verschiedene Begriffe im Detail angeschaut.' },
];
/* =========================================================
API-CONFIG (vom PHP-Wrapper injiziert)
========================================================= */
const EUW_API_BASE = window.EUW_API_BASE || '/geograsim/App/api';
const EUW_BASE = window.EUW_BASE || './';
/* Browser-localStorage-Aggregat (Cross-Szenario, Easter-Eggs / Achievements) */
const LS_KEY = 'geograsim_eu_werkstatt_v1';
function lsLoad() {
try { return JSON.parse(localStorage.getItem(LS_KEY) || '{}'); } catch { return {}; }
}
function lsSave(o) {
try { localStorage.setItem(LS_KEY, JSON.stringify(o)); } catch {}
}
/* =========================================================
SCHWIERIGKEITSSTUFE (in localStorage persistiert)
========================================================= */
function loadDifficulty() {
const ls = lsLoad();
const d = ls.difficulty === 'expert' ? 'expert' : 'easy';
state.difficulty = d;
return d;
}
function setDifficulty(d) {
if (d !== 'easy' && d !== 'expert') return;
state.difficulty = d;
const ls = lsLoad();
ls.difficulty = d;
lsSave(ls);
// Buttons visuell aktualisieren
document.querySelectorAll('.diff-btn').forEach(b => {
b.classList.toggle('active', b.dataset.diff === d);
});
}
/* =========================================================
POOL-FILTER FÜR PROFI-MODUS
- Pick-Steps: offensichtliche Mülleimer-Karten entfernen
- Sentence-Steps: Pool-Group-Label ausblenden
========================================================= */
function effectivePickPool(step) {
if (state.difficulty !== 'expert' || step.type !== 'pick' || !step.pool) return step.pool;
const fbByCard = step.feedbackByCard || {};
const filtered = step.pool.filter(cid => {
if (cid === step.correct) return true;
const fb = fbByCard[cid];
// Mülleimer-Karten im Profi-Modus rauswerfen (zu offensichtlich falsch)
return !(fb && fb.isMuelleimer);
});
// Sicherheitsnetz: mindestens 2 Karten
return filtered.length >= 2 ? filtered : step.pool;
}
/* =========================================================
ACHIEVEMENT-LOGIK
========================================================= */
function loadAchievements() {
const ls = lsLoad();
return ls.achievements || {};
}
function isAchievementUnlocked(id) {
return !!loadAchievements()[id];
}
function unlockAchievement(id) {
const ls = lsLoad();
ls.achievements = ls.achievements || {};
if (ls.achievements[id]) return false; // schon erreicht
ls.achievements[id] = { unlockedAt: Date.now() };
lsSave(ls);
return true;
}
function evaluateAchievements(ctx) {
// ctx = { scenarioId, stars, firstTryHits, stepsTotal, muelleimerCount, difficulty, endType }
const newly = [];
if (ctx.endType !== 'rejected' && ctx.firstTryHits === ctx.stepsTotal && unlockAchievement('first_try_master')) {
newly.push('first_try_master');
}
if (ctx.endType !== 'rejected' && ctx.muelleimerCount === 0 && unlockAchievement('clean_sweep')) {
newly.push('clean_sweep');
}
if (ctx.endType !== 'rejected' && ctx.difficulty === 'expert' && unlockAchievement('expert_clear')) {
newly.push('expert_clear');
}
// Marathon: alle Szenarien mindestens einmal gespielt
const ls = lsLoad();
const playedIds = Object.keys(ls.scenarios || {});
const allIds = Object.keys(SCENARIOS);
if (playedIds.length >= allIds.length && unlockAchievement('marathon')) {
newly.push('marathon');
}
// All 5 stars
const all5 = allIds.every(id => (ls.scenarios && ls.scenarios[id] && ls.scenarios[id].bestStars === 5));
if (all5 && unlockAchievement('all_5_stars')) newly.push('all_5_stars');
// Glossar-Explorer: 10 verschiedene Begriffe angeschaut
const viewedCount = (ls.conceptsViewed || []).length;
if (viewedCount >= 10 && unlockAchievement('glossar_explorer')) newly.push('glossar_explorer');
return newly;
}
function trackConceptViewed(cardId) {
const ls = lsLoad();
const viewed = new Set(ls.conceptsViewed || []);
if (viewed.has(cardId)) return;
viewed.add(cardId);
ls.conceptsViewed = Array.from(viewed);
lsSave(ls);
}
/* =========================================================
SOUND-FEEDBACK (Web Audio, kein Asset-Download)
========================================================= */
let _audioCtx = null;
function _ctx() {
if (!_audioCtx && (window.AudioContext || window.webkitAudioContext)) {
_audioCtx = new (window.AudioContext || window.webkitAudioContext)();
}
return _audioCtx;
}
function _beep(freq, durMs, type='sine', gain=0.18, atOffsetMs=0) {
const ctx = _ctx(); if (!ctx) return;
const t = ctx.currentTime + atOffsetMs/1000;
const osc = ctx.createOscillator();
const g = ctx.createGain();
osc.type = type;
osc.frequency.setValueAtTime(freq, t);
g.gain.setValueAtTime(0, t);
g.gain.linearRampToValueAtTime(gain, t + 0.012);
g.gain.exponentialRampToValueAtTime(0.001, t + durMs/1000);
osc.connect(g); g.connect(ctx.destination);
osc.start(t); osc.stop(t + durMs/1000 + 0.05);
}
function playSound(kind) {
switch (kind) {
case 'correct': _beep(523, 90); _beep(659, 110, 'sine', 0.18, 90); break;
case 'wrong': _beep(392, 140, 'triangle', 0.14); _beep(330, 160, 'triangle', 0.12, 140); break;
case 'muell': _beep(180, 220, 'sawtooth', 0.16); _beep(120, 280, 'sawtooth', 0.14, 100); break;
case 'success': _beep(523, 90); _beep(659, 90, 'sine', 0.18, 90); _beep(784, 90, 'sine', 0.18, 180); _beep(1047, 220, 'sine', 0.20, 270); break;
case 'mixed': _beep(523, 90); _beep(659, 90, 'sine', 0.18, 90); _beep(622, 220, 'sine', 0.18, 180); break;
case 'reject': _beep(220, 280, 'sawtooth', 0.18); _beep(165, 360, 'sawtooth', 0.16, 200); break;
case 'place': _beep(880, 50, 'sine', 0.12); break;
}
}
/* =========================================================
STARTSCREEN
========================================================= */
function renderScenarioGrid() {
const grid = document.getElementById('scenarioGrid');
grid.innerHTML = '';
const ls = lsLoad();
const playedMap = ls.scenarios || {};
Object.values(SCENARIOS).forEach(sc => {
const card = document.createElement('div');
card.className = 'scenario-card';
if (sc.locked) card.classList.add('locked');
if (sc.badgeType === 'failed') card.classList.add('failed');
const stats = playedMap[sc.id];
let footer = '';
if (sc.locked) {
footer = '';
} else if (stats) {
const stars = Math.max(0, Math.min(5, stats.bestStars || 0));
const starHtml = '★'.repeat(stars) + `<span class="star-empty">${'★'.repeat(5-stars)}</span>`;
const plays = stats.plays || 0;
footer = `
<div class="scenario-card-stars">
<span class="stars-glyph">${starHtml}</span>
<span>${plays}× bearbeitet</span>
${stars === 5 ? '<span class="scenario-card-played-marker">✓ MEISTERSCHAFT</span>' : ''}
</div>`;
} else {
footer = `<div class="scenario-card-stars no-plays">Noch nicht bearbeitet</div>`;
}
card.innerHTML = `
${sc.badge ? `<div class="badge">${sc.locked ? 'Bald' : sc.badge}</div>` : ''}
<div class="icon">${sc.icon}</div>
<h3>${sc.title}</h3>
<p>${sc.teaser}</p>
${footer}
`;
if (!sc.locked) card.addEventListener('click', () => startScenario(sc.id));
grid.appendChild(card);
});
}
/* =========================================================
ACHIEVEMENTS-LEISTE AUF STARTSCREEN
========================================================= */
function renderAchievementsStrip() {
const strip = document.getElementById('achievementsStrip');
if (!strip) return;
const unlockedMap = loadAchievements();
const unlockedCount = Object.keys(unlockedMap).length;
// Erst anzeigen, sobald mindestens einer freigeschaltet ist
strip.classList.toggle('has-any', unlockedCount > 0);
if (unlockedCount === 0) { strip.innerHTML = ''; return; }
const total = ACHIEVEMENTS.length;
strip.innerHTML = `
<div class="achievements-strip-head">
<div class="achievements-strip-title">🏅 Deine Erfolge</div>
<div class="achievements-strip-count">${unlockedCount} / ${total}</div>
</div>
<div class="achievements-grid">
${ACHIEVEMENTS.map(a => {
const isUnlocked = !!unlockedMap[a.id];
return `
<div class="ach-badge ${isUnlocked ? 'unlocked' : ''}" title="${a.desc}">
<span class="ach-badge-emoji">${a.emoji}</span>
<div class="ach-badge-name">${a.name}</div>
<div class="ach-badge-desc">${isUnlocked ? a.desc : 'Noch nicht erreicht'}</div>
</div>
`;
}).join('')}
</div>
`;
}
/* =========================================================
SZENARIO STARTEN
========================================================= */
function startScenario(id) {
state.scenario = SCENARIOS[id];
state.stepIndex = 0;
resetTracking();
showScreen('screen-play');
renderStep();
}
function restartScenario() {
if (!state.scenario) return;
state.stepIndex = 0;
resetTracking();
showScreen('screen-play');
renderStep();
}
function resetTracking() {
state.firstTryHits = 0;
state.totalAttempts = 0;
state.muelleimerCount = 0;
state.attemptsByStep = [];
state.detoursByStep = [];
state.reshufflesByStep = [];
state.totalReshuffles = 0;
state.startedAt = Date.now();
}
// Probierzug: Schüler:in zieht eine bereits platzierte Karte wieder aus dem
// Slot. Zeichen von Unsicherheit/Trial-and-Error — zählt pro Schritt und
// verhindert, dass der Schritt als „sauber auf Anhieb" gewertet wird.
function registerReshuffle() {
state.reshufflesByStep[state.stepIndex] = (state.reshufflesByStep[state.stepIndex] || 0) + 1;
state.totalReshuffles++;
}
/* =========================================================
AKTUELLEN SCHRITT RENDERN
========================================================= */
function renderStep() {
const sc = state.scenario;
const step = sc.steps[state.stepIndex];
// Scene & Akteur
document.getElementById('actorName').textContent = sc.actor.name;
document.getElementById('actorRole').textContent = sc.actor.role;
document.getElementById('actorAvatar').innerHTML = `
<svg viewBox="0 0 44 44" width="44" height="44">
<circle cx="22" cy="22" r="22" fill="#e8c547"/>
<circle cx="22" cy="18" r="6" fill="#1f4b37"/>
<path d="M 9,40 Q 22,26 35,40 Z" fill="#1f4b37"/>
</svg>
`;
document.getElementById('sceneIllustration').innerHTML = SCENES[step.scene] || SCENES.home;
document.getElementById('sceneNarration').innerHTML = state.stepIndex === 0
? `${sc.intro}<br><br>${step.narration}`
: step.narration;
document.getElementById('sceneQuestion').textContent = step.question;
// Antwort-Bereich aufbauen
state.selectedCardId = null;
state.slotState = [];
state.answered = false;
const area = document.getElementById('answerArea');
area.innerHTML = '';
document.getElementById('feedbackBar').classList.remove('active', 'success', 'partial');
document.getElementById('continueBtn').style.display = 'none';
// Inline-Weiter-Button aus dem letzten Step entfernen
document.querySelectorAll('.inline-next-btn').forEach(e => e.remove());
if (step.type === 'pick') renderPickStep(area, step);
else if (step.type === 'sentence') renderSentenceStep(area, step);
else if (step.type === 'animation') renderAnimationStep(area, step);
else if (step.type === 'multichoice') renderMultichoiceStep(area, step);
renderProgress();
renderProcessFlow('compact');
}
/* =========================================================
STEP-TYP: PICK eine Karte aus Pool wählen
========================================================= */
function renderPickStep(area, step) {
// Nur ein Slot
state.slotState = [{ cardId: null, accepts: 'any' }];
// Single-Slot-Anzeige
const slotWrap = document.createElement('div');
slotWrap.style.textAlign = 'center';
slotWrap.style.margin = '0 0 22px';
slotWrap.innerHTML = `
<div class="slot highlight" id="slot-0" style="min-width:200px; height:60px; font-size:1.05rem;">
<span style="opacity:0.6;">Karte hier ablegen</span>
</div>
`;
area.appendChild(slotWrap);
// Pool (Profi-Modus: ohne offensichtliche Mülleimer-Karten)
const pool = document.createElement('div');
pool.className = 'card-pool';
const cardIds = effectivePickPool(step);
cardIds.forEach(cardId => {
pool.appendChild(buildConceptCard(cardId));
});
area.appendChild(pool);
// Slot-Klick = Karte rauswerfen
document.getElementById('slot-0').addEventListener('click', () => {
if (state.answered) return;
if (state.slotState[0].cardId) { registerReshuffle(); clearSlot(0); }
});
}
/* =========================================================
STEP-TYP: SENTENCE Lückentext mit mehreren Slots
========================================================= */
function renderSentenceStep(area, step) {
// Slot-Zustand initialisieren
state.slotState = step.slots.map(s => ({ cardId: null, accepts: s.accepts }));
// Satz-Template aufbauen mit Slots
const sentence = document.createElement('div');
sentence.className = 'sentence-template';
let html = step.template;
step.slots.forEach((s, i) => {
html = html.replace(`{${i}}`,
`<span class="slot highlight" id="slot-${i}" data-accepts="${s.accepts}">
<span style="opacity:0.6;">…</span>
</span>`
);
});
sentence.innerHTML = html;
area.appendChild(sentence);
// Slot-Klick = Karte rauswerfen
step.slots.forEach((_, i) => {
document.getElementById(`slot-${i}`).addEventListener('click', () => {
if (state.answered) return;
if (state.slotState[i].cardId) { registerReshuffle(); clearSlot(i); }
});
});
// Pool ggf. gruppiert. Im Profi-Modus: Gruppen-Labels ausblenden (weniger Hilfe)
if (step.poolGroups) {
const hideLabels = state.difficulty === 'expert';
step.poolGroups.forEach(grp => {
const group = document.createElement('div');
group.className = 'pool-group';
if (!hideLabels) {
group.innerHTML = `<div class="pool-group-label">${grp.label}</div>`;
}
const pool = document.createElement('div');
pool.className = 'card-pool';
grp.cards.forEach(cardId => {
pool.appendChild(buildConceptCard(cardId, grp.slotKey));
});
group.appendChild(pool);
area.appendChild(group);
});
} else {
const pool = document.createElement('div');
pool.className = 'card-pool';
step.pool.forEach(cardId => pool.appendChild(buildConceptCard(cardId)));
area.appendChild(pool);
}
}
/* =========================================================
STEP-TYP: ANIMATION — SVG-Animation läuft, dann Weiter
========================================================= */
function renderAnimationStep(area, step) {
// Daten: 27 Mitgliedstaaten mit (x,y)-Position und MEP-Anzahl
const COUNTRIES = [
{ c:'PT', x: 90, y:225, n: 21 }, { c:'ES', x:130, y:225, n: 61 },
{ c:'FR', x:200, y:200, n: 81 }, { c:'IT', x:265, y:240, n: 76 },
{ c:'BE', x:225, y:155, n: 22 }, { c:'NL', x:230, y:130, n: 31 },
{ c:'LU', x:240, y:165, n: 6 }, { c:'IE', x:155, y:130, n: 14 },
{ c:'DE', x:280, y:170, n: 96 }, { c:'DK', x:290, y:115, n: 15 },
{ c:'PL', x:355, y:165, n: 53 }, { c:'CZ', x:320, y:185, n: 21 },
{ c:'AT', x:310, y:205, n: 20 }, { c:'SK', x:355, y:200, n: 15 },
{ c:'HU', x:355, y:215, n: 21 }, { c:'SI', x:325, y:220, n: 9 },
{ c:'HR', x:340, y:235, n: 12 }, { c:'RO', x:400, y:230, n: 33 },
{ c:'BG', x:410, y:255, n: 17 }, { c:'GR', x:395, y:285, n: 21 },
{ c:'EE', x:395, y:100, n: 7 }, { c:'LV', x:395, y:120, n: 9 },
{ c:'LT', x:390, y:140, n: 11 }, { c:'FI', x:380, y: 75, n: 15 },
{ c:'SE', x:325, y: 80, n: 21 }, { c:'CY', x:455, y:300, n: 6 },
{ c:'MT', x:285, y:295, n: 6 }
];
const TARGET_X = 240, TARGET_Y = 360; // Brüssel-Halbrund-Mitte
// Punkte SVG: pro Land ein Cluster, Animation per CSS keyframe
const pointsHtml = COUNTRIES.map((co, i) => {
const dotsPerCountry = Math.max(1, Math.round(co.n / 4));
return Array.from({length: dotsPerCountry}, (_, k) => {
const jitterX = (Math.cos(k * 1.7) * 6).toFixed(1);
const jitterY = (Math.sin(k * 1.3) * 6).toFixed(1);
const cx = (co.x + parseFloat(jitterX)).toFixed(1);
const cy = (co.y + parseFloat(jitterY)).toFixed(1);
const dx = (TARGET_X - cx + (Math.cos(i + k) * 80)).toFixed(1);
const dy = (TARGET_Y - cy).toFixed(1);
const delay = (Math.random() * 800 + i * 30).toFixed(0);
return `<circle class="mep-dot" cx="${cx}" cy="${cy}" r="2.6"
fill="#1f4b37"
style="--dx:${dx}px; --dy:${dy}px; animation-delay:${delay}ms"/>`;
}).join('');
}).join('');
area.innerHTML = `
<div class="anim-stage">
<svg class="anim-map" viewBox="0 0 500 400" xmlns="http://www.w3.org/2000/svg">
<!-- Stilisierte Europakarte -->
<rect width="500" height="400" fill="#dceadd"/>
<path d="M 80 100 L 140 80 L 200 90 L 250 70 L 300 60 L 360 55 L 420 70 L 440 110 L 450 160 L 440 220 L 420 270 L 400 310 L 350 320 L 300 310 L 250 305 L 200 290 L 150 270 L 110 240 L 90 200 L 80 150 Z"
fill="#e8e4d8" stroke="#1f4b37" stroke-width="1.4" opacity="0.9"/>
<!-- Halbrund-Parlament unten in Brüssel -->
<g class="anim-parliament" transform="translate(${TARGET_X}, ${TARGET_Y})">
<path d="M -90 0 A 90 90 0 0 1 90 0 Z" fill="#f4f1e8" stroke="#1f4b37" stroke-width="2"/>
<path d="M -75 0 A 75 75 0 0 1 75 0" fill="none" stroke="#1f4b37" stroke-width="1" opacity="0.4"/>
<path d="M -60 0 A 60 60 0 0 1 60 0" fill="none" stroke="#1f4b37" stroke-width="1" opacity="0.4"/>
<path d="M -45 0 A 45 45 0 0 1 45 0" fill="none" stroke="#1f4b37" stroke-width="1" opacity="0.4"/>
<path d="M -30 0 A 30 30 0 0 1 30 0" fill="none" stroke="#1f4b37" stroke-width="1" opacity="0.4"/>
<text x="0" y="22" text-anchor="middle" fill="#1f4b37"
font-family="Fraunces, serif" font-weight="700" font-size="13">EU-Parlament Brüssel</text>
</g>
<!-- Brüssel-Stern als Markierung auf der Karte -->
<g transform="translate(225, 158)">
<polygon points="0,-7 2,-2 7,-2 3,2 5,7 0,4 -5,7 -3,2 -7,-2 -2,-2"
fill="#e8c547" stroke="#1f4b37" stroke-width="1"/>
</g>
<!-- Punkte (eine pro ~4 MEPs) -->
${pointsHtml}
</svg>
<div class="anim-counter">
<span class="anim-counter-num" id="animCounter">0</span>
<span class="anim-counter-label">Abgeordnete</span>
</div>
</div>
`;
// Counter hochzählen synchron zur Animation
const animCounter = document.getElementById('animCounter');
const totalMeps = 720;
const animMs = step.durationMs || 5800;
const startTs = performance.now();
function tick(t) {
const p = Math.min(1, (t - startTs) / animMs);
const eased = 1 - Math.pow(1 - p, 2);
animCounter.textContent = Math.round(eased * totalMeps);
if (p < 1) requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
// Continue-Button nach Ablauf — Animation gilt als „richtig durchlaufen"
state.answered = true;
setTimeout(() => {
state.totalAttempts++;
state.attemptsByStep[state.stepIndex] = 1;
state.firstTryHits++;
document.getElementById('continueBtn').style.display = 'inline-flex';
playSound('correct');
}, animMs + 200);
}
/* =========================================================
STEP-TYP: MULTICHOICE — 4 Buttons, einer korrekt
========================================================= */
function renderMultichoiceStep(area, step) {
const opts = step.options.map((o, i) => `
<button class="mc-btn" data-idx="${i}" type="button">${o.label}</button>
`).join('');
area.innerHTML = `<div class="mc-grid">${opts}</div>`;
area.querySelectorAll('.mc-btn').forEach(btn => {
btn.addEventListener('click', () => evaluateMultichoice(parseInt(btn.dataset.idx, 10)));
});
}
function evaluateMultichoice(idx) {
if (state.answered) return;
const step = state.scenario.steps[state.stepIndex];
const opt = step.options[idx];
const correct = !!opt.correct;
state.answered = true;
state.totalAttempts++;
state.attemptsByStep[state.stepIndex] = (state.attemptsByStep[state.stepIndex] || 0) + 1;
if (correct && state.attemptsByStep[state.stepIndex] === 1) state.firstTryHits++;
const btn = document.querySelector(`.mc-btn[data-idx="${idx}"]`);
const fb = document.getElementById('feedbackBar');
if (correct) {
playSound('correct');
btn.classList.add('mc-correct');
fb.innerHTML = `<strong>✓ Richtig!</strong> ${opt.feedback}`;
fb.classList.add('active', 'success');
document.getElementById('continueBtn').style.display = 'inline-flex';
// Inline-Weiter-Button neben der richtigen Antwort
insertInlineNextButton(btn, 'afterend');
} else {
playSound('wrong');
btn.classList.add('mc-wrong');
btn.disabled = true;
// Weicher Fehlversuch — als „probiert"-Abzweigung dokumentieren
state.detoursByStep[state.stepIndex] = state.detoursByStep[state.stepIndex] || [];
// Kurz-Label aus dem Option-Text (max ~28 Zeichen)
const shortLbl = (opt.label || '').length > 28 ? (opt.label.slice(0, 25) + '…') : (opt.label || '');
if (!state.detoursByStep[state.stepIndex].some(d => d.label === shortLbl)) {
state.detoursByStep[state.stepIndex].push({ cardId: null, label: shortLbl, kind: 'soft' });
renderProcessFlow('compact');
}
fb.innerHTML = `<strong>Hmm…</strong> ${opt.feedback}`;
fb.classList.add('active', 'partial');
setTimeout(() => {
fb.classList.remove('active', 'partial');
state.answered = false;
}, 2400);
}
}
/* =========================================================
KONZEPT-KARTE bauen
========================================================= */
function buildConceptCard(cardId, slotKey) {
const c = CONCEPTS[cardId];
const card = document.createElement('div');
card.className = 'concept-card';
card.dataset.cardId = cardId;
if (slotKey) card.dataset.slotKey = slotKey;
const tagsHtml = (c.tags || []).map(t =>
`<span class="concept-card-tag">${t}</span>`
).join('');
card.innerHTML = `
<div class="concept-card-viz">
${c.detailSvg()}
<button class="concept-card-info-btn" onclick="event.stopPropagation(); showConceptDetail('${cardId}')" title="Mehr Infos">i</button>
</div>
<div class="concept-card-body">
<div class="concept-card-label">${c.label}</div>
<div class="concept-card-tags">${tagsHtml}</div>
</div>
`;
// Klick auf Karte: erst auswählen, dann beim nächsten Klick auf Slot platzieren
card.addEventListener('click', () => {
if (state.answered) return;
if (card.classList.contains('placed')) return;
if (state.selectedCardId === cardId && card.classList.contains('selected')) {
card.classList.remove('selected');
state.selectedCardId = null;
return;
}
document.querySelectorAll('.concept-card.selected').forEach(c => c.classList.remove('selected'));
card.classList.add('selected');
state.selectedCardId = cardId;
const targetSlotIdx = findFreeSlotForCard(cardId, slotKey);
if (targetSlotIdx !== -1) {
placeCard(cardId, targetSlotIdx);
}
});
return card;
}
/* =========================================================
PASSENDEN SLOT FINDEN
========================================================= */
function findFreeSlotForCard(cardId, slotKey) {
for (let i = 0; i < state.slotState.length; i++) {
if (state.slotState[i].cardId) continue; // bereits belegt
const accepts = state.slotState[i].accepts;
if (accepts === 'any' || accepts === slotKey) return i;
}
return -1;
}
/* =========================================================
KARTE IN SLOT PLATZIEREN
========================================================= */
function placeCard(cardId, slotIdx) {
state.slotState[slotIdx].cardId = cardId;
playSound('place');
const slotEl = document.getElementById(`slot-${slotIdx}`);
const c = CONCEPTS[cardId];
slotEl.classList.remove('highlight');
slotEl.classList.add('filled');
slotEl.innerHTML = `<span class="slot-card-icon">●</span> ${c.label}`;
// Karte im Pool als "platziert" markieren
document.querySelectorAll(`.concept-card[data-card-id="${cardId}"]`).forEach(el => {
el.classList.add('placed');
el.classList.remove('selected');
});
state.selectedCardId = null;
// Wenn alle Slots gefüllt → auswerten
if (state.slotState.every(s => s.cardId)) {
setTimeout(evaluateStep, 350);
}
}
/* =========================================================
SLOT LEEREN
========================================================= */
function clearSlot(slotIdx) {
const cardId = state.slotState[slotIdx].cardId;
if (!cardId) return;
state.slotState[slotIdx].cardId = null;
const slotEl = document.getElementById(`slot-${slotIdx}`);
slotEl.classList.remove('filled', 'filled-correct', 'filled-wrong');
slotEl.classList.add('highlight');
slotEl.innerHTML = `<span style="opacity:0.6;">…</span>`;
document.querySelectorAll(`.concept-card[data-card-id="${cardId}"]`).forEach(el => {
el.classList.remove('placed');
});
}
/* =========================================================
SCHRITT AUSWERTEN
========================================================= */
function evaluateStep() {
if (state.answered) return;
const step = state.scenario.steps[state.stepIndex];
// Korrektheit prüfen
let correct = false;
let placedIds = state.slotState.map(s => s.cardId);
if (step.type === 'pick') {
correct = (placedIds[0] === step.correct);
} else if (step.type === 'sentence') {
// Reihenfolge ist egal, wenn alle Slots denselben accepts-Typ haben
// (= Karten kommen aus dem gleichen Pool, semantisch austauschbar) ODER
// explizit slotsInterchangeable gesetzt ist.
const acceptsList = step.slots.map(s => s.accepts);
const allSameAccepts = acceptsList.length > 1 && acceptsList.every(a => a === acceptsList[0]);
const orderIrrelevant = allSameAccepts || step.slotsInterchangeable;
if (orderIrrelevant) {
const correctSet = step.correctSet || step.slots.map(s => s.correct);
const sortedPlaced = [...placedIds].sort();
const sortedCorrect = [...correctSet].sort();
correct = JSON.stringify(sortedPlaced) === JSON.stringify(sortedCorrect);
} else {
correct = step.slots.every((s, i) => placedIds[i] === s.correct);
}
}
state.answered = true;
// === Tracking ===
state.totalAttempts++;
state.attemptsByStep[state.stepIndex] = (state.attemptsByStep[state.stepIndex] || 0) + 1;
// Sauberer Erstversuch NUR ohne Probierzüge (Karten rein-/rausgezogen) auf
// diesem Schritt — sonst war es Trial-and-Error, kein „auf Anhieb".
if (correct && state.attemptsByStep[state.stepIndex] === 1 && !state.reshufflesByStep[state.stepIndex]) {
state.firstTryHits++;
}
if (correct) {
playSound('correct');
// Slots grün
state.slotState.forEach((_, i) => {
const el = document.getElementById(`slot-${i}`);
el.classList.remove('filled');
el.classList.add('filled-correct');
});
const fb = document.getElementById('feedbackBar');
fb.innerHTML = `<strong>✓ Richtig!</strong> ${step.feedbackCorrect}`;
fb.classList.add('active', 'success');
// Falls Meilenstein → erst Overlay
if (step.milestone) {
setTimeout(() => {
document.getElementById('milestoneTitle').textContent = step.milestone.title;
document.getElementById('milestoneText').innerHTML = step.milestone.text;
showOverlay('overlayMilestone');
}, 700);
} else {
document.getElementById('continueBtn').style.display = 'inline-flex';
// Inline-Weiter-Button neben dem richtigen Ergebnis platzieren
if (step.type === 'pick') {
const slot = document.getElementById('slot-0');
if (slot) insertInlineNextButton(slot, 'afterend');
} else if (step.type === 'sentence') {
const tmpl = document.querySelector('.sentence-template');
if (tmpl) insertInlineNextButton(tmpl, 'afterend');
}
}
} else {
// FALSCH: Slots rot, dann Reaktion je nach Step-Logik
state.slotState.forEach((_, i) => {
const el = document.getElementById(`slot-${i}`);
el.classList.remove('filled');
el.classList.add('filled-wrong');
});
// Pick-Step mit kartenspezifischem Feedback
if (step.type === 'pick' && step.feedbackByCard) {
const cardSpec = step.feedbackByCard[placedIds[0]];
if (cardSpec && cardSpec.isMuelleimer) {
state.muelleimerCount++;
// Sackgasse (Mülleimer) für die Prozessleiste merken
state.detoursByStep[state.stepIndex] = state.detoursByStep[state.stepIndex] || [];
state.detoursByStep[state.stepIndex].push({
cardId: placedIds[0],
label: (CONCEPTS[placedIds[0]] && CONCEPTS[placedIds[0]].label) || placedIds[0],
kind: 'muelleimer'
});
renderProcessFlow('compact');
playSound('muell');
const stage = document.querySelector('.scene-stage');
if (stage) {
stage.classList.add('shaking');
setTimeout(() => stage.classList.remove('shaking'), 600);
}
setTimeout(() => {
document.getElementById('wrongText').innerHTML = cardSpec.text;
showOverlay('overlayWrong');
}, 700);
} else if (cardSpec) {
playSound('wrong');
// Weicher Fehlversuch — dokumentieren als „probiert"-Abzweigung
state.detoursByStep[state.stepIndex] = state.detoursByStep[state.stepIndex] || [];
const lbl = (CONCEPTS[placedIds[0]] && CONCEPTS[placedIds[0]].label) || placedIds[0];
// Doppelte Picks der gleichen Karte nicht erneut aufzeichnen
if (!state.detoursByStep[state.stepIndex].some(d => d.cardId === placedIds[0])) {
state.detoursByStep[state.stepIndex].push({ cardId: placedIds[0], label: lbl, kind: 'soft' });
renderProcessFlow('compact');
}
// Sanfter Hinweis und nochmal versuchen
const fb = document.getElementById('feedbackBar');
fb.innerHTML = `<strong>Hmm…</strong> ${cardSpec.text}`;
fb.classList.add('active', 'partial');
setTimeout(() => {
// Slot leeren und erneuten Versuch erlauben
state.answered = false;
clearSlot(0);
}, 2200);
}
} else if (step.type === 'sentence') {
// Sentence: bei wrongIsMuelleimer → Mülleimer, sonst sanfter Hinweis
if (step.wrongIsMuelleimer) {
state.muelleimerCount++;
// Sackgasse (Mülleimer) für die Prozessleiste merken
state.detoursByStep[state.stepIndex] = state.detoursByStep[state.stepIndex] || [];
state.detoursByStep[state.stepIndex].push({
cardId: null,
label: 'Falsche Kombi',
kind: 'muelleimer'
});
renderProcessFlow('compact');
playSound('muell');
const stage = document.querySelector('.scene-stage');
if (stage) {
stage.classList.add('shaking');
setTimeout(() => stage.classList.remove('shaking'), 600);
}
setTimeout(() => {
document.getElementById('wrongText').innerHTML = step.feedbackWrong || 'Die Karten passen so nicht zusammen.';
showOverlay('overlayWrong');
}, 700);
} else {
playSound('wrong');
// Weicher Fehlversuch — als „probiert"-Abzweigung dokumentieren
state.detoursByStep[state.stepIndex] = state.detoursByStep[state.stepIndex] || [];
state.detoursByStep[state.stepIndex].push({
cardId: null,
label: 'Falsche Kombi',
kind: 'soft'
});
renderProcessFlow('compact');
const fb = document.getElementById('feedbackBar');
fb.innerHTML = `<strong>Hmm…</strong> ${step.feedbackWrong}`;
fb.classList.add('active', 'partial');
setTimeout(() => {
state.answered = false;
state.slotState.forEach((_, i) => clearSlot(i));
}, 2400);
}
}
}
}
/* =========================================================
ADVANCE / NÄCHSTER SCHRITT
advanceStep() wird vom Weiter-Button & Meilenstein-Overlay
aufgerufen — zeigt vorher die InfoCard, wenn vorhanden.
========================================================= */
/* Inline-Weiter-Button neben dem richtigen Ergebnis — kein Scroll nötig.
Der untere Weiter-Button im actions-row bleibt zusätzlich erhalten. */
function insertInlineNextButton(targetEl, position) {
if (!targetEl) return;
// Vorhandene Inline-Buttons entfernen (max. einer aktiv)
document.querySelectorAll('.inline-next-btn').forEach(e => e.remove());
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'btn btn-primary inline-next-btn';
btn.innerHTML = 'Weiter →';
btn.onclick = function() { advanceStep(); };
targetEl.insertAdjacentElement(position || 'afterend', btn);
}
function advanceStep() {
const step = state.scenario.steps[state.stepIndex];
if (step && step.funFact) {
document.getElementById('funFactEmoji').textContent = step.funFact.emoji || '💡';
document.getElementById('funFactTitle').textContent = step.funFact.title || 'Wusstest du?';
document.getElementById('funFactText').innerHTML = step.funFact.text || '';
showOverlay('overlayFunFact');
return;
}
nextStep();
}
function nextStep() {
state.stepIndex++;
if (state.stepIndex >= state.scenario.steps.length) {
showEnd();
} else {
renderStep();
}
}
/* =========================================================
ENDSCREEN
========================================================= */
const END_ILLUSTRATIONS = {
success: `<svg class="end-illustration" viewBox="0 0 200 200" width="160" height="160">
<circle cx="100" cy="100" r="90" fill="#e8c547"/>
<circle cx="100" cy="100" r="70" fill="#f4f1e8"/>
<path d="M 65,100 L 90,125 L 140,75" fill="none" stroke="#1f4b37" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
</svg>`,
mixed: `<svg class="end-illustration" viewBox="0 0 200 200" width="160" height="160">
<circle cx="100" cy="100" r="90" fill="#e8c547"/>
<circle cx="100" cy="100" r="70" fill="#f4f1e8"/>
<path d="M 60,100 L 140,100" stroke="#1f4b37" stroke-width="8" stroke-linecap="round"/>
<path d="M 100,70 L 100,90 M 100,110 L 100,130" stroke="#1f4b37" stroke-width="8" stroke-linecap="round" opacity="0.4"/>
</svg>`,
rejected: `<svg class="end-illustration" viewBox="0 0 200 200" width="160" height="160">
<circle cx="100" cy="100" r="90" fill="#b04a3e"/>
<circle cx="100" cy="100" r="70" fill="#f4f1e8"/>
<line x1="65" y1="100" x2="135" y2="100" stroke="#b04a3e" stroke-width="10" stroke-linecap="round"/>
</svg>`
};
function showEnd() {
const end = state.scenario.end;
const endType = end.type || 'success';
document.getElementById('endTitle').textContent = end.title;
document.getElementById('endSubtitle').textContent = end.subtitle;
document.getElementById('realStory').textContent = end.realStory;
document.getElementById('endIllustrationWrap').innerHTML = END_ILLUSTRATIONS[endType] || END_ILLUSTRATIONS.success;
// Klasse setzen für Farbe
const endCard = document.getElementById('endCard');
endCard.classList.remove('end-success', 'end-mixed', 'end-rejected');
endCard.classList.add('end-' + endType);
// Akten-Stempel
const stamp = document.getElementById('endStamp');
if (stamp) {
const stampText = endType === 'success' ? 'Gesetz' :
endType === 'rejected' ? 'Mülleimer' :
'Gemischt';
stamp.textContent = stampText;
stamp.className = 'end-stamp stamp-' + endType;
// Animation neu triggern (durch Re-add der Class)
stamp.style.animation = 'none';
void stamp.offsetWidth;
stamp.style.animation = '';
}
playSound(endType === 'success' ? 'success' : (endType === 'rejected' ? 'reject' : 'mixed'));
// === Statistik + Sterne berechnen ===
const stepsTotal = state.scenario.steps.length;
const firstTryRate = stepsTotal > 0 ? state.firstTryHits / stepsTotal : 0;
const stars = firstTryRate >= 1.00 ? 5
: firstTryRate >= 0.85 ? 4
: firstTryRate >= 0.65 ? 3
: firstTryRate >= 0.40 ? 2 : 1;
// Sterne + Stats unter den Endscreen einfügen
const statsEl = document.getElementById('endStats');
if (statsEl) {
const starHtml = '★'.repeat(stars) + '<span style="opacity:.3">' + '★'.repeat(5-stars) + '</span>';
statsEl.innerHTML = `
<div class="stars">${starHtml}</div>
<div class="stats-row">
<span><strong>${state.firstTryHits}</strong>/${stepsTotal} auf Anhieb richtig</span>
<span><strong>${state.totalAttempts}</strong> Versuche</span>
${state.totalReshuffles > 0 ? `<span>🔄 <strong>${state.totalReshuffles}</strong> Probierzüge</span>` : ''}
${state.muelleimerCount > 0 ? `<span><strong>${state.muelleimerCount}×</strong> im Mülleimer</span>` : ''}
</div>
`;
}
// === Aggregat in localStorage merken ===
const ls = lsLoad();
ls.scenarios = ls.scenarios || {};
const prev = ls.scenarios[state.scenario.id];
ls.scenarios[state.scenario.id] = {
bestStars: Math.max(prev?.bestStars || 0, stars),
plays: (prev?.plays || 0) + 1,
lastEndType: endType,
lastPlayedAt: Date.now(),
};
lsSave(ls);
// === Achievements auswerten + freischalten ===
const newAchievements = evaluateAchievements({
scenarioId: state.scenario.id,
stars,
firstTryHits: state.firstTryHits,
stepsTotal,
muelleimerCount: state.muelleimerCount,
difficulty: state.difficulty,
endType,
});
// Endscreen-Achievement-Block einfügen
const achEl = document.getElementById('endAchievements');
if (achEl) {
if (newAchievements.length) {
const items = newAchievements.map(id => {
const a = ACHIEVEMENTS.find(x => x.id === id);
return a ? `<li><span class="ach-emoji">${a.emoji}</span> <strong>${a.name}</strong> — ${a.desc}</li>` : '';
}).join('');
achEl.innerHTML = `
<div class="end-ach-label">🎉 Neu freigeschaltet</div>
<ul class="end-ach-list">${items}</ul>
`;
achEl.style.display = 'block';
} else {
achEl.style.display = 'none';
achEl.innerHTML = '';
}
}
// === Endbericht an Klassen-Plattform übermitteln ===
submitToBackend({
stars,
durationMs: Date.now() - state.startedAt,
data: {
scenarioId: state.scenario.id,
scenarioTitle: state.scenario.title,
endType,
stepsTotal,
firstTryHits: state.firstTryHits,
totalAttempts: state.totalAttempts,
muelleimerCount: state.muelleimerCount,
attemptsByStep: state.attemptsByStep,
detoursByStep: state.detoursByStep,
reshufflesByStep: state.reshufflesByStep,
totalReshuffles: state.totalReshuffles,
difficulty: state.difficulty,
newAchievements,
},
});
renderProcessFlow('full');
showScreen('screen-end');
}
async function submitToBackend(payload) {
// Nur in Klassen-Sessions sinnvoll. Im Free/Local-Mode keine Session-Cookie → 401, ignorieren.
if (!window.EUW_SESSION_ID) return;
try {
await fetch(EUW_API_BASE + '/progress', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'same-origin',
body: JSON.stringify({
action: 'complete',
simId: 'eu-werkstatt',
...payload,
}),
});
} catch (err) {
console.log('[eu-werkstatt] submit fehlgeschlagen', err.message);
}
}
/* =========================================================
PROZESSKETTE — Schritt für Schritt
Mode: 'compact' (Spielscreen) | 'full' (Endscreen)
========================================================= */
function renderProcessFlow(mode) {
const containerId = mode === 'full' ? 'processFlowFull' : 'processFlowCompact';
const el = document.getElementById(containerId);
if (!el || !state.scenario || !state.scenario.steps) { if (el) el.innerHTML = ''; return; }
const steps = state.scenario.steps;
const flowData = SCENARIO_FLOWS[state.scenario.id] || { steps: [], outcome: null };
const fragments = [];
steps.forEach((s, i) => {
const flow = flowData.steps[i] || { icon: (i+1), label: `Schritt ${i+1}` };
let cls = 'pf-node';
let isFuture = false;
let isActiveHidden = false;
if (mode === 'full') {
// Am Endscreen sind alle Steps abgeschlossen — kein Spoiler-Problem
cls += ' done';
} else {
if (i < state.stepIndex) cls += ' done';
else if (i === state.stepIndex) { cls += ' active'; isActiveHidden = true; } // aktiv = noch unbeantwortet → Lösung verbergen
else { cls += ' future'; isFuture = true; } // kommende Schritte: kein Spoiler
}
// Im Compact-Modus für aktiven UND künftige Schritte Inhalt verbergen — Lösung erscheint erst, wenn der Schritt erledigt ist.
const hideContent = isFuture || isActiveHidden;
const displayIcon = hideContent ? '?' : (flow.icon || (i+1));
const displayLabel = hideContent ? '—' : (flow.label || '');
const titleAttr = isFuture ? 'Noch nicht entdeckt'
: isActiveHidden ? 'Aktueller Schritt'
: (flow.label || '');
const subHtml = (mode === 'full' && flow.sub) ? `<div class="pf-sub">${flow.sub}</div>` : '';
// Sackgassen unter dem Knoten anzeigen (Mülleimer-Picks)
const detours = isFuture ? [] : (state.detoursByStep[i] || []);
let detoursHtml = '';
if (detours.length) {
detoursHtml = `<div class="pf-detours">`
+ detours.map(d => {
const isM = d.kind === 'muelleimer';
const icon = isM ? '🗑️' : '🔁';
const titlePref = isM ? 'Sackgasse: ' : 'Probiert: ';
return `<div class="pf-detour ${isM ? 'kind-muelleimer' : 'kind-soft'}" title="${titlePref}${d.label.replace(/"/g,'&quot;')}">
<span class="pf-detour-icon">${icon}</span>${d.label}
</div>`;
}).join('')
+ `</div>`;
}
fragments.push(`
<div class="${cls}" title="${titleAttr.replace(/"/g,'&quot;')}">
<div class="pf-circle">${displayIcon}</div>
<div class="pf-label">${displayLabel}</div>
${subHtml}
${detoursHtml}
</div>
`);
});
// Outcome-Knoten am Ende — nur im Full-Modus
if (mode === 'full' && flowData.outcome) {
const o = flowData.outcome;
const type = (state.scenario.end && state.scenario.end.type) || 'success';
fragments.push(`
<div class="pf-node outcome outcome-${type}" title="${(o.label || '').replace(/"/g,'&quot;')}">
<div class="pf-circle">${o.icon || '🏁'}</div>
<div class="pf-label">${o.label || 'Ergebnis'}</div>
${o.sub ? `<div class="pf-sub">${o.sub}</div>` : ''}
</div>
`);
}
el.innerHTML = fragments.join('');
}
/* =========================================================
FORTSCHRITTSANZEIGE
========================================================= */
function renderProgress() {
const bar = document.getElementById('progressBar');
bar.innerHTML = '';
if (!state.scenario || !state.scenario.steps) return;
const total = state.scenario.steps.length;
const current = Math.min(state.stepIndex + 1, total);
const label = document.createElement('span');
label.className = 'progress-label';
label.textContent = `Schritt ${current} / ${total}`;
bar.appendChild(label);
const dots = document.createElement('div');
dots.className = 'progress-dots';
state.scenario.steps.forEach((_, i) => {
const dot = document.createElement('div');
dot.className = 'progress-dot';
if (i < state.stepIndex) dot.classList.add('done');
if (i === state.stepIndex) dot.classList.add('active');
dots.appendChild(dot);
});
bar.appendChild(dots);
}
/* =========================================================
KONZEPT-DETAIL ANZEIGEN
========================================================= */
function toggleConceptZoom() {
const vis = document.getElementById('conceptVisual');
if (!vis) return;
vis.classList.toggle('zoomed');
}
function showConceptDetail(cardId) {
const c = CONCEPTS[cardId];
if (!c) return;
trackConceptViewed(cardId);
document.getElementById('conceptTitle').textContent = c.label;
document.getElementById('conceptSummary').textContent = c.summary;
// Zoom-Modus immer auf normal zurücksetzen + Hint behalten
const visualEl = document.getElementById('conceptVisual');
visualEl.classList.remove('zoomed');
visualEl.innerHTML = c.detailSvg() + '<span class="concept-detail-zoom-hint">🔍 antippen zum Vergrößern</span>';
// Body-Text (Q&A-Stil) HTML-String
const bodyEl = document.getElementById('conceptBody');
if (c.body) {
bodyEl.innerHTML = c.body;
bodyEl.style.display = 'block';
} else {
bodyEl.innerHTML = '';
bodyEl.style.display = 'none';
}
// Faktenliste
const facts = document.getElementById('conceptFacts');
facts.innerHTML = '';
(c.facts || []).forEach(f => {
const li = document.createElement('li');
li.textContent = f;
facts.appendChild(li);
});
document.getElementById('conceptFactsHeading').style.display =
(c.facts && c.facts.length) ? 'block' : 'none';
facts.style.display = (c.facts && c.facts.length) ? 'block' : 'none';
// Fall-spezifischer Kontext: nur wenn Szenario aktiv und passender Eintrag existiert
const caseBox = document.getElementById('conceptCaseContext');
const scenarioId = state.scenario && state.scenario.id;
const ctx = c.caseContext && scenarioId && c.caseContext[scenarioId];
if (ctx) {
caseBox.innerHTML = `<div class="label">In diesem Fall</div>${ctx}`;
caseBox.classList.remove('empty');
} else {
caseBox.innerHTML = '';
caseBox.classList.add('empty');
}
// Detail-Bereich nach oben scrollen
const detailEl = document.querySelector('.concept-detail');
if (detailEl) detailEl.scrollTop = 0;
showOverlay('overlayConcept');
}
/* =========================================================
GLOSSAR
========================================================= */
function openGlossary() {
const grid = document.getElementById('glossaryGrid');
grid.innerHTML = '';
Object.entries(CONCEPTS).forEach(([id, c]) => {
const card = document.createElement('div');
card.className = 'concept-card';
const tagsHtml = (c.tags || []).map(t =>
`<span class="concept-card-tag">${t}</span>`
).join('');
card.innerHTML = `
<div class="concept-card-viz">
${c.detailSvg()}
</div>
<div class="concept-card-body">
<div class="concept-card-label">${c.label}</div>
<div class="concept-card-tags">${tagsHtml}</div>
</div>
`;
card.addEventListener('click', () => {
hideOverlay('overlayGlossary');
setTimeout(() => showConceptDetail(id), 200);
});
grid.appendChild(card);
});
showOverlay('overlayGlossary');
}
/* =========================================================
SCREEN- & OVERLAY-VERWALTUNG
========================================================= */
function showScreen(id) {
document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
document.getElementById(id).classList.add('active');
window.scrollTo({ top: 0, behavior: 'smooth' });
}
function goToStart() {
showScreen('screen-start');
document.getElementById('progressBar').innerHTML = '';
renderScenarioGrid();
renderAchievementsStrip();
}
function showOverlay(id) { document.getElementById(id).classList.add('active'); }
function hideOverlay(id) { document.getElementById(id).classList.remove('active'); }
/* =========================================================
LIVE-STATE FÜR LEHRKRÄFTE
Wird von App/assets/js/live-client.js alle 4s aufgerufen.
Ergebnis landet via /api/live.php im Live-Cockpit.
========================================================= */
window.GGS_LIVE_STATE = function () {
// Aktives Screen erkennen
var screen = 'start';
try {
var endActive = document.getElementById('screen-end')?.classList.contains('active');
var playActive = document.getElementById('screen-play')?.classList.contains('active');
if (endActive) screen = 'end';
else if (playActive) screen = 'play';
} catch (_) {}
var stepsTotal = (state.scenario && state.scenario.steps) ? state.scenario.steps.length : 0;
var stepIdx = state.stepIndex || 0;
var currentStep = (state.scenario && state.scenario.steps) ? state.scenario.steps[stepIdx] : null;
var attemptsThisStep = (state.attemptsByStep && state.attemptsByStep[stepIdx]) || 0;
var reshufflesThisStep = (state.reshufflesByStep && state.reshufflesByStep[stepIdx]) || 0;
var secondsElapsed = state.startedAt ? Math.round((Date.now() - state.startedAt) / 1000) : 0;
// Health-Ampel (für die Lehrer-Live-Übersicht der nächsten Phase)
var health = 'good';
if (screen === 'play') {
if (state.muelleimerCount >= 3 || attemptsThisStep >= 3 || reshufflesThisStep >= 4) health = 'struggling';
else if (state.muelleimerCount >= 1 || attemptsThisStep >= 2 || reshufflesThisStep >= 2) health = 'ok';
} else if (screen === 'end') {
health = 'good';
} else {
health = 'good'; // Startscreen
}
// Achievement-Stand (für den Klassen-Überblick)
var ach;
try { ach = lsLoad().achievements || {}; } catch (_) { ach = {}; }
// End-Typ (success / mixed / rejected) — nur am Endscreen sinnvoll
var endType = null;
var levelWon = false;
if (screen === 'end' && state.scenario && state.scenario.end) {
endType = state.scenario.end.type || 'success';
// „Gewonnen" = success oder mixed (mixed = formal erfolgreich, inhaltlich teilweise).
// Bei rejected (Stop Brexit, Vivisection, Glyphosat-Hardlanding) gilt nicht als Win.
levelWon = (endType === 'success' || endType === 'mixed');
}
return {
screen: screen,
scenarioId: state.scenario ? state.scenario.id : null,
scenarioTitle: state.scenario ? state.scenario.title : null,
scenarioIcon: state.scenario ? state.scenario.icon : null,
step: screen === 'play' ? (stepIdx + 1) : (screen === 'end' ? stepsTotal : 0),
stepsTotal: stepsTotal,
stepType: currentStep ? currentStep.type : null,
difficulty: state.difficulty || 'easy',
firstTryHits: state.firstTryHits || 0,
totalAttempts: state.totalAttempts || 0,
muelleimerCount: state.muelleimerCount || 0,
attemptsThisStep: attemptsThisStep,
reshuffles: state.totalReshuffles || 0,
reshufflesThisStep: reshufflesThisStep,
secondsElapsed: secondsElapsed,
achievementsUnlocked: Object.keys(ach).length,
achievementsTotal: (typeof ACHIEVEMENTS !== 'undefined') ? ACHIEVEMENTS.length : 6,
health: health,
healthLabel: (screen === 'play')
? ('Schritt ' + (stepIdx + 1) + '/' + stepsTotal + (attemptsThisStep > 1 ? ' · ' + attemptsThisStep + '. Versuch' : ''))
: (screen === 'end' ? 'Endscreen erreicht' : 'Szenario-Auswahl'),
endType: endType, // 'success' | 'mixed' | 'rejected' | null
levelWon: levelWon // boolean — kompatibel zu Klima-Spectator-Feldname
};
};
/* =========================================================
START
========================================================= */
loadDifficulty();
// Toggle visuell synchronisieren
document.querySelectorAll('.diff-btn').forEach(b => {
b.classList.toggle('active', b.dataset.diff === state.difficulty);
});
renderScenarioGrid();
renderAchievementsStrip();
</script>
<script src="../../assets/js/ggs-quick-intro.js" defer></script>
<script>
window.addEventListener('DOMContentLoaded', function () {
if (window.GgsQuickIntro) GgsQuickIntro.init({
simId: 'eu-werkstatt',
title: '🏛️ EU-Werkstatt',
lead: 'Du sortierst Karten zu EU-Themen — von Mitgliedsstaaten bis Klimapolitik. Pro Szenario lernst du, wie Politische Bildung in der Praxis aussieht.',
steps: [
'Szenario wählen — jedes hat einen klaren Lerninhalt.',
'Karten richtig zuordnen — per Klick oder Drag.',
'Erste-Versuch-Treffer zählen extra; Mülleimer = bewusster Fehlversuch.',
'Glossar-Begriffe auf den Karten antippen → Tooltips für Fachbegriffe.',
],
goals: [
'Wichtige EU-Institutionen und Prozesse einordnen',
'Politische Begriffe sicher anwenden',
'Argumentationswege in EU-Themen nachvollziehen',
],
});
});
</script>
</body>
</html>