/* Octomat – responsives, ozeanisches Design. Löst sich vom fixen 1024×768. */

/* --------------------------------------------------------------------------
   Schriften – selbst gehostet, kein CDN (wie auf der öffentlichen Startseite).
   --------------------------------------------------------------------------
   Der Browser lädt eine Schriftdatei erst, wenn sie wirklich gebraucht wird:
   Wer beim Ozean bleibt, holt sich keine einzige davon. Nur die Themes,
   die sie in --font oder --font-display eintragen, zahlen dafür. */
@font-face {
  font-family: 'Fraunces';
  src: url('fonts/fraunces-latin.woff2') format('woff2');
  font-weight: 300 900;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2212, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Karla';
  src: url('fonts/karla-latin.woff2') format('woff2');
  font-weight: 300 800;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2212, U+FEFF, U+FFFD;
}

/* --------------------------------------------------------------------------
   Die Marken eines Themes
   --------------------------------------------------------------------------
   Was hier steht, ist zugleich der Ozean – die Welt, die jedes Kind von
   Anfang an hat – und die vollständige Liste dessen, was eine andere Welt
   umstellen darf. Alles im übrigen Stylesheet greift auf diese Namen zu und
   auf keine festen Farbwerte mehr; nur so kann ein Vulkan wirklich dunkel
   werden statt bloss die Ränder umzufärben. */

:root, .theme-ozean {
  /* --- Der Seitenhintergrund ---------------------------------------------
     --page ist das ganze Bild (mehrere Verläufe übereinander), --bg-top /
     --bg / --bg-deep sind seine drei Tiefen. Die drei einzeln braucht, wer
     nur den Ton der Welt aufnehmen will: der Countdown-Schleier über dem
     Spiel und die Himmelsstreifen der Ladenkarten. */
  --bg: #0f3a4d;
  --bg-deep: #0a2836;
  --bg-top: #1a5a72;
  --page: radial-gradient(120% 120% at 50% 0%, var(--bg-top) 0%, var(--bg) 45%, var(--bg-deep) 100%);

  /* --- Flächen ------------------------------------------------------------ */
  --surface: #ffffff;        /* Karten, Felder, Tastaturtasten */
  --surface-soft: #eef6fa;   /* ruhigere Fläche innerhalb einer Karte */
  --card-lift: #ffffff;      /* hervorgehobene Kachel (verdiente Medaille) */
  --key-bg: #ffffff;         /* Zifferntasten im Spiel */
  --tint: #eef6fa;           /* leicht abgesetzter Block in einer Karte */
  --tint-line: #d7e6ee;
  --line: #cfe0e7;           /* feiner Rand auf einer Karte */
  --line-soft: #e3edf2;
  --press: #d9ebf1;          /* dieselbe Fläche, gedrückt oder überfahren */
  --dash: #9fb4bf;           /* gestrichelte „noch nicht fertig"-Kontur */
  --dash-bg: #f7fbfd;
  --empty-bg: #eef4f7;       /* leerer Ergebniskasten, unerreichte Sterne */
  --empty-ink: #b3c2cb;
  --empty-line: #cddbe3;

  /* --- Schrift ------------------------------------------------------------ */
  --ink: #12313f;
  --ink-soft: #5b7885;
  --on-dark: #eaf6fb;        /* Schrift direkt auf dem Seitenhintergrund */
  --on-dark-soft: rgba(234, 246, 251, 0.82);
  --on-dark-faint: rgba(234, 246, 251, 0.7);
  --on-accent: #fff;         /* Schrift auf einer Fläche in der Akzentfarbe */

  /* --- Signalfarben -------------------------------------------------------
     Grün heisst richtig, Rot heisst falsch, Gold heisst Stern – in jeder
     Welt derselbe Ton. Eine Rückmeldung im Spiel darf nicht je nach Anstrich
     anders zu lesen sein. Umgestellt wird nur, was sie tragen muss: die
     blasse Fläche dahinter (--*-bg) und die dunklere Textvariante (--*-ink),
     denn beide hängen daran, ob die Karte hell oder dunkel ist. */
  --good: #2fbf71;
  --good-ink: #269b5b;
  --good-bg: #e6f7ec;
  --bad: #ef5b57;
  --bad-bg: #fdecec;
  --star: #f5b301;
  --star-ink: #b6860d;
  /* Dasselbe Gold, aber auf dem Seitenhintergrund statt auf einer Karte. In
     den dunklen Welten ist das --star selbst; in den hellen wäre es dort
     nicht zu lesen, die setzen den dunkleren Ton. */
  --star-on-bg: var(--star);

  /* --- Akzent ------------------------------------------------------------- */
  --accent: #1fb6d6;
  --accent-strong: #0d8fb0;
  --accent-lift: #4fd0e8;    /* das hellere Ende des Verlaufs im Lern-Knopf */

  /* --- Was auf dem Hintergrund liegt, nicht auf einer Karte --------------- */
  --shell-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.09), rgba(255, 255, 255, 0.03));
  --shell-line: rgba(255, 255, 255, 0.13);
  --shell-shadow: 0 26px 70px rgba(4, 20, 28, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.14);
  --menu-bg: rgba(255, 255, 255, 0.12);
  --menu-hover: rgba(255, 255, 255, 0.18);
  --menu-line: rgba(255, 255, 255, 0.16);
  --track-on-bg: rgba(255, 255, 255, 0.2);   /* Fortschrittsbalken im Spiel */
  --track-on-card: rgba(6, 30, 40, 0.1);     /* Fortschrittsbalken auf Karte */

  /* --- Form und Schriftbild ----------------------------------------------- */
  --radius: 20px;
  --btn-radius: 14px;
  --shadow: 0 10px 30px rgba(6, 30, 40, 0.18);
  --font: "Segoe UI", system-ui, -apple-system, Roboto, sans-serif;
  --font-display: var(--font);  /* Überschriften und grosse Zahlen */
  --display-track: 0;           /* deren Laufweite */
  --display-case: none;         /* und ob sie in Versalien stehen */

  /* --- Die Stimmungsebenen ------------------------------------------------ */
  --fx-tile: 300px;   /* Kachelhöhe des Teilchenmusters = Weg einer Runde */
  --fx-x: 0px;        /* seitlicher Versatz dabei; 0 oder genau eine
                         Kachelbreite – dazwischen springt das Muster */
  --fx-drift: 2%;     /* wie weit die ferne Ebene hin und her wandert */
}

* { box-sizing: border-box; }

/* Ein Video ist ein Bild, kein Text: Ein Tipp auf die Kiste oder auf den
   Octopus darf keine blaue Auswahl hinterlassen (und in Safari beim Halten
   kein Teilen-Menü öffnen). Ohne Tipp-Highlight: Chrome (Touchscreen) legt
   sonst bei der Berührung ein dunkelgraues Rechteck über das ganze Element –
   bei transparenten Clips wird damit das Videoformat sichtbar. */
video {
  -webkit-user-select: none; user-select: none;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
}

html { margin: 0; min-height: 100%; }

/* Der Hintergrund gehört bewusst <body> und NICHT <html>: Nur so liegen die
   beiden Stimmungsebenen (body::before / ::after, weiter unten) darüber. Der
   Browser reicht ihn trotzdem an die ganze Seite weiter, solange <html>
   selbst keinen hat – deshalb steht dort keiner. */
body {
  margin: 0;
  min-height: 100%;
  font-family: var(--font);
  color: var(--ink);
  background-color: var(--bg-deep);
  background-image: var(--page);
  background-attachment: fixed;
}

/* Überschriften tragen das Schriftbild der Welt, der Rest der Seite bleibt in
   der gut lesbaren Grundschrift. Die Rechnung selbst steht bewusst NICHT hier:
   Ob eine 6 von einer 8 zu unterscheiden ist, darf keine Geschmacksfrage sein. */
h1, h2, .brand h1, .mgmt-title, .wiz-head-title {
  font-family: var(--font-display);
  letter-spacing: var(--display-track);
  text-transform: var(--display-case);
}
h3, .stat-num, .shop-wallet-num, .medal-summary-num, .home-cta-text strong {
  font-family: var(--font-display);
}

/* --------------------------------------------------------------------------
   Themes aus dem STAR-Shop
   --------------------------------------------------------------------------
   Ein Theme heisst hier auch „Welt": Es stellt nicht nur Farben um, sondern
   Licht, Bewegung, Form und Schrift. Ein gekauftes Theme setzt seine Klasse
   auf <body> (siehe
   templates/layout/head.php). Von dort stellt sie den ganzen Satz Marken von
   oben um: Hintergrund, Karten, Schrift, Form, Schriftart. Ein Vulkan ist
   deshalb nicht mehr „dasselbe in Orange", sondern dunkles Gestein mit
   glühender Schrift; der Weltraum sind tiefblaue Tafeln unter Sternen und
   Zuckerwatte eine helle Welt, in der die Seite von unten nach oben aufhellt.

   Zwei Dinge bleiben in JEDER Welt gleich, und beide aus demselben Grund –
   ein Kind soll sich nicht umgewöhnen müssen, wenn es die Welt wechselt:
   die Anordnung der Seite und die Bedeutung der Signalfarben (grün richtig,
   rot falsch, gold Stern).

   Dazu kommen zwei Stimmungsebenen, die jede Welt selbst füllt (weiter
   unten): die Ferne und das Nahe.

   Die Schlüssel müssen zu ShopCatalog::THEMES passen; eine Klasse ohne Eintrag
   dort ist nicht kaufbar, ein Eintrag ohne Klasse hier bliebe wirkungslos.
   „theme-ozean" steht deshalb oben neben :root und nicht hier: Es IST die
   Vorgabe – aber es muss sich auch ausdrücklich setzen lassen. Sonst nähme
   die Ozean-Karte im Laden die Farben der Welt an, die man gerade trägt,
   während die vier anderen ihre eigenen behalten. */

/* --- Die zwei Stimmungsebenen ---------------------------------------------
   ::before ist die Ferne (Licht, Lavaströme, Nebel), ::after das Nahe
   (Blasen, Funken, Sterne, Glitzer). Beide kleben am Bildschirm, liegen
   hinter allem (z-index: -1) und nehmen keinen Klick an.

   Sie liegen absichtlich um mehr als eine Musterkachel über den Rand hinaus:
   Bewegt wird ausschliesslich mit transform, und zwar um genau eine Kachel
   (--fx-tile) beziehungsweise um genau eine halbe Musterbreite. Danach steht
   das Muster wieder deckungsgleich – die Wiederholung ist unsichtbar, und die
   Grafikkarte macht die Arbeit, nicht der Hauptprozessor. Die Bewegung
   läuft bewusst auch bei „weniger Bewegung“: Windows meldet das schon, sobald
   die „Animationseffekte“ aus sind, und dort stand jede gekaufte Welt still –
   Kinder sahen nur Farben und dachten, das Theme sei kaputt. Warum, steht bei
   der reduce-Regel weiter unten. */
body::before, body::after {
  content: '';
  position: fixed;
  left: -10vw; right: -10vw; top: -340px; bottom: -340px;
  z-index: -1;
  pointer-events: none;
}

@keyframes fx-schweben {   /* die Ferne wandert langsam hin und zurück */
  from { transform: translate3d(calc(var(--fx-drift) * -1), 0, 0); }
  to   { transform: translate3d(var(--fx-drift), 0, 0); }
}
@keyframes fx-ziehen {     /* eine Strömung, genau eine Musterbreite weit */
  to { transform: translate3d(-50%, 0, 0); }
}
@keyframes fx-steigen {    /* Teilchen, genau eine Kachel weit */
  to { transform: translate3d(var(--fx-x), calc(var(--fx-tile) * -1), 0); }
}
@keyframes fx-fallen {     /* dasselbe, aber abwärts: Schnee statt Blasen */
  to { transform: translate3d(var(--fx-x), var(--fx-tile), 0); }
}
@keyframes fx-glimmen {    /* Helligkeit atmet – eigene Spur neben der Bewegung */
  from { opacity: 0.65; }
  to   { opacity: 1; }
}
/* Gedreht wird ein Winkel, nicht eine Fläche – siehe Roboterwerkstatt. */
@keyframes fx-drehen-a { to { --zahn-a: 360deg; } }
@keyframes fx-drehen-b { to { --zahn-b: -360deg; } }

/* === Ozean ================================================================
   Die Welt, die jedes Kind von Anfang an hat, und die einzige, die auch
   ausserhalb des Shops gilt: Anmeldung, Lehrer- und Verwaltungsbereich haben
   keine Klasse am <body> und sehen deshalb genau das hier. Also bleibt es bei
   dem, was schon immer da war – Licht, das von der Wasseroberfläche
   hereinfällt und leise schwankt. Sonst nichts: Die nahe Ebene bleibt leer,
   damit über einer Klassenliste keine Blasen aufsteigen.

   Der Block gilt mangels Klasse für jedes <body>; die anderen Welten
   überschreiben jede einzelne Eigenschaft davon. Wer hier eine dazunimmt,
   muss sie deshalb auch bei allen vier anderen setzen, sonst scheint der
   Ozean durch. Für die nahe Ebene gilt das umgekehrt: Sie ist von Haus aus
   leer, jede Welt füllt nur ihre eigene. */
body::before {
  background-image:
    radial-gradient(58% 40% at 22% -6%, rgba(255, 255, 255, 0.13), transparent 70%),
    radial-gradient(44% 32% at 74% -4%, rgba(255, 255, 255, 0.09), transparent 70%),
    radial-gradient(60% 40% at 50% 104%, rgba(3, 20, 28, 0.5), transparent 72%);
  animation: fx-schweben 21s ease-in-out infinite alternate;
}

/* === Dschungel ============================================================
   Unter dem Blätterdach: oben knabbern dunkle Blätter in den Bildrand,
   dazwischen fallen warme Lichtflecken ein – und sonst nichts, die nahe
   Ebene bleibt hier leer. Karten sind nicht mehr weiss, sondern papierhell
   mit einem Stich ins Grüne; die Überschriften stehen in einer
   Serifenschrift, die wie gewachsen aussieht und nicht wie gebaut. */
.theme-dschungel {
  --bg: #1c5233;
  --bg-deep: #05170d;
  --bg-top: #3f9159;
  --page:
    radial-gradient(140% 120% at 50% -10%, #57a86b 0%, #1c5233 38%, #0b2a1a 72%, #05170d 100%);

  --surface: #fbfdf4;
  --surface-soft: #ecf5e6;
  --card-lift: #ffffff;
  --key-bg: #fdfff8;
  --tint: #edf6e6;
  --tint-line: #cfe3c3;
  --line: #cbe0be;
  --line-soft: #e3efdc;
  --press: #dcecd1;
  --dash: #9db894;
  --dash-bg: #f4faef;
  --empty-bg: #eef4e9;
  --empty-ink: #a9bda2;
  --empty-line: #cfdcc7;

  --ink: #17331f;
  --ink-soft: #567a5c;
  --on-dark: #f1fbe4;
  --on-dark-soft: rgba(241, 251, 228, 0.84);
  --on-dark-faint: rgba(241, 251, 228, 0.7);
  --on-accent: #06280f;

  --good-ink: #1f7f45;
  --good-bg: #e4f6e6;
  --bad-bg: #fbeae4;
  --star-ink: #96700a;

  --accent: #52c07c;
  --accent-strong: #2a8a4f;
  --accent-lift: #a8e77f;

  --shell-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.1), rgba(120, 200, 130, 0.05));
  --shell-line: rgba(190, 245, 180, 0.18);
  --shell-shadow: 0 26px 70px rgba(3, 26, 12, 0.55), inset 0 1px 0 rgba(220, 255, 200, 0.16);
  --menu-bg: rgba(190, 245, 180, 0.12);
  --menu-hover: rgba(190, 245, 180, 0.2);
  --menu-line: rgba(190, 245, 180, 0.18);
  --track-on-bg: rgba(235, 255, 220, 0.22);

  --radius: 22px;
  --btn-radius: 16px;
  --shadow: 0 10px 30px rgba(8, 40, 20, 0.24);
  --font: Karla, "Segoe UI", system-ui, sans-serif;
  --font-display: Fraunces, Georgia, "Times New Roman", serif;
  --display-track: 0.005em;

  --fx-drift: 2.5%;
}
body.theme-dschungel::before {
  background-image:
    radial-gradient(ellipse 78px 48px at 50% 0%, rgba(4, 26, 14, 0.62), transparent 72%),
    radial-gradient(34% 26% at 24% 16%, rgba(226, 255, 140, 0.16), transparent 70%),
    radial-gradient(28% 22% at 70% 8%, rgba(255, 246, 165, 0.13), transparent 70%),
    radial-gradient(60% 40% at 52% 104%, rgba(2, 18, 10, 0.55), transparent 72%);
  background-size: 156px 96px, auto, auto, auto;
  background-repeat: repeat-x, no-repeat, no-repeat, no-repeat;
  background-position: 0 340px, 0 0, 0 0, 0 0;
  animation: fx-schweben 24s ease-in-out infinite alternate;
}
/* === Zuckerwatte ==========================================================
   Die einzige helle Welt – und deshalb die, die am meisten umstellt: Auf
   einem rosa Himmel wäre weisse Schrift nicht zu lesen, also wird --on-dark
   zu einem dunklen Pflaumenton und die Leisten werden milchig statt
   durchscheinend. Dazu Zuckerwatte-Wolken, Glitzer, runde Ecken und
   Knöpfe als vollrunde Bonbons. */
.theme-zuckerwatte {
  --bg: #ffd3ec;
  --bg-deep: #cf9ae0;
  --bg-top: #fff0f8;
  --page:
    radial-gradient(70% 50% at 78% 10%, rgba(255, 255, 255, 0.6), transparent 70%),
    linear-gradient(180deg, #fff0f8 0%, #ffd3ec 38%, #f0bfe8 68%, #cf9ae0 100%);

  --surface: #fffafd;
  --surface-soft: #ffeef7;
  --card-lift: #ffffff;
  --key-bg: #fffafd;
  --tint: #fff0f8;
  --tint-line: #f7d3e8;
  --line: #f4cee4;
  --line-soft: #fbe6f2;
  --press: #ffe0f1;
  --dash: #d3a4c2;
  --dash-bg: #fff6fb;
  --empty-bg: #fbeef5;
  --empty-ink: #cfa9c1;
  --empty-line: #eed3e4;

  --ink: #4d1c3c;
  --ink-soft: #8f6480;
  --on-dark: #6b2352;
  --on-dark-soft: rgba(107, 35, 82, 0.82);
  --on-dark-faint: rgba(107, 35, 82, 0.66);
  --on-accent: #fff;

  --good-ink: #1f8f57;
  --good-bg: #e2f7ea;
  --bad-bg: #ffe6e6;
  --star-ink: #a97400;
  --star-on-bg: var(--star-ink);

  --accent: #f172b6;
  --accent-strong: #cf3d8f;
  --accent-lift: #ffc2e6;

  /* Milchglas statt Fensterscheibe: Auf hellem Grund trägt nur eine hellere
     Fläche, eine dunklere sähe wie ein Fleck aus. */
  --shell-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.62), rgba(255, 255, 255, 0.38));
  --shell-line: rgba(255, 255, 255, 0.9);
  --shell-shadow: 0 26px 60px rgba(180, 80, 145, 0.22), inset 0 1px 0 rgba(255, 255, 255, 0.9);
  --menu-bg: rgba(255, 255, 255, 0.55);
  --menu-hover: rgba(255, 255, 255, 0.78);
  --menu-line: rgba(255, 255, 255, 0.85);
  --track-on-bg: rgba(107, 35, 82, 0.16);
  --track-on-card: rgba(107, 35, 82, 0.12);

  --radius: 26px;
  --btn-radius: 999px;
  --shadow: 0 12px 30px rgba(190, 95, 155, 0.22);
  --font: ui-rounded, "SF Pro Rounded", "Varela Round", "Trebuchet MS", system-ui, sans-serif;
  --font-display: var(--font);
  --display-track: 0.01em;

  --fx-drift: 3%;
}
body.theme-zuckerwatte::before {
  background-image:
    radial-gradient(30% 22% at 18% 22%, rgba(255, 255, 255, 0.75), transparent 70%),
    radial-gradient(26% 20% at 64% 12%, rgba(255, 214, 240, 0.8), transparent 70%),
    radial-gradient(34% 24% at 84% 46%, rgba(255, 255, 255, 0.55), transparent 70%),
    radial-gradient(40% 26% at 34% 78%, rgba(233, 205, 255, 0.6), transparent 72%);
  animation: fx-schweben 26s ease-in-out infinite alternate;
}
body.theme-zuckerwatte::after {
  --fx-tile: 300px;
  --fx-x: 250px;
  /* Auf hellem Himmel wäre weisser Glitzer keiner: Hier funkelt es in Gold,
     Rosa und Flieder – dieselbe Rolle wie die weissen Blasen im Ozean, nur
     andersherum gedacht. */
  background-image:
    radial-gradient(circle 4px at 14% 18%, rgba(255, 255, 255, 0.95) 0 18%, rgba(255, 190, 60, 0.55) 42%, transparent 100%),
    radial-gradient(circle 3px at 46% 40%, rgba(255, 255, 255, 0.9) 0 20%, rgba(233, 110, 190, 0.5) 46%, transparent 100%),
    radial-gradient(circle 5px at 74% 16%, rgba(255, 255, 255, 0.9) 0 14%, rgba(255, 150, 210, 0.4) 38%, transparent 100%),
    radial-gradient(circle 3px at 88% 66%, rgba(255, 255, 255, 0.95) 0 20%, rgba(170, 120, 255, 0.45) 46%, transparent 100%),
    radial-gradient(circle 4px at 58% 84%, rgba(255, 255, 255, 0.9) 0 16%, rgba(255, 140, 205, 0.5) 42%, transparent 100%),
    radial-gradient(circle 2px at 28% 66%, rgba(255, 255, 255, 1) 0 26%, rgba(255, 190, 60, 0.5) 54%, transparent 100%);
  background-size: 250px 300px;
  animation: fx-steigen 46s linear infinite, fx-glimmen 2.2s ease-in-out infinite alternate;
}

/* === Vulkan ===============================================================
   Wirklich dunkel: Der Bildschirm ist nachtschwarzes Gestein, das Licht
   kommt von unten aus dem Krater. Darüber ziehen zwei Lavaströme quer durchs
   Bild und pulsieren dabei, Funken steigen schräg auf. Die Karten sind
   Basalt statt Papier, die Schrift glüht, die Ecken sind kantig – und der
   Schatten jeder Karte trägt einen feinen Glutrand (der zweite, innere Teil
   von --shadow). */
.theme-vulkan {
  --bg: #150a07;
  --bg-deep: #080403;
  --bg-top: #4a1508;
  --page:
    radial-gradient(120% 88% at 50% 112%, #ff5a12 0%, #a02b06 18%, #46150a 40%, #1a0a06 68%, #080403 100%);

  --surface: #1d1310;
  --surface-soft: #2a1a15;
  --card-lift: #35211a;
  --key-bg: #2f1d17;
  --tint: #2a1a15;
  --tint-line: #4a2c22;
  --line: #4a2c22;
  --line-soft: #33201a;
  --press: #3d251d;
  --dash: #7d5748;
  --dash-bg: #241610;
  --empty-bg: #241610;
  --empty-ink: #8b6a5b;
  --empty-line: #4a2c22;

  --ink: #ffe7d8;
  --ink-soft: #c69a86;
  --on-dark: #ffdfc9;
  --on-dark-soft: rgba(255, 223, 201, 0.82);
  --on-dark-faint: rgba(255, 223, 201, 0.66);
  --on-accent: #2b0c02;

  /* Grün und Rot bleiben Grün und Rot – aber die blasse Fläche dahinter
     kann auf Basalt nicht blass sein, sonst leuchtet ein Hinweis heller als
     alles andere auf der Seite. */
  --good-ink: #4ad48b;
  --good-bg: rgba(47, 191, 113, 0.18);
  --bad-bg: rgba(239, 91, 87, 0.2);
  --star-ink: #ffc94d;

  --accent: #ff6a1f;
  --accent-strong: #ff9a3c;
  --accent-lift: #ffc46a;

  --shell-bg: linear-gradient(180deg, rgba(90, 32, 14, 0.5), rgba(20, 8, 5, 0.4));
  --shell-line: rgba(255, 122, 40, 0.22);
  --shell-shadow: 0 26px 70px rgba(0, 0, 0, 0.7), inset 0 1px 0 rgba(255, 150, 60, 0.2);
  --menu-bg: rgba(255, 110, 35, 0.12);
  --menu-hover: rgba(255, 110, 35, 0.24);
  --menu-line: rgba(255, 140, 60, 0.28);
  --track-on-bg: rgba(255, 150, 80, 0.18);
  --track-on-card: rgba(255, 190, 150, 0.14);

  --radius: 12px;
  --btn-radius: 10px;
  --shadow: 0 14px 34px rgba(0, 0, 0, 0.62), inset 0 0 0 1px rgba(255, 130, 50, 0.16);
  --font: "Segoe UI", system-ui, -apple-system, Roboto, sans-serif;
  --font-display: Fraunces, Georgia, "Times New Roman", serif;
  --display-track: 0.03em;
  --display-case: uppercase;
}
/* Drei Reihen glühender Becken, die von rechts nach links durchs Bild
   wandern. Vorher standen hier schräge Farbbänder – ein Band füllt seine
   Musterkachel bis zum Rand, und dort, wo die Kachel aufhört, hört auch die
   Farbe auf: quer über den Bildschirm eine sichtbare Kante, ein Rechteck.
   Ellipsen laufen vor dem Kachelrand nach transparent aus, deshalb ist von
   der Wiederholung nichts mehr zu sehen.

   Die Höhen stehen in px und die Lage wird von UNTEN gemessen
   (calc(100% - …)): Die Ebene ragt oben und unten über den Bildschirm
   hinaus, ein Wert in Prozent läge sonst je nach Fensterhöhe woanders –
   und die Lava gehört an den unteren Rand, dorthin, wo der Krater ist.
   Die Breiten (50 % und 25 %) müssen Teiler des Wegs einer Runde bleiben,
   sonst springt das Muster beim Rundenschluss. */
body.theme-vulkan::before {
  background-image:
    radial-gradient(ellipse 40% 42% at 50% 50%, rgba(255, 178, 70, 0.34), transparent 74%),
    radial-gradient(ellipse 38% 40% at 50% 50%, rgba(255, 108, 20, 0.32), transparent 74%),
    radial-gradient(ellipse 44% 44% at 50% 50%, rgba(206, 40, 6, 0.3), transparent 76%);
  background-size: 50% 260px, 25% 150px, 50% 420px;
  background-position:
    0 calc(100% - 300px),
    0 calc(100% - 470px),
    0 calc(100% - 240px);
  background-repeat: repeat-x;
  animation: fx-ziehen 32s linear infinite, fx-glimmen 6.5s ease-in-out infinite alternate;
}
/* Funken, keine Blasen: ein winziger weissglühender Kern mit warmem Hof,
   schnell und flackernd. Eine Blase ist gross, hohl und langsam – ein Funke
   ist das Gegenteil davon, sonst sieht in jeder Welt dasselbe auf. */
body.theme-vulkan::after {
  --fx-tile: 260px;
  --fx-x: -260px;
  background-image:
    radial-gradient(circle 5px at 14% 12%, rgba(255, 240, 200, 0.95) 0 16%, rgba(255, 120, 20, 0.45) 40%, transparent 100%),
    radial-gradient(circle 3px at 42% 46%, rgba(255, 220, 150, 0.9) 0 22%, rgba(255, 90, 10, 0.4) 46%, transparent 100%),
    radial-gradient(circle 7px at 68% 22%, rgba(255, 200, 120, 0.6) 0 10%, rgba(255, 80, 10, 0.28) 34%, transparent 100%),
    radial-gradient(circle 4px at 86% 64%, rgba(255, 250, 220, 0.95) 0 18%, rgba(255, 140, 30, 0.4) 42%, transparent 100%),
    radial-gradient(circle 3px at 54% 86%, rgba(255, 210, 140, 0.85) 0 20%, rgba(255, 100, 15, 0.35) 44%, transparent 100%),
    radial-gradient(circle 2px at 26% 72%, rgba(255, 250, 230, 1) 0 26%, rgba(255, 160, 40, 0.4) 52%, transparent 100%);
  background-size: 260px 260px;
  animation: fx-steigen 11s linear infinite, fx-glimmen 1.6s ease-in-out infinite alternate;
}

/* === Weltraum =============================================================
   Nachtseite: kaltes blaues Bordlicht auf fast schwarzen Tafeln, zwei
   Nebelschwaden
   im Hintergrund und zwei Sternfelder, die verschieden schnell ziehen – das
   nahe wandert sichtbar, das ferne fast gar nicht, und genau daraus entsteht
   der Eindruck von Tiefe. Die Überschriften stehen in Versalien einer
   Schreibmaschinenschrift, weit gesperrt wie eine Anzeigetafel. */
.theme-weltraum {
  --bg: #090c28;
  --bg-deep: #020309;
  --bg-top: #1a2054;
  /* Der Nebel war rosa-violett und hat die ganze Welt eingefärbt. Jetzt ist
     er kaltes Blau und viel schwächer – die Farbe der Nachtseite kommt vom
     Sternenlicht, nicht vom Nebel. */
  --page:
    radial-gradient(40% 30% at 76% 14%, rgba(80, 130, 255, 0.14), transparent 70%),
    radial-gradient(46% 36% at 16% 62%, rgba(40, 150, 210, 0.1), transparent 70%),
    radial-gradient(130% 95% at 50% -12%, #1a2054 0%, #0d1136 28%, #060818 62%, #020309 100%);

  --surface: #0e1130;
  --surface-soft: #181c44;
  --card-lift: #202556;
  --key-bg: #1a1f4c;
  --tint: #181c44;
  --tint-line: #2d3370;
  --line: #2d3370;
  --line-soft: #222750;
  --press: #262c68;
  --dash: #5e66a6;
  --dash-bg: #131634;
  --empty-bg: #131634;
  --empty-ink: #6e76ac;
  --empty-line: #2d3370;

  --ink: #e6ebff;
  --ink-soft: #96a1cf;
  --on-dark: #e2e9ff;
  --on-dark-soft: rgba(226, 233, 255, 0.82);
  --on-dark-faint: rgba(226, 233, 255, 0.64);
  --on-accent: #04071f;

  --good-ink: #55e0a0;
  --good-bg: rgba(47, 191, 113, 0.2);
  --bad-bg: rgba(239, 91, 87, 0.22);
  --star-ink: #ffd96b;

  --accent: #4f7cff;
  --accent-strong: #8aa6ff;
  --accent-lift: #57e6ff;

  --shell-bg: linear-gradient(180deg, rgba(80, 120, 230, 0.1), rgba(3, 5, 18, 0.42));
  --shell-line: rgba(110, 150, 255, 0.2);
  --shell-shadow: 0 26px 70px rgba(0, 0, 0, 0.72), inset 0 1px 0 rgba(120, 160, 255, 0.18);
  --menu-bg: rgba(90, 130, 245, 0.12);
  --menu-hover: rgba(90, 130, 245, 0.24);
  --menu-line: rgba(110, 150, 255, 0.22);
  --track-on-bg: rgba(160, 190, 255, 0.2);
  --track-on-card: rgba(180, 205, 255, 0.14);

  --radius: 16px;
  --btn-radius: 12px;
  --shadow: 0 14px 34px rgba(0, 0, 0, 0.62), inset 0 0 0 1px rgba(90, 130, 255, 0.16);
  --font: Karla, "Segoe UI", system-ui, sans-serif;
  --font-display: ui-monospace, "SF Mono", "Segoe UI Mono", "Roboto Mono", "Courier New", monospace;
  --display-track: 0.09em;
  --display-case: uppercase;
}
body.theme-weltraum::before {
  --fx-tile: 340px;
  --fx-x: 0px;
  background-image:
    radial-gradient(circle 1px at 8% 12%, rgba(255, 255, 255, 0.55), transparent),
    radial-gradient(circle 1px at 27% 38%, rgba(200, 220, 255, 0.5), transparent),
    radial-gradient(circle 1.5px at 44% 8%, rgba(255, 255, 255, 0.45), transparent),
    radial-gradient(circle 1px at 61% 52%, rgba(255, 235, 210, 0.5), transparent),
    radial-gradient(circle 1px at 78% 24%, rgba(255, 255, 255, 0.5), transparent),
    radial-gradient(circle 1px at 92% 68%, rgba(190, 210, 255, 0.5), transparent),
    radial-gradient(circle 1.5px at 34% 78%, rgba(255, 255, 255, 0.4), transparent),
    radial-gradient(circle 1px at 68% 90%, rgba(255, 255, 255, 0.45), transparent);
  background-size: 300px 340px;
  animation: fx-steigen 150s linear infinite;
}
body.theme-weltraum::after {
  --fx-tile: 260px;
  --fx-x: 0px;
  background-image:
    radial-gradient(circle 2px at 18% 20%, rgba(255, 255, 255, 0.95), transparent),
    radial-gradient(circle 1.5px at 52% 44%, rgba(190, 230, 255, 0.85), transparent),
    radial-gradient(circle 2.5px at 76% 14%, rgba(215, 235, 255, 0.7), transparent),
    radial-gradient(circle 1.5px at 88% 70%, rgba(255, 255, 255, 0.8), transparent),
    radial-gradient(circle 2px at 40% 86%, rgba(255, 245, 200, 0.75), transparent),
    radial-gradient(circle 1px at 10% 62%, rgba(255, 255, 255, 0.9), transparent);
  background-size: 240px 260px;
  animation: fx-steigen 70s linear infinite, fx-glimmen 4.5s ease-in-out infinite alternate;
}

/* === Polarlicht ===========================================================
   Die kalte helle Welt – das Gegenstück zur Zuckerwatte, die die warme ist.
   Ein eisheller Himmel, darüber ziehen Schleier in Mint und Flieder, und es
   schneit. Karten sind weiss mit Blaustich und knappen Ecken: Eis, nicht
   Watte. Weil der Himmel hell ist, ist --on-dark hier ein tiefes Eisblau –
   dieselbe Umkehrung wie bei der Zuckerwatte. */
.theme-polarlicht {
  --bg: #cdeaf7;
  --bg-deep: #5aa8cd;
  --bg-top: #f6fdff;
  --page:
    radial-gradient(60% 40% at 76% 4%, rgba(255, 255, 255, 0.7), transparent 70%),
    linear-gradient(180deg, #f7fdff 0%, #e0f3fb 34%, #aadef1 66%, #5aa8cd 100%);

  --surface: #ffffff;
  --surface-soft: #e9f5fc;
  --card-lift: #ffffff;
  --key-bg: #ffffff;
  --tint: #e9f5fc;
  --tint-line: #c8e5f2;
  --line: #c3e1f0;
  --line-soft: #ddeff8;
  --press: #d6edf8;
  --dash: #93b6c8;
  --dash-bg: #f4fbfe;
  --empty-bg: #e8f2f8;
  --empty-ink: #a5bfcd;
  --empty-line: #cde1ec;

  --ink: #0f2e42;
  --ink-soft: #517288;
  --on-dark: #0d3049;
  --on-dark-soft: rgba(13, 48, 73, 0.82);
  --on-dark-faint: rgba(13, 48, 73, 0.64);
  --on-accent: #fff;

  --good-ink: #1f8f57;
  --good-bg: #e2f7ea;
  --bad-bg: #ffe8e6;
  --star-ink: #a2700a;
  --star-on-bg: var(--star-ink);

  --accent: #26a5d8;
  --accent-strong: #0f76a3;
  --accent-lift: #8fe2f6;

  --shell-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.68), rgba(255, 255, 255, 0.4));
  --shell-line: rgba(255, 255, 255, 0.95);
  --shell-shadow: 0 26px 60px rgba(30, 90, 130, 0.22), inset 0 1px 0 #fff;
  --menu-bg: rgba(255, 255, 255, 0.6);
  --menu-hover: rgba(255, 255, 255, 0.85);
  --menu-line: rgba(255, 255, 255, 0.9);
  --track-on-bg: rgba(13, 48, 73, 0.14);
  --track-on-card: rgba(13, 48, 73, 0.1);

  --radius: 14px;
  --btn-radius: 12px;
  --shadow: 0 10px 28px rgba(30, 90, 130, 0.2);
  --font: "Avenir Next", Avenir, "Segoe UI", system-ui, sans-serif;
  --font-display: var(--font);
  --display-track: 0.06em;

  --fx-drift: 3.5%;
}
body.theme-polarlicht::before {
  background-image:
    radial-gradient(46% 20% at 30% 8%, rgba(90, 230, 190, 0.3), transparent 72%),
    radial-gradient(38% 16% at 66% 3%, rgba(160, 150, 255, 0.28), transparent 72%),
    radial-gradient(52% 22% at 48% 14%, rgba(120, 240, 220, 0.16), transparent 74%);
  animation: fx-schweben 28s ease-in-out infinite alternate;
}
/* Die einzige Welt, in der etwas nach UNTEN geht. Weisser Schnee auf hellem
   Himmel wäre unsichtbar, deshalb hat jede Flocke einen blauen Hof – erst der
   hebt sie vom Weiss ab. */
body.theme-polarlicht::after {
  --fx-tile: 320px;
  --fx-x: -260px;
  background-image:
    radial-gradient(circle 4px at 12% 14%, rgba(255, 255, 255, 0.95) 0 30%, rgba(120, 180, 215, 0.45) 60%, transparent 100%),
    radial-gradient(circle 3px at 44% 42%, rgba(255, 255, 255, 0.9) 0 34%, rgba(120, 180, 215, 0.4) 62%, transparent 100%),
    radial-gradient(circle 5px at 72% 22%, rgba(255, 255, 255, 0.85) 0 26%, rgba(120, 180, 215, 0.35) 58%, transparent 100%),
    radial-gradient(circle 3px at 88% 68%, rgba(255, 255, 255, 0.9) 0 32%, rgba(120, 180, 215, 0.42) 60%, transparent 100%),
    radial-gradient(circle 4px at 58% 86%, rgba(255, 255, 255, 0.9) 0 28%, rgba(120, 180, 215, 0.4) 58%, transparent 100%),
    radial-gradient(circle 2.5px at 26% 70%, rgba(255, 255, 255, 0.95) 0 36%, rgba(120, 180, 215, 0.45) 64%, transparent 100%);
  background-size: 260px 320px;
  animation: fx-fallen 24s linear infinite;
}

/* === Roboterwerkstatt =====================================================
   Die einzige graue Welt: Stahlblech, Nietenkanten, Warnschild-Gelb. Hinter
   allem drehen sich zwei grosse Räder, unten am Bildrand läuft ein Band.
   Karten sind Bleche – der helle Strich oben in --shadow ist die Lichtkante,
   die eine Platte erst nach Metall aussehen lässt.

   Das Gelb ist absichtlich heller und kühler als das Gold der Sterne: Es
   sitzt auf Knöpfen, das Gold auf Zahlen, und die beiden dürfen sich nicht
   verwechseln lassen. */
.theme-werkstatt {
  --bg: #333c45;
  --bg-deep: #171c21;
  --bg-top: #59646f;
  --page:
    radial-gradient(120% 90% at 50% -20%, #616d78 0%, #3b444e 38%, #262d34 70%, #171c21 100%);

  --surface: #2b333b;
  --surface-soft: #363f49;
  --card-lift: #424c57;
  --key-bg: #3a444e;
  --tint: #363f49;
  --tint-line: #4d5964;
  --line: #4d5964;
  --line-soft: #39424b;
  --press: #46515c;
  --dash: #7b8894;
  --dash-bg: #252c33;
  --empty-bg: #252c33;
  --empty-ink: #808d99;
  --empty-line: #4d5964;

  --ink: #eef2f5;
  --ink-soft: #a2afba;
  --on-dark: #e7edf2;
  --on-dark-soft: rgba(231, 237, 242, 0.82);
  --on-dark-faint: rgba(231, 237, 242, 0.64);
  --on-accent: #2b2200;

  --good-ink: #4ad48b;
  --good-bg: rgba(47, 191, 113, 0.18);
  --bad-bg: rgba(239, 91, 87, 0.2);
  --star-ink: #f5c542;

  --accent: #ffd21e;
  --accent-strong: #ffe066;
  --accent-lift: #fff3a8;

  --shell-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.07), rgba(0, 0, 0, 0.2));
  --shell-line: rgba(255, 210, 30, 0.2);
  --shell-shadow: 0 26px 70px rgba(0, 0, 0, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.14);
  --menu-bg: rgba(255, 255, 255, 0.08);
  --menu-hover: rgba(255, 210, 30, 0.2);
  --menu-line: rgba(255, 255, 255, 0.14);
  --track-on-bg: rgba(255, 255, 255, 0.16);
  --track-on-card: rgba(255, 255, 255, 0.12);

  --radius: 6px;
  --btn-radius: 6px;
  --shadow: 0 12px 28px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.09);
  --font: "Segoe UI", system-ui, -apple-system, Roboto, sans-serif;
  --font-display: "Avenir Next Condensed", "Arial Narrow", "HelveticaNeue-CondensedBold", system-ui, sans-serif;
  --display-track: 0.07em;
  --display-case: uppercase;
}
/* Zwei Räder, gegenläufig. Gedreht wird NICHT die Ebene – die ist so gross
   wie der Bildschirm, und ein gedrehtes Rechteck zeigt seine Ecken. Gedreht
   wird der Startwinkel zweier Kegelverläufe (@property, dieselbe Technik wie
   der Glanzring der Medaillen weiter unten): Die Speichen wandern, die Scheibe
   darunter steht still, und der Bildschirm bleibt ruhig. */
@property --zahn-a { syntax: '<angle>'; initial-value: 0deg; inherits: false; }
@property --zahn-b { syntax: '<angle>'; initial-value: 0deg; inherits: false; }
body.theme-werkstatt::before {
  background-image:
    conic-gradient(from var(--zahn-a), rgba(255, 255, 255, 0.07) 0 5deg, transparent 5deg 15deg),
    radial-gradient(circle closest-side, rgba(255, 255, 255, 0.05) 0 62%,
      rgba(255, 210, 30, 0.1) 64% 68%, transparent 69%),
    conic-gradient(from var(--zahn-b), rgba(255, 255, 255, 0.06) 0 6deg, transparent 6deg 18deg),
    radial-gradient(circle closest-side, rgba(255, 255, 255, 0.04) 0 60%,
      rgba(255, 210, 30, 0.08) 62% 66%, transparent 67%);
  background-size: 460px 460px, 460px 460px, 300px 300px, 300px 300px;
  background-position:
    right 5vw top 210px, right 5vw top 210px,
    left 4vw bottom 430px, left 4vw bottom 430px;
  background-repeat: no-repeat;
  animation: fx-drehen-a 54s linear infinite, fx-drehen-b 33s linear infinite;
}
/* Und unten läuft das Band. Es wandert um genau eine Musterlänge weiter, dann
   deckt sich das Muster wieder – dafür reicht fx-steigen mit --fx-tile: 0, ein
   eigenes Keyframe braucht es nicht.

   Die 48px des Streifenmusters zählen aber SCHRÄG, längs der 45°-Achse des
   Verlaufs; verschoben wird waagrecht. Waagrecht wiederholt sich das Muster
   deshalb erst nach 48px / cos(45°), also nach dem Wurzel-2-fachen. Mit den
   48px war das Band gut 19px zu kurz gezogen und sprang jede Runde sichtbar
   zurück. Der Faktor steht absichtlich als Rechnung da und nicht als 67.88 –
   ändert jemand die Streifenbreite, muss er nicht wissen, woher die Zahl kam. */
body.theme-werkstatt::after {
  --fx-tile: 0px;
  --fx-x: calc(48px * 1.41421356);
  background-image:
    repeating-linear-gradient(45deg,
      rgba(255, 210, 30, 0.5) 0 24px, rgba(20, 24, 28, 0.5) 24px 48px);
  background-size: auto 20px;
  background-repeat: repeat-x;
  background-position: bottom 340px left 0;
  animation: fx-steigen 3.2s linear infinite;
}

/* === Pergament ============================================================
   Die Welt ohne Himmel: kein Verlauf über die Tiefe, sondern eine Fläche mit
   Struktur – altes Papier mit Flecken, dunklen Rändern und einer Faserung.
   Das Licht kommt aus einer Ecke von einer Kerze und atmet leise, im Schein
   schwebt Staub. Als einzige Welt wechselt auch der Fliesstext die Schrift:
   Auf Papier ist eine Serifenschrift keine Verzierung, sondern das Normale.
   Der Akzent ist Siegelrot; das Gold der Sterne stand hier schon immer. */
.theme-pergament {
  --bg: #e6d0a8;
  --bg-deep: #a98f61;
  --bg-top: #f7ecd2;
  --page:
    radial-gradient(38% 30% at 18% 22%, rgba(150, 110, 60, 0.13), transparent 70%),
    radial-gradient(32% 26% at 78% 68%, rgba(150, 110, 60, 0.11), transparent 70%),
    radial-gradient(120% 100% at 50% 36%, #f9efd8 0%, #eddcb8 44%, #cdb183 76%, #a2875a 100%);

  --surface: #fdf6e6;
  --surface-soft: #f2e5ca;
  --card-lift: #fffcf2;
  --key-bg: #fdf6e6;
  --tint: #f2e5ca;
  --tint-line: #ddc79c;
  --line: #d6be93;
  --line-soft: #ebdec1;
  --press: #ebdab6;
  --dash: #ae9870;
  --dash-bg: #faf3e2;
  --empty-bg: #efe5cf;
  --empty-ink: #b1a083;
  --empty-line: #d6be93;

  --ink: #3b2b16;
  --ink-soft: #7a6547;
  --on-dark: #3a2a15;
  --on-dark-soft: rgba(58, 42, 21, 0.82);
  --on-dark-faint: rgba(58, 42, 21, 0.66);
  --on-accent: #fff8ec;

  --good-ink: #3d7a2c;
  --good-bg: #e8f2df;
  --bad-bg: #f7e3dc;
  --star-ink: #8a6a12;
  --star-on-bg: var(--star-ink);

  --accent: #a3341f;
  --accent-strong: #7d2415;
  --accent-lift: #d4694a;

  --shell-bg: linear-gradient(180deg, rgba(255, 250, 236, 0.5), rgba(255, 250, 236, 0.24));
  --shell-line: rgba(120, 90, 50, 0.22);
  --shell-shadow: 0 26px 60px rgba(90, 64, 30, 0.28), inset 0 1px 0 rgba(255, 255, 255, 0.6);
  --menu-bg: rgba(255, 250, 236, 0.5);
  --menu-hover: rgba(255, 250, 236, 0.8);
  --menu-line: rgba(120, 90, 50, 0.2);
  --track-on-bg: rgba(58, 42, 21, 0.16);
  --track-on-card: rgba(58, 42, 21, 0.12);

  --radius: 8px;
  --btn-radius: 8px;
  --shadow: 0 10px 26px rgba(90, 64, 30, 0.24);
  --font: Georgia, "Iowan Old Style", "Times New Roman", serif;
  --font-display: "Palatino Linotype", Palatino, "Book Antiqua", Georgia, serif;
  --display-track: 0.01em;

  --fx-drift: 1.2%;
}
body.theme-pergament::before {
  background-image:
    radial-gradient(44% 38% at 10% 4%, rgba(255, 212, 130, 0.42), transparent 74%),
    repeating-linear-gradient(93deg,
      rgba(120, 90, 50, 0.045) 0 1px, transparent 1px 4px);
  animation: fx-glimmen 7.5s ease-in-out infinite alternate;
}
/* Staub im Kerzenlicht: wenige Körnchen, fast waagrecht und so langsam, dass
   man sie erst bemerkt, wenn man hinschaut. */
body.theme-pergament::after {
  --fx-tile: 200px;
  --fx-x: 320px;
  background-image:
    radial-gradient(circle 2px at 18% 22%, rgba(255, 235, 190, 0.75), transparent),
    radial-gradient(circle 1.5px at 56% 48%, rgba(255, 240, 205, 0.6), transparent),
    radial-gradient(circle 2.5px at 82% 16%, rgba(255, 230, 175, 0.45), transparent),
    radial-gradient(circle 1.5px at 34% 78%, rgba(255, 245, 215, 0.55), transparent);
  background-size: 320px 200px;
  animation: fx-steigen 96s linear infinite, fx-glimmen 9s ease-in-out infinite alternate;
}

/* --- Und noch je eine Handvoll Licht auf der Aufgabenkarte ----------------
   Vor dieser Karte sitzt ein Kind die ganze Übungsrunde lang. Die Welt darf
   deshalb nicht nur um sie herum stattfinden, sondern muss auch auf ihr zu
   sehen sein – aber leise, denn was zählt, ist die Rechnung darauf. Die
   Deckplatte (.card-cover) bekommt dasselbe Licht, sonst wäre der Wechsel
   „Startknopf → Aufgabe" ein Farbsprung. Der Grundton bleibt jeweils
   --surface; hier kommt nur die Zeichnung darüber dazu. */
.theme-dschungel .question, .theme-dschungel .card-cover {
  background-image: radial-gradient(80% 60% at 12% -10%, rgba(150, 220, 110, 0.22), transparent 70%);
}
.theme-zuckerwatte .question, .theme-zuckerwatte .card-cover {
  background-image: radial-gradient(90% 70% at 88% -20%, rgba(255, 170, 220, 0.28), transparent 70%);
}
.theme-vulkan .question, .theme-vulkan .card-cover {
  background-image: radial-gradient(120% 85% at 50% 135%, rgba(255, 92, 12, 0.28), transparent 62%);
}
.theme-polarlicht .question, .theme-polarlicht .card-cover {
  background-image: radial-gradient(90% 70% at 84% -18%, rgba(120, 220, 235, 0.26), transparent 70%);
}
.theme-werkstatt .question, .theme-werkstatt .card-cover {
  background-image: linear-gradient(180deg, rgba(255, 255, 255, 0.06), transparent 42%);
}
.theme-pergament .question, .theme-pergament .card-cover {
  background-image: radial-gradient(100% 80% at 8% -12%, rgba(255, 214, 140, 0.34), transparent 72%);
}
.theme-weltraum .question, .theme-weltraum .card-cover {
  background-image:
    radial-gradient(circle 1.5px at 12% 22%, rgba(255, 255, 255, 0.5), transparent),
    radial-gradient(circle 1px at 82% 34%, rgba(255, 255, 255, 0.45), transparent),
    radial-gradient(circle 1px at 34% 78%, rgba(255, 255, 255, 0.4), transparent),
    radial-gradient(100% 80% at 78% -20%, rgba(80, 130, 255, 0.26), transparent 68%);
}

/* Wo eine Welt eigenes Licht abgibt, leuchten auch ihre Knöpfe. */
.theme-vulkan .btn, .theme-vulkan .shop-buy, .theme-vulkan .home-cta,
.theme-vulkan .topmenu-item.is-main {
  box-shadow: 0 6px 20px rgba(255, 92, 12, 0.4);
}
.theme-weltraum .btn, .theme-weltraum .shop-buy, .theme-weltraum .home-cta,
.theme-weltraum .topmenu-item.is-main {
  box-shadow: 0 6px 20px rgba(70, 120, 255, 0.42);
}

body {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-height: 100vh;
  padding: clamp(12px, 3vw, 32px);
}

.panel {
  background: var(--surface);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  width: min(760px, 100%);
  padding: clamp(20px, 4vw, 40px);
}

/* App-Shell: fasst die Inhalte in einen dezenten, halbtransparenten Rahmen,
   damit sie nicht verloren auf dem Hintergrund des Themes schweben. Weil
   sie durchscheinend ist, ziehen Lava, Blasen und Sterne unscharf hinter ihr
   durch – deshalb kommen ihre drei Werte aus der Welt und nicht von hier. */
.shell {
  width: min(980px, 100%);
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: clamp(16px, 3.5vw, 34px);
  border-radius: clamp(18px, 3vw, 28px);
  background: var(--shell-bg);
  border: 1px solid var(--shell-line);
  box-shadow: var(--shell-shadow);
  -webkit-backdrop-filter: blur(7px);
  backdrop-filter: blur(7px);
}
/* Login-Seiten: etwas mehr Luft unter dem Login-Container. */
.shell-auth { padding-bottom: clamp(40px, 9vh, 84px); }
@media (max-width: 560px) {
  .shell { padding: 14px; border-radius: 18px; }
  .shell-auth { padding-bottom: clamp(28px, 8vh, 60px); }
}

.brand {
  color: var(--on-dark);
  text-align: center;
  margin-bottom: clamp(16px, 4vw, 32px);
}
.brand h1 {
  font-size: clamp(28px, 6vw, 44px);
  margin: 0.2em 0 0;
  letter-spacing: 0.02em;
}
.brand .octo { font-size: clamp(48px, 12vw, 88px); line-height: 1; }

h2 { margin-top: 0; }

label { display: block; font-weight: 600; margin: 14px 0 6px; color: var(--ink-soft); }

input[type="text"], input[type="password"] {
  width: 100%;
  padding: 14px 16px;
  font-size: 18px;
  border: 2px solid var(--surface-soft);
  border-radius: 12px;
  background: var(--surface-soft);
  /* Eingabefelder erben die Schriftfarbe NICHT von selbst – ohne diese Zeile
     stünde in den dunklen Welten schwarze Schrift auf dunklem Grund. */
  color: var(--ink);
  font-family: inherit;
}
input:focus { outline: none; border-color: var(--accent); }

/* Knöpfe sind mal <button>, mal <a> – und die beiden sind sich von Haus aus
   NICHT ähnlich: Ein <button> erbt die Schrift der Seite nicht (er nimmt die
   des Betriebssystems) und zentriert seinen Text von selbst, ein <a> erbt die
   Schrift und richtet links aus. Nebeneinander gestellt ergab das
   unterschiedlich hohe Knöpfe – „Speichern" neben „Abbrechen", „Weiter" neben
   „Zurück". Die vier Zeilen font-family, line-height, inline-flex und
   text-decoration räumen das ein für alle Mal weg. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: 100%;
  margin-top: 20px;
  padding: 15px 20px;
  font-family: inherit;
  font-size: 18px;
  font-weight: 700;
  line-height: 1.2;
  text-decoration: none;
  color: var(--on-accent);
  background: var(--accent);
  border: none;
  border-radius: var(--btn-radius);
  cursor: pointer;
  transition: transform 0.08s ease, background 0.15s ease;
}
.btn:hover { background: var(--accent-strong); }
.btn:active { transform: translateY(1px); }

/* „Mit Homework einloggen": Trennlinie + Sekundär-Button unter dem Klassen-Login. */
.sso-sep {
  display: flex; align-items: center; gap: 12px;
  margin: 22px 0 4px; color: var(--ink-soft); font-size: 14px;
}
.sso-sep::before, .sso-sep::after {
  content: ""; flex: 1; height: 1px; background: var(--line);
}
.btn-homework {
  text-align: center; text-decoration: none;
  color: var(--accent-strong); background: var(--surface-soft);
  border: 2px solid var(--line); margin-top: 12px;
}
.btn-homework:hover { background: var(--press); }

/* Klassenauswahl bei Lehrer-SSO: Buttons gestapelt. */
.class-choice { display: flex; flex-direction: column; gap: 10px; }
.class-choice .btn { margin-top: 0; }

.error {
  background: var(--bad-bg);
  color: var(--bad);
  padding: 12px 16px;
  border-radius: 12px;
  margin-bottom: 12px;
  font-weight: 600;
}

/* Schülerkacheln */
.students {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: 14px;
  margin-top: 20px;
}
.students[hidden] { display: none; }
.student {
  background: var(--surface-soft);
  border: none;
  border-radius: 16px;
  padding: 16px 10px;
  text-align: center;
  cursor: pointer;
  transition: transform 0.1s ease, box-shadow 0.15s ease;
}
.student:hover { transform: translateY(-3px); box-shadow: var(--shadow); }
.student .avatar {
  width: 64px; height: 64px; border-radius: 50%;
  margin: 0 auto 8px;
  background: var(--tint) center/cover no-repeat;
  display: grid; place-items: center; font-size: 30px;
}
.student .name { font-weight: 700; font-size: 15px; }

/* Lehrpersonen versteckt hinter einem Umschalter; dezentes Icon auf der Kachel. */
.picker-actions {
  display: flex; justify-content: center; margin-top: 24px;
}
.teacher-toggle {
  background: transparent; border: none; cursor: pointer;
  color: var(--ink-soft); font-size: 14px; font-weight: 600;
  padding: 8px 14px; border-radius: 999px; transition: background 0.15s ease, color 0.15s ease;
}
.teacher-toggle:hover { background: var(--surface-soft); color: var(--ink); }
.student.is-teacher { position: relative; }
.student.is-teacher .teacher-badge {
  position: absolute; top: 8px; right: 8px; font-size: 16px; line-height: 1; opacity: 0.9;
}

/* Dezentes Icon für über Homework-SSO angelegte Schüler (Lehrer-Dashboard). */
.hw-badge { font-size: 12px; opacity: 0.75; margin-left: 4px; vertical-align: middle; cursor: help; }

/* Code-Eingabe als zentriertes Modal */
.code-overlay {
  position: fixed; inset: 0; background: rgba(6, 30, 40, .55);
  display: flex; align-items: center; justify-content: center; padding: 20px; z-index: 50;
}
.code-box {
  position: relative; background: var(--surface); color: var(--ink);
  border-radius: 22px; box-shadow: var(--shadow);
  padding: clamp(22px, 4vw, 30px); width: min(380px, 100%); text-align: center;
  animation: pop .3s ease;
}
.code-box h2 { margin: 0 0 4px; }
.code-box p { margin: 0; color: var(--ink-soft); }
.code-close {
  position: absolute; top: 12px; right: 12px;
  width: 34px; height: 34px; border: none; border-radius: 50%;
  background: var(--surface-soft); color: var(--ink-soft);
  font-size: 16px; line-height: 1; cursor: pointer;
}
.code-close:hover { background: var(--press); color: var(--ink); }
.code-dots { display: flex; gap: 14px; justify-content: center; margin: 24px 0; }
.code-dots .dot {
  width: 20px; height: 20px; border-radius: 50%;
  background: var(--surface-soft); border: 2px solid var(--line);
}
.code-dots .dot.filled { background: var(--accent); border-color: var(--accent); }
/* Warten auf die Antwort des Servers: Nach der vierten Ziffer vergeht bis zur
   Weiterleitung spürbar Zeit. Ohne Rückmeldung sieht es aus, als sei der Tipp
   nicht angekommen, und es wird nochmals getippt. Darum pulsieren die vier
   Punkte nacheinander weiter, und das Tastenfeld nimmt solange nichts an. */
.code-dots.checking .dot { animation: code-dot-pulse 1.1s ease-in-out infinite; }
.code-dots.checking .dot:nth-child(2) { animation-delay: 0.12s; }
.code-dots.checking .dot:nth-child(3) { animation-delay: 0.24s; }
.code-dots.checking .dot:nth-child(4) { animation-delay: 0.36s; }
@keyframes code-dot-pulse {
  0%, 100% { transform: scale(1); opacity: 0.75; }
  35% { transform: scale(1.12); opacity: 1; }
}
/* Abgeblendet statt weg: `.checking` beim Üben, `.is-off` im Krakenkampf
   (Countdown, Zwischenhalt, Ende). Die Tastatur zu entfernen nähme der Seite
   auf einen Schlag ihre halbe Höhe – der Rest spränge nach oben und beim
   Wiederauftauchen zurück, und wer gerade auf die Rechnung schaut, sucht sie
   plötzlich woanders. Getippt wird ohnehin nicht: Das sperrt boss.js. */
.checking .keypad, .keypad.is-off { opacity: 0.45; pointer-events: none; }
.keypad {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px;
  max-width: 320px; margin: 0 auto;
}
.keypad button {
  font-size: 26px; font-weight: 700; padding: 18px 0;
  border: none; border-radius: 14px; background: var(--surface-soft);
  color: var(--ink); cursor: pointer;
}
.keypad button:active { background: var(--press); }
.shake { animation: shake 0.4s; }
@keyframes shake {
  0%,100% { transform: translateX(0); }
  20%,60% { transform: translateX(-8px); }
  40%,80% { transform: translateX(8px); }
}

/* --- Schüler-Einstellungen --- */
.settings { width: min(760px, 100%); display: flex; flex-direction: column; gap: 16px; }
.settings-head { color: var(--on-dark); text-align: center; margin-bottom: 2px; }
.settings-head h1 { margin: 0; font-size: clamp(24px, 5vw, 32px); }
.settings-head p { margin: 5px 0 0; font-size: 14px; color: var(--on-dark-soft); }

.notice { border-radius: 12px; padding: 12px 16px; font-weight: 800; }
.notice.ok { background: var(--good-bg); color: var(--good); }
.notice.bad { background: var(--bad-bg); color: var(--bad); }

.setting-card {
  background: var(--surface); border-radius: 18px; box-shadow: var(--shadow);
  padding: clamp(18px, 3vw, 26px);
}
.setting-card-head { display: flex; align-items: flex-start; gap: 14px; margin-bottom: 18px; }
.setting-ic {
  flex: none; width: 46px; height: 46px; border-radius: 13px; display: grid; place-items: center;
  line-height: 1; background: var(--surface-soft); color: var(--ink-soft);
}
.setting-ic svg { width: 24px; height: 24px; display: block; }
.setting-ic.ic-photo { background: color-mix(in srgb, var(--accent) 14%, var(--surface)); color: var(--accent-strong); }
.setting-ic.ic-name  { background: color-mix(in srgb, var(--good) 16%, var(--surface)); color: var(--good-ink); }
.setting-ic.ic-pin   { background: color-mix(in srgb, var(--star) 18%, var(--surface)); color: var(--star-ink); }
.setting-ic.ic-sound { background: color-mix(in srgb, var(--accent) 14%, var(--surface)); color: var(--accent-strong); }
.setting-card-head h2 { margin: 0; font-size: 18px; }
.setting-card-head p { margin: 4px 0 0; font-size: 13px; color: var(--ink-soft); line-height: 1.4; }

/* Button-Varianten (kompakt statt volle Breite) */
.btn.btn--auto { width: auto; margin-top: 0; }

/* Profil */
.profile-row { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; }
.profile-pic-wrap { position: relative; flex: none; }
.profile-pic { width: 96px; height: 96px; font-size: 46px; margin: 0; }
/* kleines Löschen-Icon in der Ecke des hochgeladenen Bildes */
.pic-delete-form { position: absolute; top: -6px; right: -6px; margin: 0; }
.pic-delete {
  width: 30px; height: 30px; padding: 0; border-radius: 50%; border: 2px solid var(--surface); cursor: pointer;
  background: var(--bad); color: #fff; display: grid; place-items: center;
  box-shadow: 0 3px 8px rgba(6,30,40,0.28); transition: transform 0.1s ease, background 0.15s ease;
}
.pic-delete svg { width: 16px; height: 16px; display: block; }
.pic-delete:hover { background: #d94743; transform: scale(1.08); }
.profile-actions { flex: 1 1 220px; min-width: 200px; display: flex; flex-direction: column; align-items: flex-start; gap: 12px; }
.profile-actions form { width: 100%; display: flex; flex-direction: column; align-items: flex-start; gap: 10px; }
.file-input { font-size: 15px; color: var(--ink-soft); max-width: 100%; }
.file-input::file-selector-button {
  margin-right: 12px; padding: 10px 16px; border: none; border-radius: 12px; cursor: pointer;
  font-size: 15px; font-weight: 700; color: var(--accent-strong); background: var(--surface-soft);
  transition: background 0.15s ease;
}
.file-input::file-selector-button:hover { background: var(--press); }

/* Namensfelder */
.field-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 14px; }
.field label { margin: 0 0 6px; }
@media (max-width: 480px) { .field-grid { grid-template-columns: 1fr; } }

/* PIN-Bereich */
.code-panel { background: var(--surface-soft); border-radius: 16px; padding: clamp(16px, 3vw, 22px); }
.code-step { text-align: center; color: var(--ink-soft); font-weight: 600; margin: 0; }
.code-panel .code-dots { margin: 16px 0 20px; }
.code-panel .keypad button { background: var(--surface); box-shadow: 0 1px 2px rgba(6,30,40,0.08); }
.code-panel .keypad button:active { background: var(--press); }
.code-msg { text-align: center; margin-top: 12px; font-weight: 800; min-height: 1.2em; }

/* Spiel */
.game { width: min(720px, 100%); text-align: center; color: var(--on-dark); position: relative; }
.progress-track {
  height: 12px; background: var(--track-on-bg); border-radius: 999px;
  overflow: hidden; margin-bottom: clamp(16px, 4vw, 28px);
}
.progress-fill { height: 100%; width: 0; background: var(--star); transition: width 0.4s ease; }

/* Kopfzeile über dem Fortschrittsbalken: links die Serie, rechts das
   Sternen-Konto. Beides sitzt auf dem Seitenhintergrund, nicht auf der Karte –
   es bleibt darum auch stehen, wenn Pause oder STAR über der Karte liegen. */
.game-hud { display: flex; justify-content: space-between; align-items: center; min-height: 34px; margin-bottom: 8px; }
.hud-chip {
  display: inline-flex; align-items: center; gap: 6px; padding: 5px 12px; border-radius: 999px;
  background: rgba(255, 255, 255, 0.14); color: var(--on-dark);
  font-weight: 800; font-size: 15px; line-height: 1;
}
.hud-chip b { font-size: 17px; font-variant-numeric: tabular-nums; }
.hud-lb { font-weight: 700; font-size: 13px; color: var(--on-dark-soft); }
.hud-streak { margin-right: auto; }
.hud-stars { margin-left: auto; color: var(--star-on-bg); }
.hud-stars.tick { animation: chip-bump 0.35s ease; }
@keyframes chip-bump { 0% { transform: scale(1); } 40% { transform: scale(1.18); } 100% { transform: scale(1); } }
/* Ab fünf in Folge glimmt die Karte in Sternengold: die Serie ist spürbar,
   ohne dass etwas blinkt. */
.question.on-fire { box-shadow: var(--shadow), 0 0 0 3px var(--star), 0 0 28px rgba(245, 179, 1, 0.45); }
/* Serien-Meilenstein (5, 10, 15 …): der Ecken-Octopus hüpft. Eine Bewegung
   auf dem vorhandenen Element – kein neuer Clip, keine Ladezeit, und die
   Drosselung der Reaktionen in mascot.js bleibt, wie sie ist. */
.mascot-stage.octo-bounce { animation: octo-bounce 0.7s cubic-bezier(.34, 1.56, .64, 1); }
@keyframes octo-bounce {
  0%, 100% { transform: scale(1) translateY(0); }
  30% { transform: scale(1.12) translateY(-14px); }
  60% { transform: scale(0.98) translateY(2px); }
}

/* Die Pause. Keine Kästen im Stapel mehr: die grosse Zahl trägt die Seite,
   der Rekord ist ein Band darunter, die Kennzahlen zwei Pillen. Seltene
   Ereignisse (Medaille, Stufe, Tages-Aufgabe geschafft) bleiben Karten –
   das Laufende (Tages-Aufgabe) wird zu einem Streifen. */
.pause-title { margin-bottom: 4px; }
.pause-hero { text-align: center; padding: 6px 0 4px; }
.rs-main { display: flex; align-items: baseline; justify-content: center; gap: 8px; flex-wrap: wrap; }
.rs-num { font-size: clamp(48px, 12vw, 68px); font-weight: 900; color: var(--accent-strong); line-height: 1; font-variant-numeric: tabular-nums; }
.pause-hero.is-perfect .rs-num { color: var(--star-ink); }
.rs-of { font-size: 19px; font-weight: 800; color: var(--ink-soft); }
.rs-label { font-size: 15px; font-weight: 700; color: var(--ink-soft); }
.rs-trendrow { flex-basis: 100%; font-size: 13.5px; font-weight: 700; margin-top: 2px; }
.rs-trend.up { color: var(--good-ink); }
.rs-trend.down, .rs-trend.same { color: var(--ink-soft); }
.rs-record {
  display: inline-block; margin: 10px 0 0; padding: 6px 14px; border-radius: 999px;
  font-size: 14px; font-weight: 900; color: #8f6300;
  background: linear-gradient(135deg, #fff8e6, #fdefc9); border: 2px solid var(--star);
  animation: pop 0.5s;
}
.rs-row { display: flex; justify-content: center; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
.rs-pill {
  display: inline-flex; align-items: baseline; gap: 6px;
  background: var(--surface-soft); border-radius: 999px; padding: 7px 14px 7px 11px;
  font-size: 14px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ink-soft);
}
.rs-pill-ic { font-size: 15px; }
.rs-pill-lb { font-size: 12px; font-weight: 800; letter-spacing: 0.06em; text-transform: uppercase; }
.rs-val { font-size: 17px; font-weight: 900; color: var(--ink); }
/* Die Rechnung der schnellsten Aufgabe: gross, fett, in Akzent – sie ist die
   Nachricht, die Zeit nur der Nachsatz. */
.rs-term { font-size: 20px; font-weight: 900; color: var(--accent-strong); letter-spacing: 0.02em; white-space: nowrap; }
.rs-time { font-size: 13.5px; }

/* Ereignisse: Karten, mit Abstand zur Zahl und zu den Streifen. */
.pause-news { margin-top: 14px; }
.pause .medals { margin: 12px 0 4px; }
.pause .levelup { margin: 12px 0 4px; }
.pause .quest-card { margin: 12px 0 4px; }

/* Das Laufende: zwei Streifen nebeneinander, auf schmalen Geräten untereinander. */
.pause-side {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 230px), 1fr));
  gap: 10px; margin: 16px 0 6px; text-align: left;
}
.pause-mini {
  display: flex; align-items: flex-start; gap: 10px;
  background: var(--surface-soft); border-radius: 14px; padding: 10px 12px; min-width: 0;
}
.pause-mini.is-done { background: var(--good-bg); }
.pm-ic { flex: none; font-size: 22px; line-height: 1; margin-top: 2px; }
.pm-ic .medal-mini { width: 34px; height: 34px; font-size: 18px; }
.pm-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.pm-lb { font-size: 10.5px; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-soft); }
.pm-txt { font-size: 14px; font-weight: 900; color: var(--ink); line-height: 1.25; }
.pm-sub { font-size: 12.5px; font-weight: 700; color: var(--ink-soft); line-height: 1.3; }
.pm-bar { display: block; height: 6px; border-radius: 999px; background: var(--surface); overflow: hidden; margin-top: 4px; }
.pm-bar span { display: block; height: 100%; border-radius: 999px; background: linear-gradient(90deg, var(--star-ink), var(--star)); }

/* Die Mystery-Box: Octopus-Clips auf einem Knopf (mystery.js). Zwei
   Video-Ebenen liegen übereinander, sichtbar ist die mit .is-on – der Wechsel
   passiert erst, wenn der nächste Clip abspielbereit ist (kein Leerbild). Der
   Gewinn schwebt als HTML über der Kistenöffnung: die Kiste steht in allen
   Clips an derselben Stelle (Mitte etwa bei 61 % / 56 % des Bilds), darum
   sitzt er per Prozentmass bei jeder Grösse richtig. --mbox-size ist die
   Kantenlänge des quadratischen Bilds. */
/* Im Laden liegen die Kisten auf einer hellen Karte – die Beschriftung ist in
   Tinte gesetzt und wäre auf dem dunklen Seitenhintergrund nicht zu lesen. */
.mbox-row {
  display: flex; flex-wrap: wrap; gap: 14px 18px; justify-content: center;
  background: var(--surface); border-radius: 18px; padding: 14px 16px 12px; box-shadow: var(--shadow);
}
/* Im Regal steht nur eine Kiste; darunter der Hinweis auf die übrigen und –
   sobald sie offen ist – der Knopf für die nächste. Beide über die ganze
   Breite, damit sie unter der Kiste stehen statt daneben. */
.mbox-more { flex: 1 1 100%; margin: 0; text-align: center; font-size: 13px; font-weight: 700; color: var(--ink-soft); }
.mbox-next { flex: 0 0 auto; margin: 2px 0 4px; animation: mbox-pop 0.45s cubic-bezier(.34, 1.56, .64, 1); }
.mbox-stage { margin: 14px 0 6px; text-align: center; }
.mbox-lead { margin: 0 0 8px; font-weight: 900; font-size: 16px; color: var(--ink); }
.mbox { --mbox-size: 180px; display: inline-flex; flex-direction: column; align-items: center; gap: 2px; width: var(--mbox-size); }
.mbox-btn {
  position: relative; width: var(--mbox-size); height: var(--mbox-size); border: 0; background: none; cursor: pointer;
  padding: 0; font: inherit; border-radius: 18px;
  -webkit-tap-highlight-color: transparent; /* Touch-Chrome: kein graues Rechteck ums transparente Video */
}
.mbox-btn:focus-visible { outline: 3px solid var(--star); outline-offset: 2px; }
.mbox-vid {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain;
  opacity: 0; pointer-events: none;
}
.mbox-vid.is-on { opacity: 1; }
/* Der Gewinn: steigt aus der Kiste auf und wippt dann leicht im Leuchten. */
.mbox-prize {
  position: absolute; left: 61%; top: 68%; transform: translate(-50%, -50%) scale(0.3); opacity: 0;
  width: 44px; height: 44px; display: grid; place-items: center; pointer-events: none;
  font-size: 34px; line-height: 1; /* wie .shop-card-emoji – nicht grösser als im Regal */
  filter: drop-shadow(0 6px 10px rgba(6, 30, 40, 0.3));
  transition: transform 0.7s cubic-bezier(.34, 1.56, .64, 1), top 0.7s cubic-bezier(.34, 1.56, .64, 1), opacity 0.3s;
}
.mbox-prize::before { /* Lichtkegel aus der Kiste */
  content: ''; position: absolute; inset: -30%; border-radius: 50%; z-index: -1;
  background: radial-gradient(circle, rgba(255, 232, 150, 0.85) 0%, rgba(255, 214, 90, 0.35) 45%, rgba(255, 214, 90, 0) 70%);
}
.mbox-prize.is-on { top: 56%; transform: translate(-50%, -50%) scale(1); opacity: 1; }
.mbox-prize.is-on .mbox-prize-ic { display: block; animation: mbox-bob 2.6s ease-in-out 0.7s infinite; }
/* Gewonnener Avatar-Hintergrund: die eigene Figur, rund, mit dem neuen Verlauf –
   in derselben Grösse wie ein Emoji. */
.mbox-prize.is-avatar .mbox-prize-ic { width: 38px; height: 38px; }
.mbox-prize-av {
  display: block; width: 100%; height: 100%; border-radius: 50%; object-fit: cover;
  background: var(--surface); box-shadow: 0 0 0 2px var(--surface), 0 4px 10px rgba(6, 30, 40, 0.3);
}
.mbox-prize-n {
  position: absolute; right: -10px; bottom: -2px; background: var(--star); color: var(--ink); border-radius: 999px;
  padding: 1px 7px; font-size: 12px; font-weight: 900; font-variant-numeric: tabular-nums;
  box-shadow: 0 2px 6px rgba(6, 30, 40, 0.25);
}
.mbox-name { font-weight: 800; font-size: 14px; color: var(--ink); text-align: center; }
.mbox-hint { font-size: 12.5px; font-weight: 700; color: var(--ink-soft); text-align: center; min-height: 1.3em; }
.mbox.is-fresh .mbox-btn { animation: mbox-glow 1.4s ease-in-out infinite; }
.mbox.is-shaking .mbox-btn, .mbox.is-open .mbox-btn { cursor: default; animation: none; }
/* Der Satz zum Los: springt als Plakette unter der Kiste auf – gross genug,
   dass er neben der Figur besteht. Gold bei Sternen, hell bei einem Stück,
   leise bei der Niete. */
.mbox.is-open .mbox-hint {
  font-size: 16px; font-weight: 600; line-height: 1.25; color: var(--ink);
  background: var(--tint); border: 1px solid var(--tint-line); border-radius: 14px; padding: 7px 14px;
  /* Darf breiter sein als die Kiste: eine Zeile, solange der Platz reicht. */
  width: max-content; max-width: min(420px, calc(100vw - 48px)); white-space: normal;
  animation: mbox-pop 0.55s cubic-bezier(.34, 1.56, .64, 1);
}
.mbox.is-open.is-stars .mbox-hint { color: var(--star-ink); background: rgba(245, 179, 1, 0.18); border-color: rgba(245, 179, 1, 0.45); }
.mbox.is-open .mbox-hint b { font-weight: 900; }
.mbox.is-open.is-stars .mbox-hint { font-weight: 900; }
.mbox.is-open.is-nothing .mbox-hint { color: var(--ink-soft); font-weight: 600; font-size: 14px; }
@keyframes mbox-pop {
  0% { transform: scale(0.6); opacity: 0; }
  100% { transform: scale(1); opacity: 1; }
}
@keyframes mbox-glow {
  0%, 100% { transform: translateY(0); filter: drop-shadow(0 0 0 rgba(245, 179, 1, 0)); }
  50% { transform: translateY(-4px); filter: drop-shadow(0 0 12px rgba(245, 179, 1, 0.7)); }
}
@keyframes mbox-bob {
  0%, 100% { transform: translateY(0) rotate(-3deg); }
  50% { transform: translateY(-6px) rotate(3deg); }
}
@media (prefers-reduced-motion: reduce) { .mbox-prize.is-on .mbox-prize-ic { animation: none; } }
/* Im Spiel: die Kiste mittig auf dem Panel, etwas grösser. */
.pause .mbox, .panel .mbox { --mbox-size: 220px; }
.box-rows select, .box-rows input { font-size: 13px; }
/* Zwischentitel im Inhalts-Editor (Sterne, Themes, …) – wie in der Übersicht. */
.box-rows .box-group td {
  padding: 10px 8px 2px; font-size: 11px; font-weight: 800; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--ink-soft); border-bottom: 0;
}
.box-rows td { vertical-align: middle; }
.box-fill { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin: 0 0 10px; }
.box-fill select { max-width: 260px; }
.box-rows [data-only] { display: inline-block; }
.box-rows [data-only][hidden] { display: none; }

/* Konfetti (confetti.js): ein Canvas über allem, anderthalb Sekunden. */
.konfetti { position: fixed; inset: 0; pointer-events: none; z-index: 9999; }

.question {
  background: var(--surface); color: var(--ink);
  border-radius: var(--radius); box-shadow: var(--shadow);
  padding: clamp(20px, 5vw, 40px);
  position: relative; overflow: hidden;
  /* FESTE Höhe: das Feld bewegt sich weder bei Eingabe noch zwischen Aufgaben. */
  height: clamp(160px, 30vw, 210px);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
}
/* Start-Button & Countdown im weissen Feld (nicht als Vollbild). */
.card-cover {
  position: absolute; inset: 0; z-index: 3; background: var(--surface);
  border-radius: var(--radius); display: grid; place-items: center; padding: 16px;
}
.card-cover.hidden { display: none; }
.start-btn {
  width: auto; margin: 0; padding: 16px 32px; font-size: clamp(20px, 5vw, 26px);
  border-radius: 16px;
  display: inline-flex; align-items: center; gap: 0.5em;
}
/* Kurze Erklärung über dem Startknopf – nur beim Einstufungstest. */
.card-cover .start-note {
  margin: 0 0 14px; max-width: 26em; text-align: center; text-wrap: balance;
  color: var(--ink-soft); font-size: clamp(14px, 3.2vw, 17px); line-height: 1.35;
}
.start-btn .start-ic { display: inline-flex; line-height: 0; }
.start-btn .start-ic svg { width: 1.15em; height: 1.15em; } /* Lernen-Icon statt 🎮 */
.card-cover .countdown-num {
  color: var(--ink); font-size: clamp(64px, 20vw, 120px); text-shadow: none;
}
.card-cover .countdown-num.go { color: var(--accent-strong); font-size: clamp(48px, 15vw, 96px); }
/* Rechnung und eingegebenes Resultat auf EINER Zeile: 3 · 4 = [Zahl].
   Feste Breiten für Faktoren und Antwort → die Rechnung wackelt nicht beim
   Tippen und nicht zwischen Aufgaben (unterschiedlich lange Zahlen). */
.question .term {
  font-size: clamp(28px, 9vw, 60px); font-weight: 800; letter-spacing: 0.01em;
  display: flex; flex-wrap: nowrap; align-items: center; justify-content: center;
  gap: 0.08em 0.35em; font-variant-numeric: tabular-nums; line-height: 1.15;
  max-width: 100%;
}
.question .term-op, .question .term-eq { color: var(--ink-soft); }
/* Beide Rechenzeichen hängen optisch zu tief: der Doppelpunkt sitzt auf der
   Grundlinie (oberer Punkt auf x-Höhe), der Malpunkt auf x-Mitte – die
   Ziffern reichen aber bis zur Versalhöhe. Anheben auf die Ziffernmitte. */
.question .term-op { transform: translateY(-0.06em); }
.question .term-a, .question .term-b {
  display: inline-block; min-width: 2ch;
}
/* Zum Zeichen hin ausgerichtet: eine einstellige Zahl hätte zentriert im
   2ch-Feld auf beiden Seiten Luft, eine zweistellige keine – „10 : 5" sähe
   dann so aus, als stünde die 10 näher am Zeichen als die 5. Die Leerbreite
   liegt jetzt immer aussen, der Abstand zum Zeichen ist konstant. */
.question .term-a { text-align: right; }
.question .term-b { text-align: left; }
/* Feste Höhe/Breite: die Ausfülllinie bleibt stehen, es rutscht nichts beim Tippen. */
.question .answer {
  display: inline-block; min-width: 3ch; height: 1.15em; line-height: 1.15em;
  text-align: left; font-weight: 800; color: var(--accent-strong);
  border-bottom: 5px solid var(--surface-soft);
}
/* Lückenaufgabe: `_ · 8 = 56`. Die Eingabe wandert an die erste Stelle und
   trägt dort die Ausfülllinie; das Produkt rechts steht fest in Tintenfarbe.
   Die Breiten bleiben dieselben wie bei der normalen Aufgabe – das Feld darf
   auch hier nicht wackeln. */
.question.is-gap .term-a {
  color: var(--accent-strong); text-align: center;
  /* Dieselbe Ausfülllinie wie bei der normalen Antwort. Kein erklärender
     Satz darunter – die Lücke im Term sagt es selbst. */
  border-bottom: 5px solid var(--surface-soft); height: 1.15em; line-height: 1.15em;
}
.question.is-gap-right .term-b {
  color: var(--accent-strong); text-align: center;
  border-bottom: 5px solid var(--surface-soft); height: 1.15em; line-height: 1.15em;
}
.question.is-gap .answer, .question.is-gap-right .answer { color: var(--ink); border-bottom-color: transparent; }

/* Der Server braucht ausnahmsweise länger: ein leiser Balken am oberen Rand
   des Aufgabenfelds. So sieht das Kind, dass die Eingabe angekommen ist,
   statt vor einem stillstehenden Feld zu sitzen. */
.question.warten::before {
  content: ''; position: absolute; left: 0; top: 0; z-index: 2;
  width: 40%; height: 4px; border-radius: 4px;
  background: linear-gradient(90deg, transparent, var(--star), transparent);
  animation: warten-lauf 1.1s ease-in-out infinite;
}
@keyframes warten-lauf {
  from { transform: translateX(-100%); }
  to { transform: translateX(250%); }
}
.question.correct { animation: pop 0.4s; }
.question.wrong { animation: shake 0.4s; }
@keyframes pop { 0% { transform: scale(1); } 50% { transform: scale(1.06); } 100% { transform: scale(1); } }

.term-a, .term-b { color: var(--ink); }
/* Hinweis-Octopus (hint1): sitzt an DERSELBEN Stelle wie der idle-Octopus
   (.mascot-stage). hint1 ist grösser (1332×1604, Octopus unten links, +500 px
   oben/rechts). Das Video wird unten-links verankert und auf 160.1 % (=1332/832)
   der Box-Breite skaliert → der Octopus-Teil deckt die idle-Box exakt, die
   Hinweis-Geste ragt oben/rechts darüber hinaus.
   → Geometrie (right/top/width) mit .mascot-stage synchron halten! */
.hint-octo {
  position: absolute; z-index: 5; right: -36px;
  top: clamp(-10px, 0.4vw, 8px);
  width: clamp(88px, 23vw, 128px); aspect-ratio: 832 / 1104;
  opacity: 0; transform: scale(0.9);
  transition: opacity 0.3s ease, transform 0.3s cubic-bezier(.34,1.56,.64,1);
  pointer-events: none;
}
.hint-octo.show { opacity: 1; transform: none; cursor: pointer; pointer-events: auto; -webkit-tap-highlight-color: transparent; }
.hint-octo-vid {
  position: absolute; left: 0; bottom: 0;
  width: 160.1%; aspect-ratio: 1332 / 1604; height: auto; /* Octopus-Teil = idle-Box */
  object-fit: contain; display: block;
}
@media (max-width: 560px) {
  .hint-octo { right: -16px; width: clamp(76px, 21vw, 108px); }
}

/* Maskottchen (Octopus) --------------------------------------------------- */
/* Zwei gestapelte Video-Ebenen (Crossfade). Transparente WebM/HEVC-Clips. */
.mascot-host { pointer-events: none; }
.mascot-vid {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: contain; /* harte Schnitte zwischen Clips (kein Fade) */
}
/* Begleiter im Spiel: Octopus über der oberen rechten Ecke der Aufgaben-Karte;
   darf bewusst über den weissen Container hinausragen. Position/Grösse leicht
   anpassbar. */
.mascot-stage {
  position: absolute; z-index: 4; right: -36px;
  top: clamp(-10px, 0.4vw, 8px); /* leicht über der oberen Kante, etwas tiefer */
  /* Box im echten Videoformat (832×1104) statt quadratisch → keine seitlichen
     Leerränder, damit der Octopus wirklich über die rechte Kante der Karte ragt. */
  width: clamp(88px, 23vw, 128px); aspect-ratio: 832 / 1104;
  opacity: 0; transform: scale(0.6) translateY(8px);
  transition: opacity 0.35s ease, transform 0.35s cubic-bezier(.34,1.56,.64,1);
}
.mascot-stage.is-visible { opacity: 1; transform: scale(1) translateY(0); } /* Pop-in */
@media (max-width: 560px) {
  .mascot-stage { right: -16px; width: clamp(76px, 21vw, 108px); }
}

/* Grosse Feier-Variante (Medaillen) innerhalb eines Overlay-Panels. */
.mascot-celebrate {
  display: block; margin: 4px auto 6px; width: clamp(150px, 46vw, 240px);
  height: clamp(150px, 46vw, 240px); object-fit: contain;
}

/* Begrüssungs-Maskottchen (Home/Login): einmaliger Auftritt. */
.mascot-greet {
  position: relative; display: inline-block;
  width: clamp(96px, 24vw, 140px); height: clamp(96px, 24vw, 140px);
  opacity: 0; transform: scale(0.7);
  transition: opacity 0.4s ease, transform 0.4s cubic-bezier(.34,1.56,.64,1);
}
.mascot-greet.is-visible { opacity: 1; transform: scale(1); }
/* Login-Seite (Schülerauswahl): grösserer Auftritt als auf Home; negatives
   oben/unten-Margin holt die transparente Rand-Fläche im Clip wieder rein
   (weniger Abstand zu Überschrift/Panel). */
.brand .mascot-greet {
  width: clamp(150px, 40vw, 240px); height: clamp(150px, 40vw, 240px);
  /* oben / unten getrennt – unten noch enger an die Überschrift. */
  margin: clamp(-34px, -7vw, -16px) 0 clamp(-56px, -11vw, -30px);
}

/* Home: deutlich grösser als die Basis-Variante, mit weniger Abstand oben/unten. */
.home-hero .mascot-greet {
  width: clamp(160px, 42vw, 250px); height: clamp(160px, 42vw, 250px);
  margin-top: clamp(-32px, -6vw, -14px);
  margin-bottom: clamp(-32px, -6vw, -14px);
}

.octo-wrap { margin-top: clamp(16px, 4vw, 28px); font-size: clamp(48px, 14vw, 96px); }
.octo-speech {
  display: inline-block; background: var(--surface); color: var(--ink);
  padding: 10px 18px; border-radius: 18px; font-weight: 700; font-size: 20px;
  box-shadow: var(--shadow); margin-bottom: 10px;
}

/* Pause / Medaillen */
.medals { display: grid; gap: 14px; margin: 20px 0; }
.medal {
  display: flex; align-items: center; gap: 14px;
  background: var(--surface); color: var(--ink);
  border-radius: 16px; padding: 14px 18px; box-shadow: var(--shadow);
  animation: pop 0.5s;
}
.medal .icon { font-size: 40px; }
.medal .medal-badge { margin: 0; flex: none; }
/* Der Textblock nimmt den Rest der Zeile und zentriert darin. Ohne `flex: 1`
   ist er nur so breit wie sein Inhalt: die von `.game` geerbte Zentrierung
   wirkt dann innerhalb dieser Inhaltsbreite und nicht in der Karte, und der
   Name sitzt je nach Textlänge woanders. `min-width: 0` lässt lange
   Beschreibungen umbrechen, statt die Karte zu sprengen. */
.medal .medal-text { flex: 1; min-width: 0; text-align: center; }
.medal .title { font-weight: 800; }
.medal .desc { color: var(--ink-soft); font-size: 14px; }

/* Stufenwechsel in der Pause.
   Der Aufstieg ist der grösste Moment einer Runde und stand bisher als
   fettgedruckte Zeile zwischen Medaillen und Knopf – er bekommt jetzt eine
   eigene Karte in Sternengold, der Farbe, die im Spiel „geschafft" heisst.
   Die Verschiebung nach unten benutzt dieselbe Karte, aber ohne Signalfarbe:
   sie ist kein Misserfolg und soll auch nicht wie einer aussehen. */
.levelup {
  display: flex; align-items: center; gap: 14px; text-align: left;
  margin: 18px 0 4px; padding: 14px 18px;
  border: 2px solid; border-radius: 18px;
}
.levelup-badge {
  flex: none; width: 52px; height: 52px; border-radius: 50%;
  display: grid; place-items: center;
}
.levelup-badge svg { width: 27px; height: 27px; display: block; }
.levelup-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.levelup-title { font-size: 19px; font-weight: 900; line-height: 1.15; text-wrap: balance; }
.levelup-sub { font-size: 14px; font-weight: 600; color: var(--ink-soft); line-height: 1.35; }

.levelup-up {
  background: linear-gradient(135deg, #fff8e6, #fdefc9);
  border-color: var(--star);
  box-shadow: 0 8px 22px rgba(245, 179, 1, 0.24);
  animation: pop 0.5s;
}
.levelup-up .levelup-badge {
  background: linear-gradient(150deg, #ffd873, var(--star) 58%, #d99a00);
  color: #fff; box-shadow: 0 0 0 5px rgba(245, 179, 1, 0.2);
}
.levelup-up .levelup-title { color: #8f6300; }
/* Die Pfeile steigen zweimal auf, dann ist Ruhe – ein Hinweis, kein Blinken. */
.levelup-up .levelup-badge svg { animation: levelup-rise 1.5s ease-out 2; }
@keyframes levelup-rise {
  0%, 55%, 100% { transform: translateY(0); }
  28% { transform: translateY(-3.5px); }
}

.levelup-down { background: var(--surface-soft); border-color: var(--line); }
.levelup-down .levelup-badge { background: var(--tint); color: var(--ink-soft); }
/* Ohne Schlagzeile trägt der ganze Satz die Titelzeile – der darf leiser sein. */
.levelup-down .levelup-title { color: var(--ink); font-size: 16px; font-weight: 700; }

.hidden { display: none !important; }
/* Und das hidden-Attribut muss stärker sein als jede display-Angabe von uns:
   Ein .shop-buy ist inline-flex, und das schlüge sonst das display:none, das
   der Browser für [hidden] von sich aus mitbringt – der Knopf bliebe stehen,
   obwohl er versteckt sein soll. */
[hidden] { display: none !important; }
/* „Weniger Bewegung“ – bewusst KEINE globale Regel. Windows ist hier kein
   verlässliches Signal: Chrome und Edge melden `prefers-reduced-motion: reduce`,
   sobald die „Animationseffekte“ aus sind (Barrierefreiheit → Visuelle Effekte
   oder „Für beste Leistung anpassen“) – auf vielen Schulgeräten ist das der
   Normalzustand. Mit einer globalen Sperre stand dort die ganze App still: kein
   Wackeln bei falscher PIN, kein Pop bei richtiger Antwort, keine Ladeanzeige.
   Eine Zeit lang blieb noch die Hintergrundwelt der Themes (fx-* auf
   body::before/::after) stehen – aber genau die ist im Laden gekauft: Wer sich
   den Vulkan leistet und dann nur stillstehende Farben sieht, hält das Theme
   für kaputt. Darum läuft auch sie jetzt überall; sie bewegt sich nur mit
   transform und kostet die Grafikkarte, nicht den Hauptprozessor.
   Dieselbe Überlegung wie bei den Octopus-Clips (mascot.js), den Trick-
   Erklärungen (tricks.js) und dem Medaillen-Glanz. Wo einzelne Bewegungen bei
   `reduce` trotzdem still stehen sollen (Pulsieren von Warnbalken, Dots …),
   steht das als eigene Regel bei der jeweiligen Klasse. */

/* Animierte Trick-Erklärungen mit Punktefeld */
.trick { text-align: center; }
.trick-title { font-size: 22px; font-weight: 800; margin-bottom: 6px; color: var(--accent-strong); }
/* Abgetrennter Kopf: die ursprüngliche Aufgabe, immer klar sichtbar oben. */
.trick-top {
  background: var(--tint); border: 2px solid var(--tint-line); border-radius: 16px;
  padding: 10px 16px 14px; margin-bottom: 16px;
}
.trick-top-label {
  font-size: 12px; font-weight: 800; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--ink-soft); margin-bottom: 4px;
}
/* Die ursprüngliche Rechnung. Am Schluss wird hier das Resultat im grünen Kasten eingesetzt. */
.trick-rechnung {
  font-size: clamp(26px, 7vw, 38px); font-weight: 900; color: var(--ink);
  display: inline-flex; align-items: center; gap: 10px; flex-wrap: wrap;
  justify-content: center; font-variant-numeric: tabular-nums;
}
/* Getrennter Arbeitsbereich: hier wird gerechnet. */
.trick-work { padding-top: 2px; }
/* Instruktion ÜBER dem Arbeitsfeld (leer = kein Platzverbrauch). */
.trick-lead { font-size: 17px; font-weight: 700; color: var(--ink); }
.trick-lead:not(:empty) { margin-bottom: 12px; }

.trick-stage {
  display: inline-grid; gap: clamp(2px, 1.1vw, 6px);
  margin: 6px auto; padding: 10px; border-radius: 14px;
  background: var(--surface-soft); max-width: 100%;
  transition: transform 0.6s ease;
}
.trick-stage.rot { transform: rotate(90deg); }

.t-dot {
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #ff7b76, #e0403b);
  opacity: 0; transform: scale(0.3);
  transition: opacity 0.3s ease, transform 0.3s ease, background 0.4s ease;
}
.t-dot.show { opacity: 1; transform: scale(1); }
.t-dot.removed { opacity: 0.22; transform: scale(0.62); background: #c4d2d9; }
.t-dot.g2 { background: radial-gradient(circle at 35% 30%, #ffe38a, #f5b301); }
.t-dot.g3 { background: radial-gradient(circle at 35% 30%, #7bd0ff, #1fa0d6); }
.t-dot.pulse { box-shadow: 0 0 0 4px rgba(31,182,214,0.35); }

.trick-caption {
  min-height: 1.4em; font-size: 17px; font-weight: 700; color: var(--ink);
  margin: 10px 0 2px; transition: opacity 0.25s ease;
}
.trick-badge {
  display: inline-block; margin-top: 6px;
  background: var(--star); color: #5a4000; font-weight: 900; font-size: 26px;
  padding: 6px 18px; border-radius: 14px; animation: pop 0.5s;
}
.trick-weiter { width: auto; margin-top: 14px; padding: 12px 26px; }
/* Feste Höhe reserviert das Zahlenfeld, damit der Container beim Wechsel
   „Weiter-Knopf ↔ Zahlenfeld" nicht springt. Inhalt oben (nah an der Caption).
   Am Trick-Ende wird die Reservierung per JS aufgehoben (Sterne-Aufgaben). */
.trick-controls {
  min-height: clamp(330px, 58vw, 400px);
  display: flex; flex-direction: column; align-items: center; justify-content: flex-start;
}

/* Bleibende Zwischenresultate (Rechenweg), farblich an die Punktgruppen gekoppelt */
.trick-notes { margin: 8px 0 18px; display: flex; flex-direction: column; gap: 6px; align-items: center; }
.trick-notes:not(:empty)::before {
  content: "Rechenweg";
  font-size: 12px; font-weight: 800; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--ink-soft); margin-bottom: 2px;
}
/* Zwischenresultate: gestrichelte Kontur – überall gleich als „noch nicht fertig". */
.trick-note-line {
  font-size: 18px; font-weight: 800; color: var(--ink); font-variant-numeric: tabular-nums;
  display: inline-block; border: 2px dashed var(--dash); border-radius: 10px;
  padding: 2px 12px; background: var(--dash-bg);
}
.trick-note-line.nl-g2 { color: #d29200; }   /* wie die gelben (g2) Punkte */
.trick-note-line.nl-g3 { color: var(--accent-strong); } /* wie die blauen (g3) Punkte */
.trick-note-line.nl-zero { color: var(--star); }

/* Rahmen um einen Reihen-Block (z. B. 4er: 2 Reihen = ein Block) */
.t-frame {
  position: absolute; box-sizing: border-box; pointer-events: none;
  border: 3px solid var(--accent-strong); border-radius: 10px;
  animation: pop 0.4s;
}
.t-frame.f-copy { border-color: #f0850c; }
.t-frame.f-red { border-color: #e0403b; }
.t-frame.f-gold { border-color: #d29200; }
.t-frame.f-good { border-color: var(--good); }
.t-frame-label {
  position: absolute; top: -12px; left: 8px;
  background: var(--accent-strong); color: var(--on-accent);
  font-size: 12px; font-weight: 800; padding: 1px 8px; border-radius: 8px; white-space: nowrap;
}
.t-frame.f-copy .t-frame-label { background: #f0850c; }
.t-frame.f-red .t-frame-label { background: #e0403b; }
.t-frame.f-gold .t-frame-label { background: #d29200; }
.t-frame.f-good .t-frame-label { background: var(--good); }

/* Trick-Zahlenfeld (Eingabe der Zwischen-/Endrechnung) */
.trick-pad { margin-top: 2px; }
.trick-pad.wrong { animation: shake 0.4s; }
.trick-pad-disp {
  font-size: clamp(34px, 9vw, 52px); font-weight: 900; color: var(--accent-strong);
  min-height: 1.2em; margin-bottom: 8px; font-variant-numeric: tabular-nums;
}
/* Mit umrahmter Aufgabe: klar sichtbar, was gerade gerechnet wird. */
.trick-pad-disp.has-eq {
  display: inline-flex; align-items: baseline; gap: 4px;
  padding: 10px clamp(14px, 4vw, 22px); margin: 0 auto 12px;
  border: 3px solid var(--accent); border-radius: 16px;
  background: var(--tint); box-shadow: var(--shadow);
}
/* Zwischenresultat-Eingabe (noch nicht das Endergebnis): gestrichelter Rahmen. */
.trick-pad-disp.has-eq.inter { border-style: dashed; border-color: var(--dash); background: var(--dash-bg); }
.trick-pad-disp .pad-eq { color: var(--ink); }
/* Eingabe-Slot als Ausfülllinie – funktioniert am Ende und mitten in der Rechnung (7 + ▢ = 10). */
.trick-pad-disp.has-eq .pad-val {
  display: inline-block; min-width: 1.3em; text-align: center;
  border-bottom: 3px solid var(--accent); line-height: 1.05;
}
.trick-keypad { margin: 0 auto; }

/* Farbige Markierung direkt in der Rechnung oben */
.c-z { color: #1fa0d6; }          /* Zehner – blau */
.c-e { color: #f0850c; }          /* Einer – orange */
.c-zero { color: var(--star); }   /* die angehängte 0 / ×10 */
.c-g2 { color: #d29200; }         /* wie die gelben (g2) Zusatzpunkte */
.c-base { color: #e0403b; }       /* wie die roten Grundpunkte */
.c-nb1 { color: #1573c9; }        /* Nachbar-Trick: die Zahl (6) */
.c-nb2 { color: #4a9fe0; }        /* Nachbar-Trick: der Nachbar (5) – ähnliche Farbe */

/* 9er-Trick: Zwischenrechnung im Arbeitsbereich (9 · n = [Zehner][Einer]) */
.nine-eq {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: clamp(28px, 8vw, 42px); font-weight: 900; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.nine-box {
  display: inline-block; min-width: 40px; height: 44px; line-height: 40px;
  text-align: center; padding: 0 6px; margin: 0 3px; border-radius: 12px;
  background: var(--tint); box-shadow: inset 0 0 0 3px var(--line); color: var(--empty-ink);
  vertical-align: middle;
}
.nine-box.filled.z, .nine-box.active.z { box-shadow: inset 0 0 0 3px #1fa0d6; color: #1fa0d6; }
.nine-box.filled.e, .nine-box.active.e { box-shadow: inset 0 0 0 3px #d99a00; color: #d99a00; }
.nine-box.active { animation: nine-pulse 0.9s ease-in-out infinite; }
.nine-box.active.z { background: color-mix(in srgb, var(--accent) 14%, var(--surface)); }
.nine-box.active.e { background: color-mix(in srgb, var(--star) 16%, var(--surface)); }
/* 9er-Farben: die „andere" Zahl (n) violett, Zehner blau, Einer gelb */
.c-nine-n { color: #7c4dff; }
.c-nine-z { color: #1fa0d6; }
.c-nine-e { color: #d99a00; }
@keyframes nine-pulse {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.16); }
}

/* Zahl-Visualisierung (z. B. 10er: grosse 0 anhängen) */
.trick-stage.num {
  display: flex; align-items: center; justify-content: center; gap: 4px;
  min-height: 120px; background: var(--surface-soft);
}
.big-num, .big-zero {
  font-size: clamp(40px, 12vw, 68px); font-weight: 900; color: var(--ink);
  font-variant-numeric: tabular-nums; line-height: 1;
}
/* Ganze Zwischenrechnung im grossen Feld: „6 · 10 = 60" */
.big-calc {
  display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap;
  justify-content: center; font-size: clamp(26px, 8vw, 50px); font-weight: 900;
  color: var(--ink); font-variant-numeric: tabular-nums; line-height: 1;
}
.big-calc .big-num, .big-calc .big-zero { font-size: 1em; }
.big-eq { white-space: nowrap; }
/* Farbmarkierung muss die Standardfarbe der grossen Zahlen überschreiben */
.big-num.c-e { color: #f0850c; }
.big-num.c-z { color: #1fa0d6; }
.big-num.c-zero { color: var(--star); }
/* Zwischenresultat im grossen Feld: gestrichelte Kontur signalisiert „noch nicht fertig". */
.trick-stage.big-inter { border: 3px dashed var(--dash); background: var(--dash-bg); }
/* Ergebnis-Kasten in der Aufgabe: anfangs leer (grau, gestrichelt), am Schluss grün gefüllt. */
.trick-result-box {
  display: inline-block; border: 3px solid; border-radius: 12px;
  padding: 0 16px; min-width: 1.5em; text-align: center;
}
.trick-result-box.empty { background: var(--empty-bg); color: var(--empty-ink); border-color: var(--empty-line); border-style: dashed; }
.trick-result-box:not(.empty) { background: var(--good-bg); color: var(--good); border-color: var(--good); animation: pop 0.4s; }
.big-zero { color: var(--star); opacity: 0; transform: translateY(-40px) scale(0.4); }
.big-zero.drop { animation: zero-drop 0.6s cubic-bezier(0.3, 1.4, 0.5, 1) forwards; }
@keyframes zero-drop {
  to { opacity: 1; transform: none; }
}
/* × zwischen zwei grossen Zahlen (Umkehrrechnung) */
.big-op {
  font-size: clamp(32px, 9vw, 60px); font-weight: 800; color: var(--ink-soft);
  margin: 0 clamp(8px, 3vw, 20px); align-self: center;
}
.big-num.swap { animation: num-swap 0.45s ease; }
@keyframes num-swap {
  0% { transform: translateY(10px) scale(0.7); opacity: 0.3; }
  60% { transform: translateY(-4px) scale(1.08); }
  100% { transform: none; opacity: 1; }
}

/* Sterne-Leiste + Lösungseingabe direkt in der Trick-Hilfe */
.star-tray { margin-top: 20px; padding-top: 16px; border-top: 2px dashed var(--line); text-align: center; }
.star-slots { display: flex; gap: 10px; justify-content: center; font-size: 40px; line-height: 1; margin-top: 4px; }
.star-slot { color: var(--empty-ink); transition: transform 0.2s ease; }
.star-slot.got { color: var(--star); animation: pop 0.5s; }
.star-slot.miss { color: var(--empty-ink); }
.star-input { margin-top: 14px; }
.star-input.wrong { animation: shake 0.4s; }
.star-input.correct { animation: pop 0.4s; }
.star-bad { color: var(--bad); text-decoration: line-through; }
.star-lead {
  font-weight: 800; font-size: 18px; color: var(--accent-strong);
  margin-bottom: 10px;
}
.star-frage {
  font-size: clamp(28px, 8vw, 44px); font-weight: 900; color: var(--ink);
  margin-bottom: 12px; font-variant-numeric: tabular-nums;
}
.star-answer { color: var(--accent-strong); }
.star-input .game-keypad { margin-top: 0; }

/* Durchgängiges Schüler-Menü oben */
.topmenu {
  display: flex; gap: 6px; flex-wrap: wrap; justify-content: center;
  width: min(880px, 100%); margin: 0 auto clamp(14px, 3vw, 24px);
  background: var(--menu-bg); padding: 8px; border-radius: 16px;
}
.topmenu-item {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  flex: 1 1 80px; min-width: 68px; padding: 8px 6px; border-radius: 12px;
  color: var(--on-dark); text-decoration: none; font-weight: 700; font-size: 12px;
  background: transparent; border: none; cursor: pointer;
}
.topmenu-item .ic { display: block; line-height: 0; }
.topmenu-item .ic svg { display: block; width: 26px; height: 26px; }
.topmenu-item:hover { background: var(--menu-hover); }

/* „Lernen" ist nicht einer von fünf gleichwertigen Punkten – es ist der Grund,
   warum es Octomat gibt. Medaillen und Shop sind die Belohnung dafür und
   dürfen optisch nicht mit ihm gleichziehen. Deshalb steht es in der
   Akzentfarbe, auch wenn man gerade woanders ist. */
.topmenu-item.is-main {
  background: var(--accent); color: var(--on-accent);
  box-shadow: 0 4px 14px rgba(6, 30, 40, 0.28);
}
.topmenu-item.is-main:hover { background: var(--accent-strong); }
/* Ist man dort, gewinnt der Aktiv-Zustand: sonst wüsste man nicht mehr, wo
   man steht. Ein Rand in der Akzentfarbe hält die Sonderrolle trotzdem fest. */
.topmenu-item.active { background: var(--surface); color: var(--ink); }
.topmenu-item.is-main.active { box-shadow: 0 0 0 2px var(--accent); }
.topmenu form, .topmenu .logout { margin: 0; }
.topmenu .logout button {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  background: transparent; border: none; color: var(--on-dark); font-weight: 700;
  font-size: 12px; cursor: pointer; width: 100%;
}

/* Startseite */
.home { width: min(880px, 100%); display: flex; flex-direction: column; gap: 14px; }

.home-hero { display: flex; align-items: center; gap: 18px; color: var(--on-dark); }
.home-photo {
  width: 84px; height: 84px; border-radius: 50%; flex: none;
  background: var(--tint) center/cover no-repeat; border: 3px solid rgba(255,255,255,0.55);
  box-shadow: 0 6px 18px rgba(6,30,40,0.25);
  display: grid; place-items: center; font-size: 40px;
}
.home-hi { min-width: 0; }
.home-greet {
  margin: 0; font-size: 13px; font-weight: 800; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--on-dark-faint);
}
.home-hi h1 { margin: 1px 0 4px; font-size: clamp(26px, 6vw, 38px); line-height: 1.05; }
.home-sub { margin: 0; font-size: 14px; color: var(--on-dark-soft); }
/* Der gekaufte Titel steht direkt unter dem Namen – dort gehört er hin. */
/* Der gekaufte Titel – auf der Startseite und auf der Anmeldekachel.
   --------------------------------------------------------------------------
   Er trägt als Einziges IMMER dieselbe Schrift, in jeder Welt: Ein Titel ist
   kein Teil der Einrichtung, sondern etwas, das man sich verdient hat, und das
   soll überall gleich aussehen. Selbst gehostet heisst ausserdem: auf iPad,
   Windows und Mac wirklich dieselbe Schrift, was ein Stapel Systemschriften
   nie garantiert.

   Karla, schwer und gesperrt in Versalien – eine Gravur, kein Fliesstext. Eine
   Serifenschrift stand hier zuerst, aber sie muss zu ACHT Welten passen: neben
   der Schreibmaschinenschrift des Weltraums und der Schmalschrift der
   Werkstatt wirkte sie wie aus einem anderen Buch. Eine ruhige Grotesk streitet
   mit keiner von ihnen; die Sonderstellung trägt die Sperrung, nicht die Form.

   Das Symbol davor kommt aus dem Katalog und macht aus einem Wort ein
   Abzeichen. Karla hat keine Emoji-Zeichen, der Browser nimmt dafür von selbst
   die Emoji-Schrift – dafür braucht es hier keine Regel. */
.home-title {
  display: inline-flex; align-items: center; gap: 0.4em;
  margin: 0 0 4px;
  font-family: Karla, "Segoe UI", system-ui, sans-serif;
  font-size: 12.5px; font-weight: 800;
  text-transform: uppercase; letter-spacing: 0.13em;
  color: var(--star-on-bg);
}
/* Das Symbol sitzt in einer anderen Schrift als der Text und bringt deren
   Zeilenmasse mit: Auf der Grundlinie stünde es zu tief, und in Versalhöhe
   wirkt es zu klein. Eine eigene Zeilenhöhe nimmt ihm das Übermass, ein wenig
   mehr Grösse gleicht die Versalien aus, und mittig ausgerichtet stehen beide
   auf derselben Mitte statt auf derselben Grundlinie. */
.home-title .ic, .student-shop-title .ic {
  flex: none; font-size: 1.25em; line-height: 1;
}
.home-title .t { line-height: 1.2; }

.home-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.stat {
  background: var(--surface); border-radius: 16px; padding: 14px 8px;
  box-shadow: var(--shadow); text-align: center;
  display: flex; flex-direction: column; align-items: center; gap: 3px;
}
.stat-ic { font-size: 24px; line-height: 1; }
.stat-num {
  font-size: clamp(22px, 6vw, 30px); font-weight: 900; color: var(--accent-strong);
  line-height: 1; font-variant-numeric: tabular-nums;
}
.stat-num small { font-size: 0.5em; color: var(--ink-soft); font-weight: 800; }
.stat-lb { font-size: 12px; font-weight: 700; color: var(--ink-soft); }

.home-goal {
  background: var(--surface); color: var(--ink); border-radius: 16px;
  padding: 14px 16px; box-shadow: var(--shadow);
}
.home-goal-head { display: flex; align-items: center; gap: 9px; font-size: 16px; }
.home-goal-ic { font-size: 22px; line-height: 1; flex: none; }
.home-goal-sub { margin: 6px 0 0; font-size: 13px; color: var(--ink-soft); font-weight: 700; }
.home-goal-bar {
  margin-top: 10px; height: 10px; border-radius: 999px;
  background: var(--track-on-card); overflow: hidden;
}
.home-goal-bar span {
  display: block; height: 100%; border-radius: 999px;
  background: linear-gradient(90deg, var(--accent-strong), var(--accent));
  transition: width 0.4s ease;
}
.home-goal-done { outline: 2px solid var(--accent); }
.home-goal-cancelled .home-goal-sub { color: var(--ink-soft); }

/* Übungstage in Folge, im Kopf der Startseite: Zähler, sieben Tagespunkte
   (heute zuletzt), ein Satz dazu. Sitzt auf dem Seitenhintergrund. */
.home-days {
  display: flex; flex-direction: column; align-items: flex-start; gap: 6px;
  margin-top: 10px; font-size: 13px; color: var(--on-dark-soft); font-weight: 700;
}
.home-days-num { color: var(--on-dark-soft); font-size: 13px; }
.home-days-num b { color: var(--on-dark); font-size: 15px; font-variant-numeric: tabular-nums; }
.home-days-sep { opacity: 0.6; margin: 0 2px; }
.home-week { display: inline-flex; gap: 4px; }
.home-day {
  width: 24px; height: 24px; border-radius: 50%; display: grid; place-items: center;
  font-size: 10px; font-weight: 800; color: var(--on-dark-faint);
  background: rgba(255, 255, 255, 0.12); border: 1.5px solid transparent;
}
.home-day.done { background: var(--star-on-bg); color: #4a3600; }
.home-day.today { border-color: var(--on-dark); }
.home-day.future { opacity: 0.35; }
/* Heute und geübt: Häkchen statt Kürzel, etwas grösser – das ist der Punkt,
   um den es geht. */
.home-day.today.done { font-size: 13px; transform: scale(1.12); box-shadow: 0 0 0 3px rgba(245, 179, 1, 0.3); }

/* Tages-Aufgabe auf der Startseite: gleiche Karte wie das Wochenziel. */
.home-quest {
  background: var(--surface); color: var(--ink); border-radius: 16px;
  padding: 14px 16px; box-shadow: var(--shadow);
}
.home-quest.is-done { outline: 2px solid var(--good); }
.home-quest-bar span { background: linear-gradient(90deg, var(--star-ink), var(--star)); }
.home-goal-reward { color: var(--ink); }
/* Der versprochene Gutschein des Klassenziels: ein Abzeichen mit gestrichelter
   Kante wie ein Coupon, verlinkt in den Shop. */
.home-reward-voucher {
  display: flex; align-items: center; gap: 10px; margin-top: 10px; padding: 8px 12px;
  border-radius: 12px; border: 2px dashed var(--star); background: color-mix(in srgb, var(--star) 12%, var(--surface));
  color: var(--ink); text-decoration: none;
}
a.home-reward-voucher:hover { background: color-mix(in srgb, var(--star) 22%, var(--surface)); }
.home-reward-voucher-ic { font-size: 24px; line-height: 1; flex: none; }
.home-reward-voucher-text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.home-reward-voucher-lb { font-size: 10.5px; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase; color: var(--star-ink); }
.home-reward-voucher-text strong { font-size: 14px; font-weight: 900; }
.home-reward-voucher-go { flex: none; font-size: 18px; font-weight: 900; color: var(--star-ink); }

/* Der Gutschein aus dem Klassenziel: Code gross, damit er vorgelesen werden kann. */
.home-voucher {
  display: flex; align-items: center; gap: 12px; margin-top: 12px; padding: 10px 14px;
  border-radius: 14px; background: var(--good-bg); color: var(--ink); border: 2px dashed var(--good);
}
.home-voucher.is-redeemed { background: var(--surface-soft); border-color: var(--line); color: var(--ink-soft); }
.home-voucher-ic { font-size: 28px; line-height: 1; flex: none; }
.home-voucher-text { display: flex; flex-direction: column; gap: 2px; font-size: 13.5px; font-weight: 700; }
.home-voucher-code { font-size: 20px; letter-spacing: 0.18em; font-variant-numeric: tabular-nums; color: var(--good-ink); }
.home-class-goal .home-goal-bar span { background: linear-gradient(90deg, var(--good-ink), var(--good)); }

/* Tages-Aufgabe in der Pause (game.js → questHtml). */
.quest-card {
  background: var(--surface-soft); color: var(--ink); border-radius: 14px;
  padding: 10px 14px; margin: 12px 0 4px; text-align: left; font-size: 15px; font-weight: 700;
}
.quest-card.is-just-done {
  background: var(--good-bg); color: var(--good-ink); text-align: center; animation: pop 0.5s;
}
.quest-card.is-just-done { display: flex; flex-direction: column; align-items: center; gap: 4px; }
.quest-done-title { font-size: 16px; font-weight: 900; }
.quest-done-text { font-size: 14px; font-weight: 700; color: var(--ink); }
/* Der Bonus als goldenes Abzeichen auf eigener Zeile – eindeutig ein Plus. */
.quest-bonus-chip {
  display: inline-block; margin-top: 8px; padding: 5px 12px; border-radius: 999px;
  font-size: 13.5px; font-weight: 900; color: #8f6300; white-space: nowrap;
  background: linear-gradient(135deg, #fff8e6, #fdefc9); border: 2px solid var(--star);
}
.quest-bar { display: block; height: 7px; border-radius: 999px; background: var(--surface); overflow: hidden; margin-top: 8px; }
.quest-bar span { display: block; height: 100%; border-radius: 999px; background: linear-gradient(90deg, var(--star-ink), var(--star)); }
.quest-sub { display: block; font-size: 12.5px; color: var(--ink-soft); margin-top: 4px; }


.home-cta {
  display: flex; align-items: center; gap: 14px; text-decoration: none; color: var(--on-accent);
  padding: 16px 18px; border-radius: 18px;
  background: linear-gradient(120deg, var(--accent-strong), var(--accent) 72%, var(--accent-lift));
  box-shadow: var(--shadow); transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.home-cta:hover { transform: translateY(-2px); box-shadow: 0 14px 34px rgba(6,30,40,0.28); }
.home-cta-emoji { font-size: 34px; line-height: 1; flex: none; }
.home-cta-emoji svg { width: 34px; height: 34px; display: block; }
.home-cta-text { flex: 1 1 auto; display: flex; flex-direction: column; min-width: 0; }
.home-cta-text strong { font-size: 19px; font-weight: 900; }
.home-cta-text span { font-size: 13px; opacity: 0.9; }
.home-cta-go {
  flex: none; width: 40px; height: 40px; border-radius: 50%; font-size: 22px; font-weight: 900;
  background: rgba(255,255,255,0.22); display: grid; place-items: center;
}

.home-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
@media (max-width: 520px) { .home-cols { grid-template-columns: 1fr; } }
.home-card { background: var(--surface); color: var(--ink); border-radius: 16px; padding: 16px; box-shadow: var(--shadow); }
.home-card h3 { margin: 0 0 6px; font-size: 15px; display: flex; align-items: center; gap: 8px; }
.home-card h3 .home-card-ic { font-size: 19px; }
.home-medal {
  display: flex; align-items: center; gap: 10px; padding: 8px 0;
  border-top: 1px solid var(--surface-soft);
}
.home-medal:first-of-type { border-top: none; }
.home-medal .icon {
  font-size: 20px; width: 36px; height: 36px; flex: none;
  display: grid; place-items: center; border-radius: 11px; background: var(--surface-soft);
}
.home-medal span:last-child { font-weight: 600; font-size: 14px; }
/* Mini-Metallscheibe: kompakte Variante der Vitrinen-Medaille */
.medal-mini {
  width: 40px; height: 40px; flex: none; border-radius: 50%;
  display: grid; place-items: center; font-size: 21px;
  background: linear-gradient(145deg, var(--m1, #c6cfd9), var(--m2, #c6cfd9) 54%, var(--m3, #828e9b));
  border: 2px solid var(--mrim, #aab4bf);
  box-shadow: 0 0 0 1.5px rgba(255,255,255,0.5), 0 2px 5px rgba(6,30,40,0.2);
}
.medal-mini .medal-emoji { --sym: 20px; }
/* Noch nicht gewonnen: graue Scheibe mit blasser Silhouette und Schloss,
   wie die gesperrte Medaille in der Vitrine. Grau über die Metall-Variablen,
   nicht per Filter – sonst würde das Schloss mit ausgegraut. */
.medal-mini.is-locked {
  position: relative; --m1: #e6e9ec; --m2: #b4bcc4; --m3: #6f7a85; --mrim: #98a3ae;
  box-shadow: 0 0 0 1.5px rgba(255,255,255,0.35);
}
.medal-mini.is-locked .medal-emoji { opacity: 0.35; filter: grayscale(1); }
.medal-mini.is-locked::after {
  content: '🔒'; position: absolute; top: -6px; right: -6px; font-size: 14px; line-height: 1;
  filter: drop-shadow(0 1px 1px rgba(0,0,0,0.35));
}
.home-empty { color: var(--ink-soft); font-size: 14px; margin: 6px 0 0; }

/* Punktefeld (Hunderterfeld) mit Rechenschieber */
.punktefeld-wrap { width: min(640px, 100%); color: var(--on-dark); text-align: center; }

.pf-stage {
  position: relative;
  /* Über der Übung ist weniger Platz – dort setzt .pf-panel --pf-size kleiner.
     Der Wert kommt per Vererbung, darum steht die Vorgabe nur als Fallback:
     hier gesetzt, würde sie den des Panels überstimmen. */
  width: var(--pf-size, min(560px, 94vw));
  height: var(--pf-size, min(560px, 94vw));
  margin: 16px auto;
  touch-action: none; /* Ziehen soll nicht scrollen */
}
.pf-clip { position: absolute; inset: 0; overflow: hidden; border-radius: 16px; }

.pf-board {
  position: absolute; inset: 0;
  display: grid; grid-template-columns: repeat(10, 1fr); grid-template-rows: repeat(10, 1fr);
  background: var(--surface); box-shadow: var(--shadow);
}
.pf-board::before, .pf-board::after {
  content: ""; position: absolute; z-index: 1; pointer-events: none;
}
.pf-board::before { left: 50%; top: 0; bottom: 0; border-left: 2px dashed var(--ink-soft); }
.pf-board::after { top: 50%; left: 0; right: 0; border-top: 2px dashed var(--ink-soft); }

/* Wendeplättchen: flache, matte Kartonplättchen mit zwei Seiten (Wende-Flip) */
.pf-dot { display: grid; place-items: center; cursor: pointer; perspective: 260px; }
.pf-token {
  width: 62%; aspect-ratio: 1 / 1; position: relative;
  transform-style: preserve-3d; transition: transform 0.4s cubic-bezier(0.2, 0.75, 0.25, 1);
}
.pf-dot.flipped .pf-token { transform: rotateY(180deg); }
.pf-dot:active .pf-token { filter: brightness(0.96); }
.pf-face {
  position: absolute; inset: 0; border-radius: 50%;
  -webkit-backface-visibility: hidden; backface-visibility: hidden;
  /* matte Fläche, dünne Kartonkante, flacher Schlagschatten – kein Glanz */
  box-shadow:
    0 1px 2px rgba(6,30,40,0.28),
    inset 0 -2px 3px rgba(0,0,0,0.14),
    inset 0 0 0 1px rgba(0,0,0,0.07);
}
.pf-blue { background: #3f72bd; }
.pf-red  { transform: rotateY(180deg); background: #d1423c; }
@media (prefers-reduced-motion: reduce) { .pf-token { transition: none; } }

/* Rechenschieber: opake „L"-Fläche, oben links ein Viertel ausgeschnitten */
.pf-schieber {
  position: absolute; z-index: 2; pointer-events: none;
  background: rgba(12, 44, 58, 0.92);
  clip-path: polygon(50% 0, 100% 0, 100% 100%, 0 100%, 0 50%, 50% 50%);
}

.pf-handle {
  position: absolute; z-index: 3; transform: translate(-50%, -50%);
  width: 34px; height: 34px; border-radius: 50%;
  background: var(--star); color: #5a4000; font-weight: 900;
  display: grid; place-items: center; cursor: grab;
  box-shadow: 0 3px 10px rgba(0,0,0,0.3); touch-action: none;
  border: 3px solid var(--surface);
}
.pf-handle:active { cursor: grabbing; }

.pf-result { font-size: clamp(28px, 7vw, 46px); font-weight: 800; margin-top: 12px; }
.pf-controls { margin-top: 12px; }
.pf-controls button {
  font-size: 17px; padding: 10px 16px; border-radius: 12px; border: none;
  background: var(--surface); color: var(--ink); font-weight: 700; cursor: pointer;
}

/* Punktefeld über der laufenden Übung: kurz rechnen, schliessen, weitertippen.
   Liegt über dem Menü, aber unter Pause/Medaillen-Overlay (z-index 60). */
.pf-overlay {
  position: fixed; inset: 0; z-index: 55;
  display: flex; align-items: center; justify-content: center;
  padding: 12px; background: rgba(6, 30, 40, 0.62);
}
.pf-overlay.hidden { display: none; }
.pf-panel {
  /* Feld, Kopfzeile und Knopf müssen zusammen aufs Tablet passen, quer wie hoch.
     Das Feld bleibt dabei kleiner als der Kasten: Ringsum Luft, damit es nicht
     an den Kanten klebt und die Finger beim Wenden Platz haben. */
  --pf-size: min(460px, 78vw, 50vh);
  width: min(560px, 96vw); max-height: 96vh; overflow: auto;
  padding: 14px clamp(16px, 4vw, 28px) 24px; border-radius: 20px;
  background: var(--bg-deep, #0c2c3a); box-shadow: 0 18px 50px rgba(0, 0, 0, 0.45);
  color: var(--on-dark); text-align: center;
}
.pf-panel-head {
  display: flex; align-items: center; justify-content: space-between;
  font-weight: 800; font-size: 18px; padding: 2px 0 0 6px;
}
.pf-panel .pf-stage { margin: clamp(16px, 4vw, 26px) auto 0; }
.pf-close {
  width: 38px; height: 38px; border-radius: 50%; border: none; cursor: pointer;
  background: var(--menu-hover, rgba(255,255,255,0.14)); color: var(--on-dark);
  font-size: 17px; font-weight: 800; line-height: 1;
}
.pf-panel .pf-result { margin-top: 14px; font-size: clamp(24px, 5vw, 38px); }
.pf-done { margin-top: 16px; }
/* Kein Scrollen dahinter und kein Weichzeichner in der Shell, solange das Feld
   offen ist: siehe body.modal-open weiter unten – sonst würde die Shell zum
   Bezugsrahmen des Overlays und ihre runden Ecken sähen eckig aus. */

/* Medaillen-Vitrine: Gesamtfortschritt + Kachelraster pro Reihe */
.medal-vitrine { width: min(820px, 100%); }

.medal-summary { text-align: center; margin-bottom: 22px; }
.medal-summary-num { font-size: clamp(40px, 10vw, 60px); font-weight: 900; color: var(--accent-strong); line-height: 1; }
.medal-summary-num span { font-size: 0.5em; color: var(--ink-soft); font-weight: 800; }
.medal-summary-bar {
  height: 15px; background: var(--surface-soft); border-radius: 999px; overflow: hidden;
  margin: 12px auto 8px; max-width: 420px;
  box-shadow: inset 0 2px 4px rgba(6, 30, 40, 0.18);
}
.medal-summary-bar > div {
  height: 100%; border-radius: 999px; transition: width 0.6s ease; min-width: 15px;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0) 48%),
    linear-gradient(90deg, #c67f3e, #f2c336 60%, #ffe08a);
  box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.65), 0 0 6px rgba(217, 168, 28, 0.5);
}
.medal-summary-label { color: var(--ink-soft); font-weight: 700; font-size: 14px; letter-spacing: 0.03em; text-transform: uppercase; }

.medal-group { margin-bottom: 18px; }
.medal-group-head {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin-bottom: 10px; padding-bottom: 6px; border-bottom: 2px solid var(--cat, var(--surface-soft));
}
.medal-group-head h3 { margin: 0; font-size: 16px; color: var(--cat, var(--ink)); }
.medal-chip {
  font-weight: 800; font-size: 13px; color: var(--cat, var(--ink-soft));
  background: transparent; padding: 2px 10px; border-radius: 999px;
  border: 2px solid var(--cat, var(--surface-soft));
  font-variant-numeric: tabular-nums;
}
.medal-chip.done { background: var(--good); border-color: var(--good); color: #fff; }

/* --- Reihen-Kategorien: Signaturfarbe je Kategorie (Titel, Chip, Band) --- */
.cat-a    { --cat:#2f80ed; --catd:#1b5fc0; }  /* 1/2/5/10er Reihe */
.cat-b    { --cat:#27ae60; --catd:#1c8449; }  /* 3/6/9er Reihe */
.cat-c    { --cat:#8e44ad; --catd:#6c3184; }  /* 4/7/8er Reihe */
.cat-d    { --cat:#e67e22; --catd:#b55f13; }  /* 11/12/15er Reihe */
.cat-e    { --cat:#eb5b8a; --catd:#c23c69; }  /* 24/25er Reihe */
.cat-allg { --cat:var(--accent-strong); --catd:#0a6b85; }
.cat-star { --cat:#e0a91b; --catd:#b6860d; }

.medal-tiles {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fill, minmax(128px, 1fr));
}
.medal-tile {
  background: var(--surface-soft); border-radius: 16px; padding: 14px 8px 12px;
  text-align: center; display: flex; flex-direction: column; align-items: center; gap: 3px;
  transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.medal-tile.owned { background: var(--card-lift); box-shadow: var(--shadow); }
.medal-tile.owned:hover { transform: translateY(-4px); box-shadow: 0 16px 34px rgba(6, 30, 40, 0.24); }
.medal-tile-name { font-weight: 800; font-size: 13px; margin-top: 2px; }
.medal-tile-desc { font-size: 11px; color: var(--ink-soft); line-height: 1.25; }

/* --- Metall-Ränge: Verlaufsfarben je Stufe ---
   Silber und Platin unterschieden sich früher nur in der Helligkeit und waren
   auf 64px nicht auseinanderzuhalten. Jetzt trennt sie beides: Silber ist ein
   dunkleres, neutrales Grau, Platin ein sehr helles Weissmetall mit kühlem
   Violettstich (und behält seinen Schimmer weiter unten). Der Violettton grenzt
   es zugleich vom cyanfarbenen Diamant ab. */
.tier-bronze  { --m1:#f4cda0; --m2:#c67f3e; --m3:#7a4a1e; --mrim:#a5622a; }
.tier-silber  { --m1:#f4f7fa; --m2:#a8b4c0; --m3:#5c6875; --mrim:#8c98a4; }
.tier-gold    { --m1:#fff3bc; --m2:#f2c336; --m3:#a9791a; --mrim:#d9a81c; }
.tier-platin  { --m1:#ffffff; --m2:#e3e0f4; --m3:#8b83b0; --mrim:#cbc4e4; }
.tier-diamant { --m1:#ffffff; --m2:#a7e6ff; --m3:#3fb2dd; --mrim:#7fd8f5; }

/* --- Medaille: Band + glänzende Metall-Scheibe --- */
.medal-badge { position: relative; width: 64px; height: 86px; margin: 0 auto 2px; }
.medal-ribbon {
  position: absolute; top: 40px; left: 50%; transform: translateX(-50%);
  width: 38px; height: 42px; z-index: 0;
  background: linear-gradient(180deg, var(--cat, var(--m2)), var(--catd, var(--m3)));
  clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 72%, 0 100%);
  box-shadow: inset 0 0 6px rgba(0,0,0,0.25);
}
/* animierbarer Winkel für den rotierenden Ring-Glanz */
@property --shine-angle {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}
.medal-disc {
  position: absolute; top: 0; left: 50%; transform: translateX(-50%);
  width: 64px; height: 64px; border-radius: 50%; z-index: 1; box-sizing: border-box;
  display: flex; align-items: center; justify-content: center;
  border: 3px solid transparent;
  /* flache Metall-Fläche (padding-box) + glänzender Ring (border-box, conic) */
  background:
    linear-gradient(145deg, var(--m1), var(--m2) 54%, var(--m3)) padding-box,
    conic-gradient(from var(--shine-angle, 0deg),
      var(--mrim), #fff, var(--m1), var(--mrim),
      var(--m3), var(--mrim), #fff, var(--mrim)) border-box;
  box-shadow:
    0 0 0 2px rgba(255,255,255,0.5),
    0 4px 9px rgba(6,30,40,0.22);
  transition: transform 0.12s ease;
}
/* flacher Diagonal-Glanz auf der Fläche (keine kugelige Wölbung) */
.medal-disc::before {
  content: ''; position: absolute; inset: 0; border-radius: 50%; pointer-events: none;
  background: linear-gradient(158deg, rgba(255,255,255,0.5), rgba(255,255,255,0) 42%);
}
/* Ring dreht sich wie poliertes Metall – nur bei STAR-Medaillen (Klasse is-shiny).
   Läuft bewusst auch bei „weniger Bewegung“ (wie die Octopus-Clips): Windows
   meldet `prefers-reduced-motion: reduce`, sobald „Animationseffekte“ aus sind,
   und der Glanz gehört zur Belohnung. */
.medal-disc.is-shiny { animation: medal-shine 7s linear infinite; }
@keyframes medal-shine { to { --shine-angle: 360deg; } }
/* Symbol wie in Metall geprägt: entfärbt, nimmt die Metallfarbe an (luminosity),
   Licht oben / Schatten unten erzeugt die Reliefwirkung. */
.medal-emoji {
  position: relative; z-index: 1; --sym: 30px; font-size: var(--sym); line-height: 1;
  mix-blend-mode: luminosity;
  filter: grayscale(1) contrast(1.1) brightness(1.12)
    drop-shadow(0 -1px 0.4px rgba(255,255,255,0.45))
    drop-shadow(0 1px 0.6px rgba(0,0,0,0.45));
}
/* Buchstabe oder Zahl statt Emoji (z. B. die Reihe einer Reihen-Medaille):
   gleiche Prägung, aber fett gesetzt. Der mittelgraue Ton nimmt durch den
   luminosity-Blend die Metallfarbe an und bleibt auf Bronze wie auf Platin
   lesbar. Buchstaben füllen ihr Textfeld stärker als Emojis → etwas kleiner,
   zwei Zeichen nochmals. */
.medal-emoji.is-text {
  font-size: calc(var(--sym) * 0.84); font-weight: 900;
  font-variant-numeric: tabular-nums; letter-spacing: -0.02em;
  color: #5a5a5a; -webkit-text-fill-color: #5a5a5a;
}
.medal-emoji.is-text.is-two { font-size: calc(var(--sym) * 0.62); }

/* Höhere Ränge glänzen zusätzlich (Ring bleibt der conic-Glanz) */
.tier-platin.owned .medal-disc { box-shadow:
    0 0 0 2px rgba(255,255,255,0.5), 0 4px 9px rgba(6,30,40,0.22),
    0 0 12px rgba(180,205,230,0.65); }
.tier-diamant.owned .medal-disc { box-shadow:
    0 0 0 2px rgba(255,255,255,0.5), 0 4px 9px rgba(6,30,40,0.22),
    0 0 16px rgba(120,220,255,0.75); }

/* Gesperrt: Silhouette statt buntem Symbol, kleines Schloss oben rechts */
.medal-lock {
  position: absolute; top: 0; right: 4px; z-index: 3; font-size: 15px;
  filter: drop-shadow(0 1px 1px rgba(0,0,0,0.35));
}
.medal-tile.locked .medal-disc { filter: grayscale(1) brightness(0.92); }
.medal-tile.locked .medal-ribbon { filter: grayscale(1); opacity: 0.5; }
.medal-tile.locked .medal-emoji { opacity: 0.32; }
.medal-tile.locked .medal-tile-name,
.medal-tile.locked .medal-tile-desc { opacity: 0.7; }
/* Der Stand an der gesperrten Kachel: eine leise Zahl oben links in der
   Ecke, kein Balken – nimmt keine Höhe, die Kacheln bleiben gleich gross. */
.medal-tile-count {
  position: absolute; top: 6px; left: 7px; z-index: 3;
  font-size: 9px; font-weight: 700; color: var(--ink-soft); opacity: 0.65;
  font-variant-numeric: tabular-nums; line-height: 1; letter-spacing: -0.01em;
}
/* „Fast erreicht": kleines Band oben an der Kachel, Sternfarbe. */
.medal-tile { position: relative; }
.medal-tile-near {
  position: absolute; top: -8px; left: 50%; transform: translateX(-50%); z-index: 4;
  font-size: 9.5px; font-weight: 900; letter-spacing: 0.06em; text-transform: uppercase; white-space: nowrap;
  background: var(--star); color: var(--star-ink); border-radius: 999px; padding: 2px 8px;
  box-shadow: 0 1px 3px rgba(6,30,40,0.2);
}
.medal-tile.is-near { box-shadow: 0 0 0 2px var(--star); }

/* --- Spiel: grössere, klarere Zifferntastatur --- */
.game-keypad {
  width: min(380px, 94vw);
  margin: clamp(16px, 4vw, 26px) auto 0;
  gap: 10px;
}
.game-keypad button {
  min-height: clamp(56px, 12vw, 76px);
  font-size: clamp(24px, 6vw, 32px);
  border-radius: 16px;
  background: var(--key-bg);
  box-shadow: 0 3px 0 rgba(6,30,40,0.15);
}
.game-keypad button:active { transform: translateY(2px); box-shadow: 0 1px 0 rgba(6,30,40,0.15); }
.game-keypad .key-ok { background: var(--good); color: #fff; }
.game-keypad .key-fn { background: var(--surface-soft); }

/* „Weiss ich nicht" unter der Tastatur – nur im Einstufungstest. Bewusst
   zurückhaltend: ein Ausweg, keine Einladung. */
.skip-row { width: min(380px, 94vw); margin: 12px auto 0; text-align: center; }
.skip-row.hidden { display: none; }
.btn-skip {
  width: auto; margin: 0; padding: 10px 22px;
  font-size: clamp(15px, 3.6vw, 18px); font-weight: 700;
  border-radius: 14px;
  background: var(--surface-soft); color: var(--ink-soft);
  box-shadow: 0 2px 0 rgba(6,30,40,0.12);
}
.btn-skip:hover { background: var(--press); color: var(--ink); }
.btn-skip:active { transform: translateY(1px); box-shadow: 0 1px 0 rgba(6,30,40,0.12); }

/* --- 3-2-1-Countdown beim Spielstart --- */
.countdown {
  position: fixed; inset: 0; z-index: 60;
  display: grid; place-items: center;
  background: radial-gradient(120% 120% at 50% 30%,
    color-mix(in srgb, var(--bg-top) 92%, transparent),
    color-mix(in srgb, var(--bg-deep) 96%, transparent));
}
.countdown-num {
  font-size: clamp(90px, 34vw, 220px); font-weight: 900; color: var(--on-dark);
  text-shadow: 0 6px 30px rgba(0,0,0,0.35);
}
.countdown-num.go { color: var(--star); font-size: clamp(60px, 20vw, 140px); }
.countdown-num.tick { animation: cd-pop 0.85s ease; }
@keyframes cd-pop {
  0% { transform: scale(0.2); opacity: 0; }
  30% { transform: scale(1.1); opacity: 1; }
  100% { transform: scale(0.85); opacity: 0.15; }
}

/* ===================================================================== */
/* Verwaltungsbereich (Lehrer & Admin)                                   */
/* ===================================================================== */

/* Titel oben, darunter das Menü über die volle Breite – genau so breit wie die
   Panels darunter und wie im Schüler-Menü. Vorher standen Titel und Menü
   nebeneinander: Das Menü war dann nur so breit wie seine Einträge und endete
   irgendwo mitten im Bild, während alles darunter bis an den Rand lief. */
.mgmt-bar {
  width: min(1080px, 100%); margin: 0 auto clamp(16px, 3vw, 26px);
  display: flex; flex-direction: column; gap: 12px;
}
.mgmt-brand { display: flex; align-items: center; gap: 10px; color: var(--on-dark); }
.mgmt-brand .octo { font-size: 34px; line-height: 1; }
.mgmt-title { font-size: clamp(18px, 3vw, 24px); font-weight: 900; letter-spacing: .01em; }
.mgmt-menu { width: 100%; margin: 0; flex-wrap: wrap; justify-content: center; }
/* Verwaltungs-Menü: Buttons nebeneinander, Icon + Text in einer Zeile. */
.mgmt-menu .topmenu-item { flex: 0 0 auto; flex-direction: row; align-items: center; gap: 7px; padding: 8px 14px; }
.mgmt-menu .topmenu-item .ic svg { width: 20px; height: 20px; }
.mgmt-menu .topmenu-item .lb { font-size: 13.5px; }
/* „Abmelden" ist ein <form> mit <button> darin, alle anderen sind <a>. Der
   Knopf brachte aus dem Schüler-Menü seine eigene Anordnung mit (Symbol oben,
   Wort darunter) und stand deshalb als Einziger zweizeilig in der Reihe. */
.mgmt-menu .logout button { flex-direction: row; align-items: center; gap: 7px; }
.mgmt-menu .logout .lb { font-size: 13.5px; }

/* Auf schmalen Geräten rücken die Einträge zusammen, statt in immer mehr
   Zeilen zu zerfallen. */
@media (max-width: 560px) {
  .mgmt-menu { padding: 6px; gap: 4px; }
  .mgmt-menu .topmenu-item { padding: 7px 10px; gap: 6px; }
  .mgmt-menu .topmenu-item .lb, .mgmt-menu .logout .lb { font-size: 12.5px; }
  .mgmt-brand .octo { font-size: 28px; }
}

.manage { width: min(1080px, 100%); margin: 0 auto; }
.mgmt-panel {
  background: var(--surface); color: var(--ink); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: clamp(18px, 3vw, 30px); margin-bottom: 18px;
}
.mgmt-panel h2 { margin: 0 0 4px; font-size: 21px; }
.mgmt-panel h2:not(:first-child) { margin-top: 26px; }
.mgmt-panel .lead { color: var(--ink-soft); margin: 0 0 16px; }
.mgmt-head { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; justify-content: space-between; margin-bottom: 14px; }
.mgmt-back { color: var(--accent-strong); text-decoration: none; font-weight: 700; }

/* Tabellen */
.mgmt-table { width: 100%; border-collapse: collapse; font-variant-numeric: tabular-nums; }
.mgmt-table th {
  text-align: left; font-size: 12px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-soft); padding: 8px 10px; border-bottom: 2px solid var(--surface-soft);
}
.mgmt-table td { padding: 11px 10px; border-bottom: 1px solid var(--surface-soft); }
.mgmt-table tbody tr:hover { background: color-mix(in srgb, var(--accent) 7%, transparent); }
.mgmt-table .num { text-align: right; }
.mgmt-table a { color: var(--accent-strong); text-decoration: none; font-weight: 700; }
.mgmt-table .row-actions { display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
.mgmt-scroll { overflow-x: auto; }

/* Klassen-Kennzahlen über der Liste */
.class-stats { margin: 0 0 18px; }
.class-stats .stat-tile .v small { font-size: 0.5em; color: var(--ink-soft); font-weight: 800; }

/* Klassenliste: Schülerzelle mit Initialen-Avatar */
.roster td { vertical-align: middle; }
.student-cell { display: inline-flex; align-items: center; gap: 10px; }
.student-cell:hover .student-name { text-decoration: underline; }
.avatar {
  flex: none; width: 34px; height: 34px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 13px; font-weight: 800; letter-spacing: .02em; color: var(--on-accent);
  background: linear-gradient(135deg, var(--accent-strong), var(--accent));
}
/* nowrap, damit das Verknüpfungs-Symbol 🔗 neben dem Namen bleibt und nicht in
   die nächste Zeile rutscht. Die Tabelle scrollt ohnehin waagrecht (.mgmt-scroll). */
.student-name { font-weight: 700; white-space: nowrap; }
.mgmt-table td.muted, .muted { color: var(--ink-soft); }

/* Quote als farbige Pille */
.quote-pill {
  display: inline-block; min-width: 46px; text-align: center;
  font-weight: 800; font-size: 13px; padding: 3px 9px; border-radius: 999px;
  background: var(--surface-soft); color: var(--ink-soft);
}
.quote-pill.good { background: #e6f8ee; color: #1f9d5b; }
.quote-pill.warn { background: #fdf3dc; color: #b47b00; }
.quote-pill.bad { background: #fde7e6; color: var(--bad); }

/* Wochenziel-Spalte in der Klassenliste */
.mgmt-table th.goal-col, .mgmt-table td.goal-col { text-align: left; min-width: 120px; }
.goal-badge {
  display: inline-flex; align-items: center; gap: 4px; font-size: 12px; font-weight: 800;
  padding: 3px 10px; border-radius: 999px; background: #e6f8ee; color: #1f9d5b;
}
.goal-mini { display: flex; flex-direction: column; gap: 4px; min-width: 92px; }
.goal-mini-bar { height: 8px; border-radius: 999px; background: var(--surface-soft); overflow: hidden; }
.goal-mini-bar span {
  display: block; height: 100%; border-radius: 999px;
  background: linear-gradient(90deg, var(--accent-strong), var(--accent));
}
.goal-mini-lab { font-size: 12px; font-weight: 700; color: var(--ink-soft); font-variant-numeric: tabular-nums; }

/* Schülerdetail: Kopf mit grossem Avatar */
.student-head { display: flex; align-items: center; gap: 14px; }
.avatar-lg { width: 52px; height: 52px; font-size: 19px; }

/* Wochenziel-Streifen im Schülerdetail */
.goal-strip {
  margin-top: 16px; padding: 14px 16px; border-radius: 14px;
  background: var(--surface-soft); border: 1px solid transparent;
}
.goal-strip.done { background: #e6f8ee; border-color: color-mix(in srgb, var(--good) 30%, transparent); }
.goal-strip-head { display: flex; align-items: center; gap: 12px; }
.goal-strip-ic { font-size: 24px; line-height: 1; flex: none; }
.goal-strip-head strong { display: block; font-size: 15px; }
.goal-strip-sub { display: block; font-size: 13px; color: var(--ink-soft); font-weight: 600; margin-top: 1px; }
.goal-strip-val { margin-left: auto; font-size: 17px; font-weight: 900; font-variant-numeric: tabular-nums; white-space: nowrap; }
.goal-strip-bar { margin-top: 10px; height: 10px; border-radius: 999px; background: rgba(6,30,40,0.10); overflow: hidden; }
.goal-strip-bar span {
  display: block; height: 100%; border-radius: 999px;
  background: linear-gradient(90deg, var(--accent-strong), var(--accent)); transition: width .4s ease;
}
.goal-strip.done .goal-strip-bar span { background: linear-gradient(90deg, var(--good), #4fd08a); }

/* Buttons in der Verwaltung */
/* Dasselbe im Kleinen: Auch hier stehen <a> und <button> nebeneinander
   (Zeilen-Aktionen in den Verwaltungstabellen). Siehe .btn. */
.btn-sm {
  font-family: inherit;
  font-size: 13px; font-weight: 700; line-height: 1.2; padding: 7px 12px;
  border: none; border-radius: 10px;
  background: var(--surface-soft); color: var(--ink); cursor: pointer; text-decoration: none;
  display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box;
}
.btn-sm:hover { background: var(--press); }
.btn-sm.primary { background: var(--accent); color: #fff; }
.btn-sm.primary:hover { background: var(--accent-strong); }
.btn-sm.danger { background: #fde7e6; color: var(--bad); }
.btn-sm.danger:hover { background: #f9d3d1; }

/* Trend-Pfeil neben der Quote (letzte Antworten vs. Gesamtschnitt). */
.quote-cell { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
/* Der Trendpfeil belegt seine Spalte auch dann, wenn es keinen Trend gibt –
   sonst stünden die Prozentwerte je nach Zeile unterschiedlich weit rechts. */
.trend { flex: none; width: 13px; text-align: center; font-weight: 900; font-size: 13px; cursor: help; }
.trend:empty { cursor: default; }
.trend-up { color: var(--good); }
.trend-down { color: var(--bad); }
.trend-flat { color: var(--ink-soft); }

/* „Neu einstufen" steht direkt unter den Stufen – es ist die Alternative dazu,
   die Stufe von Hand zu setzen, und keine losgelöste „weitere Aktion". */
.restart-placement {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 10px;
}
.restart-placement .hint { flex: 1 1 260px; font-size: 12.5px; color: var(--ink-soft); }

/* Stufe von Hand setzen: je Stufe die enthaltenen Reihen zeigen. */
.level-set { display: grid; gap: 8px; }
.level-set-btn {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 10px; text-align: left;
  padding: 9px 14px; border-radius: 12px; border: 1px solid transparent;
  background: var(--surface-soft); color: var(--ink); cursor: pointer; font: inherit; width: 100%;
  transition: background .15s ease, border-color .15s ease;
}
.level-set-btn:hover { background: var(--press); }
/* Zwei Stärken: `included` sind die tieferen Stufen, die durch die gesetzte
   mit freigeschaltet sind – ebenfalls im Einsatz, aber nicht das, was hier
   eingestellt ist. `primary` ist die gesetzte Stufe selbst. */
.level-set-btn.included {
  border-color: color-mix(in srgb, var(--accent) 28%, transparent);
  background: color-mix(in srgb, var(--accent) 6%, var(--surface-soft));
}
.level-set-btn.primary {
  border-color: var(--accent); border-width: 2px; padding: 8px 13px;
  background: color-mix(in srgb, var(--accent) 14%, var(--surface-soft));
}
.level-set-btn .lvl { font-weight: 800; white-space: nowrap; }
.level-set-btn.primary .lvl { color: var(--accent); }
.level-set-btn .rows { color: var(--ink-soft); font-size: 13px; font-weight: 600; }
/* Hervorhebung im Hinweistext, z. B. „zurzeit ein" – hebt sich vom
   gedämpften Rest ab, ohne dass die Zeile lauter wirkt. */
.level-set-btn .rows b { color: var(--ink); font-weight: 800; }
/* Rechtsbündige Markierung: welche Stufe gesetzt ist und welche nur mitläuft. */
.level-set-btn .lvl-tag {
  margin-left: auto; font-size: 11.5px; font-weight: 700; white-space: nowrap;
  color: var(--ink-soft); text-transform: uppercase; letter-spacing: .3px;
}
.level-set-btn .lvl-tag.now {
  color: #fff; background: var(--accent);
  padding: 2px 9px; border-radius: 999px; letter-spacing: 0;
}
/* Während im Hintergrund gespeichert wird: kurz abblenden und sperren, damit
   nicht zwei Klicks um dieselbe Auswahl konkurrieren. */
.level-set.is-saving { opacity: .55; pointer-events: none; }

/* Formulare */
.mgmt-form { display: grid; gap: 14px; max-width: 560px; }
.mgmt-form.wide { max-width: 100%; }
.mgmt-grid2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 14px; }
.field { display: flex; flex-direction: column; gap: 5px; }
.field label { font-weight: 700; color: var(--ink-soft); font-size: 14px; }
/* Alle Eingabefelder ausser den Ankreuzfeldern – aufgezählt wurden früher die
   Typen, und `email` und `url` fehlten in der Liste: Sie standen als schmale
   weisse Kästchen zwischen den anderen. Umgekehrt gedacht kann das nicht mehr
   passieren, denn ein neuer Typ ist von selbst dabei. */
.field input:not([type="checkbox"]):not([type="radio"]):not([type="color"]):not([type="file"]),
.field select {
  width: 100%; padding: 11px 13px; font-size: 16px; border: 2px solid var(--surface-soft);
  border-radius: 11px; background: var(--surface-soft); color: var(--ink); font-family: inherit;
}
.field input:focus, .field select:focus { outline: none; border-color: var(--accent); }
.field .hint { font-size: 12.5px; color: var(--ink-soft); }
.field.check { flex-direction: row; align-items: center; gap: 9px; }
.field.check input { width: 20px; height: 20px; accent-color: var(--accent); }
.field.check label { font-weight: 600; color: var(--ink); }
.form-actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 6px; align-items: center; }
/* Buttons in einer Aktionszeile richten sich an derselben Höhe aus (kein Top-Margin). */
.form-actions .btn { margin-top: 0; }
/* Sekundär-Button („Abbrechen"): gleiche Grösse wie .btn, aber neutrale Farbe. */
.btn.ghost { background: var(--surface-soft); color: var(--ink); }
.btn.ghost:hover { background: var(--press); }

/* Heikle Aktionen in zwei Stärken: `danger-soft` ist nur umrandet (wirkt auf
   das eigene Gerät), `danger` ist voll gefüllt (wirkt auf die ganze Klasse). */
.btn.danger-soft {
  background: transparent; color: var(--bad);
  box-shadow: inset 0 0 0 2px var(--bad);
}
.btn.danger-soft:hover { background: #fde7e6; }
.btn.danger { background: var(--bad); color: #fff; }
.btn.danger:hover { background: #e04440; }

/* Badges (Lizenz / Status) */
.badge {
  display: inline-flex; align-items: center; gap: 5px; font-size: 12px; font-weight: 800;
  padding: 3px 10px; border-radius: 999px; letter-spacing: .02em;
}
.badge.ok { background: #e6f8ee; color: #1f9d5b; }
.badge.warn { background: #fdf3dc; color: #b47b00; }
.badge.bad { background: #fde7e6; color: var(--bad); }
.badge.info { background: var(--surface-soft); color: var(--ink-soft); }
.badge.role-teacher { background: #e7eefc; color: #2f6ad0; }
.badge.role-student { background: var(--surface-soft); color: var(--ink-soft); }

/* Hinweis-Box (z. B. „Klasse mit Homework verbunden") */
.notice { border-radius: 12px; padding: 12px 14px; font-size: 14px; line-height: 1.5; }
.notice.info { background: #eaf4fb; color: #245b78; border: 1px solid #cfe3ef; }
.notice strong { color: inherit; }

/* Aktivitäts-Kacheln (Schülerdetail) */
.stat-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin: 4px 0 8px; }
.stat-tile { background: var(--surface-soft); border-radius: 14px; padding: 14px 16px; }
.stat-tile .k { font-size: 12px; font-weight: 800; letter-spacing: .05em; text-transform: uppercase; color: var(--ink-soft); }
.stat-tile .v { font-size: 26px; font-weight: 900; color: var(--ink); font-variant-numeric: tabular-nums; }
.stat-tile .sub { font-size: 12.5px; color: var(--ink-soft); }

/* Fortschrittsbalken pro Reihe */
.rowbar { display: grid; gap: 8px; }
.rowbar .r { display: grid; gap: 4px; }
/* Umbruch statt Quetschen: wird es eng (schmales Fenster, langer Hinweis wie
   „aktuell nicht freigeschaltet"), rutschen die Kennzahlen auf eine eigene
   Zeile, statt dass Name und Werte ineinanderlaufen. */
.rowbar .r .lab { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 2px 12px; font-weight: 600; font-size: 14px; }
.rowbar .r .lab .meta { color: var(--ink-soft); font-weight: 500; display: flex; flex-wrap: wrap; gap: 3px 14px; }
/* Feste Spaltenbreiten, damit die Werte über alle Reihen hinweg untereinander
   stehen. Ohne sie richtet sich jede Zeile nach ihrer eigenen Zahlenlänge, und
   „61 / 64 (95%)" schiebt die Zeit gegenüber „7 / 9 (78%)" nach rechts.
   Bemessen nach dem längsten realistischen Wert (100%, 999 / 999 (100%), 10,0 s). */
.rowbar .r .lab .meta .m {
  display: inline-flex; align-items: center; gap: 4px;
  white-space: nowrap; font-variant-numeric: tabular-nums;
  flex: none;
}
.rowbar .r .lab .meta .m-mastery { width: 62px; }
.rowbar .r .lab .meta .m-solid { width: 84px; }
.rowbar .r .lab .meta .m-correct { width: 132px; }
.rowbar .r .lab .meta .m-time { width: 68px; }
.rowbar .r .lab .meta .m svg { width: 14px; height: 14px; flex: none; opacity: .75; }
.rowbar .track { height: 10px; background: var(--surface-soft); border-radius: 999px; overflow: hidden; }
.rowbar .fill { height: 100%; background: var(--good); border-radius: 999px; }
/* Reihen, die für das Kind nicht freigeschaltet sind: blass, damit der Blick
   an den Reihen hängen bleibt, die gerade wirklich geübt werden. Die Zahlen
   bleiben lesbar – sie sind nicht falsch, nur (noch) ohne Bedeutung. */
.rowbar .r.locked { opacity: .48; }
.rowbar .r.locked .lab { font-weight: 500; }
.rowbar .rowlock {
  margin-left: 8px; padding: 1px 7px; border-radius: 999px;
  background: var(--surface-soft); color: var(--ink-soft);
  font-size: 11.5px; font-weight: 600; white-space: nowrap;
  vertical-align: 1px;
}

/* Reiter im Schülerdetail: „Fortschritt pro Reihe" / „pro Rechnung". Der
   aktive Reiter sieht aus wie die Panel-Überschrift, der andere steht blass
   daneben – so bleibt die Seite ruhig und die zweite Sicht ist trotzdem
   einen Klick entfernt. */
.tabbar { display: flex; flex-wrap: wrap; gap: 4px 18px; margin: 0 0 12px; border-bottom: 1px solid var(--line-soft); }
.tabbar .tab {
  appearance: none; background: none; border: 0; padding: 4px 0 8px; margin: 0 0 -1px; cursor: pointer;
  font: inherit; font-size: 21px; font-weight: 800; color: var(--ink-soft);
  border-bottom: 3px solid transparent;
}
.tabbar .tab:hover { color: var(--ink); }
.tabbar .tab.active { color: var(--ink); border-bottom-color: var(--accent); cursor: default; }
.tabbar .tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 6px; }

/* Fortschritt pro Rechnung: jede Aufgabe ein Feld. Die Farbe trägt die
   Aussage, die Zahlen darunter sind bewusst klein – wer sie will, findet sie
   (und den vollen Text im Tooltip), wer nur den Überblick will, sieht die
   Farbfläche. Breiter als das Panel? Dann scrollt die Tabelle, nicht die Seite. */
.factgrid-wrap { overflow-x: auto; margin: 0 -4px; padding: 0 4px; }
.factgrid { border-collapse: separate; border-spacing: 3px; font-variant-numeric: tabular-nums; min-width: 100%; }
.factgrid th { font-size: 12px; font-weight: 700; color: var(--ink-soft); padding: 2px 4px; white-space: nowrap; }
.factgrid thead th { text-align: center; padding-bottom: 4px; }
.factgrid th.corner { text-align: left; font-weight: 600; }
.factgrid tbody th { text-align: right; padding-right: 8px; font-size: 13px; color: var(--ink); }
.factgrid td.c {
  min-width: 60px; padding: 9px 4px 7px; border-radius: 8px; text-align: center; line-height: 1.15;
  background: var(--surface-soft); color: var(--ink-soft);
}
/* Die Rechnung ist das, was man sucht – deutlich grösser als die Kennzahlen
   darunter und mit Luft dazwischen, damit beides nicht zu einem Block verschwimmt. */
.factgrid td.c .t { display: block; font-size: 15px; font-weight: 800; color: var(--ink); white-space: nowrap; letter-spacing: .01em; }
.factgrid td.c .d2 { display: block; font-size: 10px; font-weight: 500; opacity: .8; white-space: nowrap; margin-top: 7px; }
.factgrid td.c .d2 .ok { font-style: normal; font-weight: 800; opacity: 1; }
.factgrid td.c.good { background: #e6f8ee; color: #1f9d5b; }
.factgrid td.c.mid { background: #fdf3dc; color: #b47b00; }
.factgrid td.c.bad { background: #fde7e6; color: #c9423e; }
.factgrid td.c.new { background: var(--surface-soft); color: var(--ink-soft); }
.factgrid td.c.new .t { color: var(--ink-soft); font-weight: 600; }
.factgrid td.c.none { background: none; }
/* Nicht freigeschaltete Reihen: blass wie im Reihenbalken. */
.factgrid tr.locked { opacity: .45; }
.factgrid tr.locked th { font-weight: 500; }
.factgrid-legend { display: flex; flex-wrap: wrap; gap: 4px 16px; margin-top: 10px; font-size: 12px; color: var(--ink-soft); }
.factgrid-legend span { display: inline-flex; align-items: center; gap: 6px; }
.factgrid-legend i { width: 12px; height: 12px; border-radius: 4px; display: inline-block; }
.factgrid-legend i.good { background: #e6f8ee; border: 1px solid #1f9d5b; }
.factgrid-legend i.mid { background: #fdf3dc; border: 1px solid #b47b00; }
.factgrid-legend i.bad { background: #fde7e6; border: 1px solid #c9423e; }
.factgrid-legend i.new { background: var(--surface-soft); border: 1px solid var(--line); }

/* Bestätigungs-Modal */
.confirm-overlay {
  position: fixed; inset: 0; background: rgba(6, 30, 40, .55); display: none;
  align-items: center; justify-content: center; padding: 20px; z-index: 50;
}
.confirm-overlay.show { display: flex; }
.confirm-box {
  background: var(--surface); color: var(--ink); border-radius: 18px; box-shadow: var(--shadow);
  padding: clamp(20px, 4vw, 28px); width: min(420px, 100%); text-align: center; animation: pop .3s ease;
}
.confirm-box h3 { margin: 0 0 8px; font-size: 20px; }
.confirm-box p { margin: 0 0 18px; color: var(--ink-soft); }
.confirm-box .row { display: flex; gap: 10px; justify-content: center; }
.confirm-box .btn { width: auto; margin: 0; padding: 12px 20px; }
.confirm-box .btn.cancel { background: var(--surface-soft); color: var(--ink); }
.confirm-box .btn.go-danger { background: var(--bad); }

/* Toast (Flash-Meldung) */
.toast {
  position: fixed; left: 50%; top: 18px; transform: translateX(-50%) translateY(-16px);
  background: var(--ink); color: #fff; padding: 12px 20px; border-radius: 12px; font-weight: 700;
  box-shadow: var(--shadow); opacity: 0; transition: opacity .25s ease, transform .25s ease; z-index: 60;
  max-width: min(90vw, 520px);
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
.toast.ok { background: #1f9d5b; }
.toast.err { background: var(--bad); }

/* Ein Toast, der ohne JavaScript auskommt: Er wird vom Server in die Seite
   geschrieben und blendet sich von selbst wieder aus. Die Fassung darüber
   braucht ein Skript, das `.show` setzt und einen Wecker stellt – hier gibt es
   nichts zu entscheiden, also genügt die Zeitleiste.

   Ohne globale Bewegungs-Sperre ist das verlässlich: Die wurde ausdrücklich
   abgeschafft, weil sie auf Schulgeräten die halbe App stillstellte (siehe die
   Begründung weiter oben bei prefers-reduced-motion). */
.toast--auto { animation: toast-auto 4s ease forwards; }
@keyframes toast-auto {
  0%   { opacity: 0; transform: translateX(-50%) translateY(-16px); }
  6%   { opacity: 1; transform: translateX(-50%) translateY(0); }
  85%  { opacity: 1; transform: translateX(-50%) translateY(0); }
  100% { opacity: 0; transform: translateX(-50%) translateY(-16px); visibility: hidden; }
}

/* Anmeldeseite Admin */
.admin-login { width: min(400px, 100%); margin: 8vh auto 0; }

/* Benutzerkarten (Klassen-Benutzerverwaltung) */
.user-list { display: grid; gap: 12px; }
.user-card { border: 1px solid var(--surface-soft); border-radius: 14px; padding: 14px 16px; }
.user-card .uc-top { display: flex; flex-wrap: wrap; gap: 8px 12px; align-items: center; justify-content: space-between; }
.user-card .uc-name { font-weight: 800; font-size: 16px; display: flex; align-items: center; gap: 8px; }
.user-card .uc-meta { color: var(--ink-soft); font-size: 13px; margin-top: 2px; }
.user-card .uc-act { display: flex; gap: 6px; flex-wrap: wrap; }

/* Tastenkappe – zeigt, dass es auch ohne Maus geht (⏎ im Anlegen-Knopf).
   Erbt Schrift und Farbe vom Umfeld, damit sie auf dem farbigen Knopf ebenso
   sitzt wie in einem Hinweis daneben. */
.kbd {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 1.6em; padding: 1px 5px; margin-left: 2px;
  font-family: inherit; font-size: 0.8em; line-height: 1.4;
  border: 1px solid currentColor; border-radius: 6px;
  opacity: 0.75;
}
/* Im Knopf zählt das Leerzeichen daneben nicht: .btn ist ein Flex-Container
   und wirft Leerraum zwischen seinen Kindern weg. Der Abstand muss deshalb
   von hier kommen. */
.btn > .kbd, .btn-sm > .kbd { margin-left: 10px; }

/* „optional" neben einem Feldnamen: leiser als das Label, aber lesbar. */
.lab-opt { font-weight: 500; font-size: 12px; color: var(--ink-soft); }

/* „Gerade angelegt": was beim Erfassen am Stück entstanden ist (quick-add.js).
   Die eigentliche Liste oben zeigt es nach dem nächsten Laden – hier stehen so
   lange Name und Code, zum Aufschreiben und zum Nachschauen. */
.added-list { margin-top: 18px; border-top: 1px solid var(--surface-soft); padding-top: 14px; }
.added-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
.added-items { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.added-items li {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  border: 1px solid var(--surface-soft); border-radius: 12px; padding: 8px 12px;
}
.added-items .added-name { font-weight: 700; }
.added-items .added-code {
  font-family: ui-monospace, monospace; font-size: 13px; color: var(--ink-soft);
  background: var(--surface-soft); border-radius: 8px; padding: 2px 8px;
}
.added-items li > .btn-sm { margin-left: auto; }

/* „Bearbeiten" öffnet ein Modal. Bewusst über <details open> statt <dialog>
   gelöst: ohne JavaScript bleibt es ein normales Aufklapp-Element, das Formular
   funktioniert also weiterhin. Der Backdrop ist das ::before des <details>. */
.edit-pop { display: inline-block; }
.edit-pop > summary { list-style: none; user-select: none; }
.edit-pop > summary::-webkit-details-marker { display: none; }
.edit-pop > summary::before { content: '✎ '; }
/* Ohne Stift – z. B. wenn der Knopf keinen Datensatz bearbeitet. */
.edit-pop.plain > summary::before { content: none; }
.edit-pop[open] > summary { background: var(--accent); color: #fff; }
.edit-pop[open]::before { content: ''; position: fixed; inset: 0; background: rgba(6, 30, 40, .55); z-index: 60; }
/* Geschlossen ist der Dialog ganz weg, nicht bloss verborgen: Chrome legt den
   Inhalt eines zugeklappten <details> seit Version 131 trotzdem im Layout an
   (content-visibility statt display: none). Liegt der Dialog in einer
   ziehbaren Tabellenzeile, wüchse deren Schleppbild sonst auf die Fläche des
   unsichtbaren 620px-Dialogs an – und zeigte die halbe Seite. */
.edit-pop:not([open]) > .edit-dialog { display: none; }
.edit-dialog {
  position: fixed; z-index: 61; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: min(620px, calc(100vw - 28px)); max-height: min(88vh, 760px); overflow: auto;
  background: var(--surface); color: var(--ink); border-radius: 18px; box-shadow: var(--shadow);
  padding: clamp(18px, 3vw, 26px); text-align: left; animation: dialog-pop .25s ease;
}
/* Eigene Einblendung mit der Zentrierung IM Keyframe: Die allgemeine `pop`-
   Animation setzt nur `scale()` und warf damit für die Dauer der Animation das
   `translate(-50%, -50%)` weg – der Kasten erschien unten rechts (Ecke in der
   Bildschirmmitte) und sprang danach in die Mitte. */
@keyframes dialog-pop {
  0% { transform: translate(-50%, -50%) scale(0.94); opacity: 0; }
  100% { transform: translate(-50%, -50%) scale(1); opacity: 1; }
}
.edit-dialog .ed-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 16px; }
.edit-dialog .ed-head h3 { margin: 0; font-size: 20px; }
.edit-dialog .ed-x {
  border: none; background: var(--surface-soft); color: var(--ink-soft); border-radius: 10px;
  cursor: pointer; font-size: 15px; line-height: 1; padding: 8px 10px; flex: none;
}
.edit-dialog .ed-x:hover { background: var(--press); color: var(--ink); }
.edit-dialog .form-actions { margin-top: 4px; }
/* Feldfehler (z. B. doppelte Homework-ID), noch vor dem Speichern. */
.field .err-msg { font-size: 12.5px; font-weight: 700; color: var(--bad); }
.field input.err { border-color: var(--bad); }
.field input.err:focus { border-color: var(--bad); }
.btn-sm[disabled] { opacity: .5; cursor: not-allowed; }
.btn-sm.primary[disabled]:hover { background: var(--accent); }

/* Der Verwaltungsbereich verwendet bewusst helle Karten (weisse Panels auf dem
   Türkis-Verlauf) – in Light wie in Dark identisch. Frühere Dark-Overrides gingen
   von dunklen Karten aus und liessen z. B. den „Abbrechen"-Hover unlesbar; daher
   entfernt, damit das Erscheinungsbild in beiden Modi konsistent bleibt. */

/* --- Medaillenverwaltung (Admin) ------------------------------------------ */
/* Gleiche helle Karten wie der restliche Verwaltungsbereich – bewusst keine
   Dark-Mode-Sonderregeln. */
.hint { font-size: 12.5px; color: var(--ink-soft); }

/* Übersicht als Kachelraster – dieselben Medaillen-Kacheln wie in der Vitrine
   der Kinder, nur breiter (Bedingung + Aktionen brauchen Platz). */
.medal-admin .medal-group { margin-bottom: 26px; }
.medal-admin .medal-tiles { grid-template-columns: repeat(auto-fill, minmax(186px, 1fr)); }
.medal-tile.admin-tile { padding: 14px 10px 12px; gap: 4px; position: relative; }
/* Kein Hover-Anheben: auf den Kacheln liegen Buttons. */
.medal-tile.admin-tile.owned:hover { transform: none; box-shadow: var(--shadow); }
.medal-tile-cond {
  font-size: 11.5px; line-height: 1.3; color: var(--cat, var(--ink-soft));
  font-weight: 700; margin-top: 4px;
}
/* Verwaltungs-Infoblock in der Kachel: Bedingung + Anzahl Vergaben. Bewusst
   als abgesetzter grauer Kasten, damit klar ist, dass das Kind ihn nie sieht. */
/* margin-top:auto schiebt Infoblock UND Button gemeinsam ans untere Ende der
   Kachel. So sitzt der Block in einer Zeile immer auf derselben Höhe, auch wenn
   Name oder Beschreibung unterschiedlich lang umbrechen. */
.medal-admin-info {
  align-self: stretch; margin-top: auto; padding: 7px 9px; border-radius: 10px;
  background: var(--surface-soft); display: grid; gap: 4px; text-align: left;
}
.mai-row { display: grid; grid-template-columns: auto 1fr; gap: 4px 8px; align-items: baseline; }
.mai-label {
  font-size: 9.5px; font-weight: 800; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--ink-soft); opacity: 0.75; grid-column: 1 / -1;
}
.mai-row + .mai-row { border-top: 1px solid rgba(6, 30, 40, 0.08); padding-top: 5px; }
.mai-value {
  grid-column: 1 / -1; font-size: 11.5px; font-weight: 700; line-height: 1.3;
  color: var(--ink); font-variant-numeric: tabular-nums;
}
.mai-value.strong { color: var(--accent-strong); }
/* Nur ein Button je Kachel – Ausblenden und Löschen stecken im Formular, damit
   das Raster ruhig bleibt und die heiklen Aktionen einen Klick weiter weg sind. */
/* margin-top:auto schiebt den Button ans untere Ende, damit die Buttons einer
   Zeile trotz unterschiedlich langer Bedingungstexte auf einer Linie liegen. */
.medal-tile-edit {
  margin-top: 10px; align-self: stretch; text-align: center;
  padding-top: 6px; padding-bottom: 6px;
}
.medal-tile-meta { margin-bottom: 10px; }
.medal-off-flag {
  position: absolute; top: 8px; right: 8px; font-size: 11px; font-weight: 800;
  padding: 2px 7px; border-radius: 999px; background: #fdf3dc; color: #b47b00;
}
/* Ausgeblendete Medaille: wie „gesperrt" in der Vitrine, Aktionen bleiben klar lesbar. */
.medal-tile.is-off { background: var(--surface-soft); }
.medal-tile.is-off .medal-disc { filter: grayscale(1) brightness(0.92); }
.medal-tile.is-off .medal-ribbon { filter: grayscale(1); opacity: 0.5; }
.medal-tile.is-off .medal-tile-name,
.medal-tile.is-off .medal-tile-desc,
.medal-tile.is-off .medal-tile-cond { opacity: 0.65; }

/* Aufklappbarer „Gruppen & Farben"-Block */
/* Ganze Karte zuklappbar: zugeklappt sieht man nur den Titel und den Chevron.
   Für Einstellungen, die selten gebraucht werden und die Seite sonst zuziehen. */
.panel-fold > summary {
  cursor: pointer; list-style: none;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
}
.panel-fold > summary::-webkit-details-marker { display: none; }
.panel-fold > summary h2 { margin: 0; }
.panel-fold > summary::after {
  content: ''; flex: none; width: 9px; height: 9px; margin-right: 4px;
  border-right: 2.5px solid var(--ink-soft); border-bottom: 2.5px solid var(--ink-soft);
  transform: rotate(45deg) translate(-3px, -3px);
  transition: transform 0.2s ease;
}
.panel-fold[open] > summary::after { transform: rotate(-135deg) translate(-3px, -3px); }
.panel-fold[open] > summary { margin-bottom: 22px; }
@media (prefers-reduced-motion: reduce) { .panel-fold > summary::after { transition: none; } }
/* Formular mit Live-Vorschau daneben */
.medal-edit { display: grid; grid-template-columns: minmax(0, 1fr) 190px; gap: 24px; align-items: start; }
.medal-edit-main { display: grid; gap: 0; }
.medal-edit-preview {
  position: sticky; top: 16px; padding: 16px; border-radius: 16px; background: var(--surface-soft);
}
.medal-edit-preview .medal-tile { background: #fff; box-shadow: var(--shadow); }
@media (max-width: 720px) {
  .medal-edit { grid-template-columns: 1fr; }
  .medal-edit-preview { position: static; order: -1; }
}

/* Ausblenden/Löschen im Bearbeiten-Formular – abgesetzt, damit sie nicht
   versehentlich neben „Speichern" geklickt werden. */
.danger-zone {
  margin-top: 22px; padding-top: 14px; border-top: 1px dashed var(--surface-soft);
  display: flex; flex-wrap: wrap; gap: 10px 16px; align-items: center; justify-content: space-between;
}
.danger-zone .hint { flex: 1 1 240px; }
.danger-zone-actions { display: flex; gap: 8px; flex-wrap: wrap; }

/* Bedingung im Medaillen-Formular: Erklärung darüber, Klartext-Satz darunter. */
.rule-note { margin: 6px 0 12px; line-height: 1.45; }
.rule-text {
  margin: 10px 0 0; padding: 9px 13px; border-radius: 11px;
  background: var(--surface-soft); font-size: 13.5px; font-weight: 700; color: var(--ink);
}
.rule-text::before { content: '→ '; color: var(--accent-strong); }

/* Umsortieren per Ziehen und Ablegen (nur Verwaltung). */
.medal-tile.admin-tile { cursor: grab; }
.medal-tile.admin-tile:active { cursor: grabbing; }
/* Buttons und Links bleiben klickbar statt zu ziehen. */
.medal-tile.admin-tile .medal-tile-edit { cursor: pointer; }
.medal-grip {
  position: absolute; top: 7px; left: 8px; font-size: 13px; line-height: 1;
  color: var(--ink-soft); opacity: 0.35; letter-spacing: -1px;
}
.medal-tile.admin-tile:hover .medal-grip { opacity: 0.7; }

/* Das gezogene Teil bleibt an seinem künftigen Platz stehen und wird dort zur
   Lücke: gestrichelter Rahmen, Inhalt unsichtbar. Es soll ja gerade sichtbar
   sein, WO es landen wird – ein bloss blasses Abbild sähe aus, als läge es
   noch dort, wo es herkam. `visibility` statt `display`, damit die Lücke
   genau so gross bleibt wie die Kachel. */
.medal-tile.is-dragging {
  background: var(--surface-soft);
  box-shadow: none;
  outline: 2px dashed var(--accent);
  outline-offset: -3px;
}
.medal-tile.is-dragging > * { visibility: hidden; }

/* Tabellenzeilen können keinen Rahmen tragen – die Zellen übernehmen ihn. */
tr.is-dragging > td {
  background: var(--surface-soft);
  box-shadow: inset 0 2px 0 var(--accent), inset 0 -2px 0 var(--accent);
}
tr.is-dragging > td > * { visibility: hidden; }
tr.is-dragging > td:first-child { box-shadow: inset 0 2px 0 var(--accent), inset 0 -2px 0 var(--accent), inset 2px 0 0 var(--accent); }
tr.is-dragging > td:last-child { box-shadow: inset 0 2px 0 var(--accent), inset 0 -2px 0 var(--accent), inset -2px 0 0 var(--accent); }

.medal-tiles.is-saving, tbody.is-saving { opacity: 0.6; pointer-events: none; }

/* --- Shop-Verwaltung (Admin) ---------------------------------------------- */

/* Ein Regal je Art, mit Luft dazwischen: Themes, Titel und Hintergründe
   sind getrennte Sortierflächen und sollen auch getrennt aussehen. */
.shop-admin-group { margin-bottom: 26px; }
.shop-admin-group:last-child { margin-bottom: 0; }
.shop-admin-group > .mgmt-head { margin-bottom: 2px; }

/* Das Symbol trägt im Laden die Karte – in der Verwaltung genügt es als
   Erkennungszeichen vor dem Namen. */
.shop-admin-emoji { font-size: 18px; line-height: 1; margin-right: 6px; }
.shop-admin-table td .hint { display: block; margin-top: 2px; }
.shop-admin-table code {
  font-size: 12px; color: var(--ink-soft);
  background: var(--surface-soft); border-radius: 6px; padding: 2px 6px;
}
/* Ausgeblendete Artikel bleiben lesbar – sie sollen ja wiedergefunden werden. */
.shop-admin-table tr.is-off td { color: var(--ink-soft); }
.shop-admin-table tr.is-off .shop-admin-emoji { opacity: 0.5; }
/* Das „aus"-Fähnchen klebt bei den Medaillen in der Ecke der Kachel. In einer
   Tabellenzelle gibt es keine solche Ecke – hier läuft es einfach mit. */
.shop-admin-table .medal-off-flag { position: static; margin-left: 6px; white-space: nowrap; }

/* Der Griff sitzt in der Shop-Tabelle in einer eigenen Spalte, nicht über der
   Kachel – dort braucht er keine absolute Position. */
.shop-admin-table .medal-grip { position: static; display: block; }
.shop-admin-table tr:hover .medal-grip { opacity: 0.7; }
.shop-admin-table tr[draggable="true"] { cursor: grab; }
.shop-admin-table tr[draggable="true"]:active { cursor: grabbing; }
/* Knöpfe und Links bleiben klickbar, statt die Zeile zu ziehen. */
.shop-admin-table .row-actions { cursor: auto; }
.shop-admin-table .row-actions form { margin: 0; }

/* --- Stufen-Verwaltung (Admin) -------------------------------------------- */
.level-warn {
  display: flex; gap: 12px; align-items: flex-start; margin: 0 0 20px;
  padding: 13px 16px; border-radius: 14px;
  background: #fdf3dc; color: #6b4c00; font-size: 13.5px; line-height: 1.5;
}
.level-warn-ic { font-size: 18px; line-height: 1.3; flex: none; }
.level-warn strong { display: block; }

.level-grid { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(215px, 1fr)); }
.level-card {
  border: 2px solid var(--surface-soft); border-radius: 16px; padding: 13px 14px 14px;
  display: flex; flex-direction: column; gap: 10px;
}
.level-card.is-empty { border-color: var(--bad); background: #fff6f5; }
.level-card-head h3 { margin: 0; font-size: 15px; }
.level-card-meta { font-size: 12px; color: var(--ink-soft); font-weight: 700; font-variant-numeric: tabular-nums; }
.level-rows { display: flex; flex-wrap: wrap; gap: 8px; }

/* Reihen-Plakette: wird zwischen den Stufen-Karten gezogen. Die zugeordnete
   Stufe steckt im versteckten Feld und wird beim Ablegen gesetzt. */
.row-chip {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--surface-soft); border-radius: 11px; padding: 5px 7px 5px 7px;
  cursor: grab;
}
.row-chip:active { cursor: grabbing; }
.row-chip-grip { color: var(--ink-soft); opacity: 0.4; font-size: 12px; letter-spacing: -1px; }
.row-chip:hover .row-chip-grip { opacity: 0.75; }
.row-chip.is-dragging { opacity: 0.4; }
.row-chip-name { font-weight: 800; font-size: 14px; font-variant-numeric: tabular-nums; padding-right: 4px; }
/* Karte, über der gerade losgelassen würde. */
.level-card.is-drop { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, transparent); }
.row-chip.just-moved { animation: chip-pop 0.6s ease; }
@keyframes chip-pop {
  0% { transform: scale(1.12); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 45%, transparent); }
  100% { transform: none; box-shadow: none; }
}
.level-empty { margin: 2px 0 0; font-size: 12.5px; font-weight: 700; color: var(--bad); }

/* Gruppen-Editor: Karte selbst ziehbar, Fuss mit Farbe/Startblock/Entfernen. */
.level-card[data-group] { cursor: grab; }
.level-card[data-group]:active { cursor: grabbing; }
.level-card.is-dragging { opacity: 0.4; }
/* Bedienelemente im Fuss bleiben klickbar, statt die Karte zu ziehen. */
.level-card-foot { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-top: auto; cursor: default; }
.level-card-foot .check-chip { cursor: pointer; }
.level-card-foot input[type="color"] {
  width: 26px; height: 26px; padding: 0; border: none; background: none; cursor: pointer;
}
.level-card-head h3 .row-chip-grip { margin-right: 4px; }

/* Ablage für Reihen, die in dieser Klasse nicht geübt werden. */
.level-card.is-pool {
  border-style: dashed; background: var(--surface-soft);
  /* Eine leere Ablage ist der Normalfall und keine Fehlermeldung. */
}
.level-card.is-pool.is-empty { border-color: var(--surface-soft); background: var(--surface-soft); }
.level-card.is-pool .level-empty { color: var(--ink-soft); }

/* „Zusätzliche Gruppe erstellen": sieht aus wie eine leere Gruppen-Karte und
   steht an der Stelle, an der die neue Gruppe entstehen wird. */
.level-add {
  border: 2px dashed var(--surface-soft); border-radius: 16px; padding: 13px 14px 14px;
  min-height: 108px; background: none; cursor: pointer; font: inherit;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  font-size: 13.5px; font-weight: 800; color: var(--ink-soft); text-align: center;
}
.level-add:hover { border-color: var(--accent); color: var(--accent); }
.level-add-plus { font-size: 27px; line-height: 1; font-weight: 400; }

.btn.ghost[disabled] { opacity: 0.5; cursor: not-allowed; }
.btn.ghost[disabled]:hover { background: var(--surface-soft); }

/* Mitte des Startblocks: gehört zwingend dazu, also nicht abwählbar. */
.check-chip.is-locked { opacity: 0.75; cursor: default; }
.check-chip.is-locked input { cursor: default; }

/* Hinweiskasten mit führendem Symbol (z. B. Homework-Verknüpfung). */
.notice-ic { font-size: 17px; line-height: 1.3; flex: none; }
.notice:has(.notice-ic) { display: flex; gap: 11px; align-items: flex-start; }

/* Grosser Ein/Aus-Schalter (Wochenziel): die Entscheidung „überhaupt ein Ziel?"
   soll man nicht in einer Auswahlliste suchen müssen. */
.big-switch {
  display: flex; align-items: flex-start; gap: 14px; cursor: pointer;
  padding: 14px 16px; border-radius: 14px; background: var(--surface-soft);
  border: 2px solid transparent; transition: border-color .15s ease, background .15s ease;
}
.big-switch:hover { background: var(--press); }
.big-switch:has(input:checked) { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, var(--surface-soft)); }
.big-switch input { width: 26px; height: 26px; flex: none; margin: 0; accent-color: var(--accent); cursor: pointer; }
.big-switch-text strong { display: block; font-size: 15px; }
.big-switch-text span { display: block; font-size: 12.5px; color: var(--ink-soft); margin-top: 2px; }

/* Ein Häkchen, das zum grossen Schalter darüber gehört: eingerückt, leiser,
   ohne eigene Fläche. Es soll nach Nebensatz aussehen und nicht nach zweiter
   Entscheidung. */
.sub-switch {
  display: flex; align-items: flex-start; gap: 10px; cursor: pointer;
  margin: 10px 0 0 30px; padding: 4px 2px;
}
.sub-switch input {
  width: 19px; height: 19px; flex: none; margin: 2px 0 0;
  accent-color: var(--accent); cursor: pointer;
}
.sub-switch > span { display: block; }
.sub-switch strong { display: block; font-size: 14px; }
.sub-switch .hint { display: block; margin-top: 2px; }
@media (max-width: 480px) { .sub-switch { margin-left: 8px; } }

/* Klassenformular im Admin: sichtbar trennen, was die Lehrperson auch selbst
   ändern kann – sonst weiss man nie, wo eine Einstellung wirklich herkommt. */
.scope-head {
  margin: 22px 0 4px; font-size: 15px; display: flex; align-items: center; gap: 8px;
}
.scope-head:first-of-type { margin-top: 2px; }
.scope-head::after {
  font-size: 11px; font-weight: 800; letter-spacing: .03em; text-transform: uppercase;
  padding: 2px 8px; border-radius: 999px;
}
.scope-head.scope-admin::after { content: 'nur Admin'; background: #eaf4fb; color: #245b78; }
.scope-head.scope-teacher::after { content: 'auch Lehrperson'; background: #e6f8ee; color: #1f9d5b; }

/* Formularfelder einer Rasterzeile auf gleicher Höhe: Beschriftung, Eingabe und
   Hinweis bekommen je eine gemeinsame Zeile (subgrid). Ohne subgrid bleibt es
   beim bisherigen Verhalten – dann stehen die Eingaben wie zuvor. */
@supports (grid-template-rows: subgrid) {
  .mgmt-grid2 { grid-auto-rows: auto auto 1fr; }
  .mgmt-grid2 > .field { grid-row: span 3; display: grid; grid-template-rows: subgrid; }
  /* Häkchenfelder haben keinen Hinweis und sollen nicht mitgestreckt werden. */
  .mgmt-grid2 > .field.check { grid-row: auto; display: flex; }
}

/* Häkchen-Einstellungen nebeneinander statt je in einer eigenen Rasterzelle. */
.check-row { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 4px; }
.check-chip {
  display: inline-flex; align-items: center; gap: 9px; cursor: pointer;
  padding: 9px 14px 9px 11px; border-radius: 12px; background: var(--surface-soft);
  border: 2px solid transparent; font-weight: 700; font-size: 14px;
  transition: border-color .15s ease, background .15s ease;
}
.check-chip:hover { background: var(--press); }
.check-chip:has(input:checked) { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, var(--surface-soft)); }
.check-chip input { width: 20px; height: 20px; flex: none; margin: 0; accent-color: var(--accent); cursor: pointer; }

/* Ohne den Knopf „Klasse wechseln" schliesst die Kachelfläche sonst hart am
   unteren Rand ab – der Abstand, den der Knopf sonst mitbringt, fehlt dann. */
.panel.no-logout { margin-bottom: 42px; }

/* ===================================================================== */
/* Profilbild: Baukasten (Kind) und Modus-Auswahl (Lehrperson/Admin)      */
/* ===================================================================== */

/* --- Baukasten in den Schüler-Einstellungen (Modal) --- */
/* Ein Overlay über einem Element mit backdrop-filter lässt dessen abgerundete
   Ecken eckig erscheinen. Solange ein Modal offen ist, bleibt der Weichzeichner
   deshalb aus – hinter der dunklen Fläche sieht man ihn ohnehin kaum. */
body.modal-open .shell, body.pf-open .shell { -webkit-backdrop-filter: none; backdrop-filter: none; }
body.modal-open, body.pf-open { overflow: hidden; }

.ab-modal {
  position: fixed; inset: 0; z-index: 60; background: rgba(6, 30, 40, 0.55);
  display: flex; align-items: center; justify-content: center; padding: 16px;
}
.ab-modal-box {
  position: relative; background: var(--surface); border-radius: 22px;
  box-shadow: var(--shadow);
  /* Oben mehr Luft: dort sitzt der Schliessen-Knopf und soll nicht auf den
     Farbfeldern der ersten Zeile liegen. */
  padding: 52px 26px 24px;
  width: min(680px, 100%); max-height: 92vh; overflow-y: auto;
  animation: pop 0.25s ease;
}
.ab-modal-box .code-close { top: 14px; right: 14px; }
/* Zwei Spalten: links das Bild, rechts die Zeilen. */
.avatar-builder {
  display: grid; gap: 16px 24px;
  /* Feste, grosszügige Spalte links – das Bild soll Platz um sich haben. */
  grid-template-columns: 224px minmax(0, 1fr);
  grid-template-areas: "stage rows" "actions actions";
  align-items: start;
}
.ab-stage {
  grid-area: stage; position: sticky; top: 0;
  display: flex; flex-direction: column; align-items: center; gap: 14px;
  padding: 6px 0 2px;
}
.ab-rows { grid-area: rows; }
.ab-actions { grid-area: actions; }
.ab-preview {
  width: 168px; height: 168px; border-radius: 50%; display: block; flex: none;
  background: var(--surface-soft);
  box-shadow: 0 6px 18px rgba(6, 30, 40, 0.22);
  transition: opacity 0.15s ease;
}
.ab-preview.is-loading { opacity: 0.65; }
.ab-dice { font-size: 13px; }

/* Eine Zeile pro Teil: Name, Pfeile, Farben – alles auf einer Höhe. */
.ab-rows { display: grid; gap: 5px; }
.ab-row {
  display: grid; align-items: center; gap: 4px 8px;
  grid-template-columns: 5.6em auto 1fr;
  padding: 4px 6px; border-radius: 10px;
}
.ab-row:nth-child(odd) { background: var(--surface-soft); }
.ab-label { font-weight: 700; font-size: 12.5px; color: var(--ink-soft); }

.ab-pick { display: inline-flex; align-items: center; gap: 2px; }
/* „keine" sitzt als kleiner Schalter vor dem linken Pfeil. */
.ab-none {
  width: 24px; height: 24px; flex: none; margin-right: 4px;
  border: none; border-radius: 50%; cursor: pointer; line-height: 1;
  font-size: 13px; background: var(--surface); color: var(--ink-soft);
  box-shadow: 0 1px 2px rgba(6, 30, 40, 0.14);
}
.ab-none:hover { background: var(--press); color: var(--ink); }
.ab-none[aria-pressed="true"] { background: var(--accent); color: var(--on-accent); }
.ab-arrow {
  width: 28px; height: 28px; flex: none; border: none; border-radius: 8px;
  background: var(--surface); color: var(--ink); cursor: pointer;
  font-size: 17px; font-weight: 800; line-height: 1;
  box-shadow: 0 1px 2px rgba(6, 30, 40, 0.14);
}
.ab-arrow:hover { background: var(--press); }
.ab-arrow:active { transform: translateY(1px); }
.ab-count {
  min-width: 3.2em; text-align: center; font-size: 11.5px; font-weight: 700;
  color: var(--ink-soft); font-variant-numeric: tabular-nums;
}

/* Farbfelder direkt neben den Pfeilen, klein genug für eine Zeile. */
.ab-colors { display: flex; flex-wrap: wrap; gap: 4px; justify-content: flex-end; }
.ab-color { position: relative; line-height: 0; cursor: pointer; }
.ab-color input { position: absolute; opacity: 0; pointer-events: none; }
.ab-color span {
  display: block; width: 20px; height: 20px; border-radius: 50%;
  box-shadow: inset 0 0 0 1px rgba(6, 30, 40, 0.18);
  transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.ab-color input:checked + span {
  transform: scale(1.2);
  box-shadow: 0 0 0 2px var(--surface), 0 0 0 3.5px var(--accent);
}
.ab-color input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 3px; }

/* Noch nicht gekauft: Das Farbfeld bleibt in voller Farbe und anwählbar – man
   soll den Verlauf am eigenen Bild sehen, bevor man sich entscheidet. Das
   kleine Schloss sagt nur, dass zum Speichern noch ein Schritt fehlt. */
.ab-color.is-locked .ab-lock {
  position: absolute; right: -3px; bottom: -3px;
  font-size: 9px; line-height: 1; pointer-events: none;
  filter: drop-shadow(0 0 2px var(--surface)) drop-shadow(0 0 2px var(--surface));
}

/* Die Kaufzeile unter der Hintergrund-Reihe. */
/* Kein negativer Rand mehr: Der zog die Kaufzeile bis an die Farbfelder
   heran, so dass sie oben klebte und nur unten Luft hatte. Jetzt trägt der
   Rasterabstand von .ab-rows beide Seiten gleich. */
.ab-offers { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; }
.ab-offer {
  display: inline-flex; align-items: center; gap: 6px;
  border: 0; border-radius: 999px; padding: 7px 14px;
  font: inherit; font-size: 13px; font-weight: 800; line-height: 1.2;
  background: var(--star); color: #4a3500; cursor: pointer;
}
.ab-offer:hover { background: color-mix(in srgb, var(--star) 88%, #000); }
.ab-offer.is-off { background: var(--surface-soft); color: var(--ink-soft); cursor: default; }

.ab-actions { display: flex; align-items: center; gap: 10px; margin-top: 4px; flex-wrap: wrap; }
.ab-actions .btn { margin-top: 0; }
.ab-actions .btn[disabled] { opacity: 0.5; cursor: not-allowed; }
.ab-save-hint { margin: 0; font-size: 13px; font-weight: 700; color: var(--ink-soft); }

@media (max-width: 560px) {
  .avatar-builder { grid-template-columns: 1fr; grid-template-areas: "stage" "rows" "actions"; }
  .ab-stage { position: static; flex-direction: row; justify-content: center; }
  .ab-preview { width: 116px; height: 116px; }
}
@media (max-width: 420px) {
  .ab-row { grid-template-columns: 4.6em auto; }
  .ab-colors { grid-column: 1 / -1; justify-content: flex-start; }
}

/* --- Modus-Auswahl in der Verwaltung --- */
/* Entweder-oder: die drei Möglichkeiten bilden EINEN Block, keine drei
   getrennten Kästen. Getrennt werden sie nur durch eine feine Linie. */
.avatar-modes {
  display: grid; background: var(--surface-soft);
  border-radius: 14px; overflow: hidden;
}
.avatar-mode {
  display: flex; align-items: center; gap: 13px; cursor: pointer;
  /* margin: 0 ist nötig – die allgemeine label-Regel gibt sonst 14px oben und
     6px unten mit, die weder eingefärbt noch angeklickt werden können. */
  margin: 0; padding: 12px 14px; background: transparent;
  box-shadow: inset 0 -1px 0 rgba(6, 30, 40, 0.08);
  transition: background 0.15s ease, box-shadow 0.15s ease;
}
.avatar-mode:last-child { box-shadow: none; }
.avatar-mode:hover { background: var(--press); }
/* Die Auswahl färbt das ganze Segment, mit Balken links – so springt beim
   Wechseln nichts. `:has` sorgt dafür, dass die Markierung sofort dem Klick
   folgt; `.is-on` kommt vom Server und deckt Browser ohne `:has` ab. */
.avatar-mode.is-on,
.avatar-mode:has(input:checked) {
  background: color-mix(in srgb, var(--accent) 14%, var(--surface-soft));
  box-shadow: inset 4px 0 0 var(--accent);
}
/* Beim Umschalten darf die alte Markierung nicht stehen bleiben. */
.avatar-modes:has(input:checked) .avatar-mode.is-on:not(:has(input:checked)) {
  background: transparent;
  box-shadow: inset 0 -1px 0 rgba(6, 30, 40, 0.08);
}
.avatar-modes:has(input:checked) .avatar-mode.is-on:not(:has(input:checked)):last-child {
  box-shadow: none;
}
.avatar-mode input { width: 20px; height: 20px; flex: none; accent-color: var(--accent); }

/* Unterauswahl „welche Figuren" – gehört zur Möglichkeit darüber, deshalb
   eingerückt, kleiner und nur schwach abgesetzt. */
.avatar-figures {
  display: flex; flex-wrap: wrap; gap: 6px 8px;
  padding: 2px 12px 11px 60px;
  /* Sie ist nur sichtbar, wenn „Avatar" gewählt ist – also trägt sie dieselbe
     Einfärbung und denselben Balken wie die Zeile darüber und wirkt mit ihr
     als ein Stück. */
  background: color-mix(in srgb, var(--accent) 14%, var(--surface-soft));
  box-shadow: inset 4px 0 0 var(--accent), inset 0 -1px 0 rgba(6, 30, 40, 0.08);
}
.avatar-figures:last-child { box-shadow: inset 4px 0 0 var(--accent); }
/* Keine Trennlinie zwischen der Zeile „Avatar" und ihrer Unterauswahl. */
.avatar-modes:has(input[value="builder"]:checked) .avatar-mode:has(input[value="builder"]) {
  box-shadow: inset 4px 0 0 var(--accent);
}
.avatar-modes:has(input[value="builder"]:not(:checked)) .avatar-figures { display: none; }
.avatar-figure { position: relative; margin: 0; cursor: pointer; }
.avatar-figure input { position: absolute; opacity: 0; pointer-events: none; }
.avatar-figure span {
  display: inline-block; padding: 5px 11px; border-radius: 999px;
  background: var(--surface); color: var(--ink-soft);
  font-size: 12.5px; font-weight: 600; line-height: 1.3;
  box-shadow: inset 0 0 0 1px rgba(6, 30, 40, 0.12);
}
.avatar-figure span strong { color: var(--ink); font-weight: 800; }
.avatar-figure input:checked + span {
  background: color-mix(in srgb, var(--accent) 16%, var(--surface));
  box-shadow: inset 0 0 0 2px var(--accent);
}
.avatar-figure input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }

.avatar-mode-demo { flex: none; line-height: 0; }
.am-dot {
  display: grid; place-items: center; width: 38px; height: 38px; border-radius: 50%;
  font-size: 19px; line-height: 1; flex: none;
}
.am-dot svg { width: 20px; height: 20px; display: block; }
.am-plain { background: linear-gradient(160deg, #cfe6ef, #9cc6d6); }
.am-built { background: linear-gradient(180deg, #7fe6ff, #1fb6d6); color: #fff; }
.am-photo { background: var(--surface); color: var(--ink-soft); box-shadow: inset 0 0 0 2px var(--line); }
.avatar-mode-text { display: flex; flex-direction: column; gap: 2px; }
.avatar-mode-text strong { font-size: 15px; }
.avatar-mode-text span { font-size: 13px; color: var(--ink-soft); line-height: 1.35; }

/* Auskunft statt Eingabe: Was am Artikel feststeht, weil es zum Code gehört
   (Art und Baustein im Shop-Formular). Sieht bewusst nicht wie ein Feld aus –
   es gibt daran nichts zu tippen. */
.fact-list { display: grid; grid-template-columns: 7em minmax(0, 1fr); gap: 8px 14px; margin: 0 0 4px; }
.fact-list dt { font-weight: 700; color: var(--ink-soft); font-size: 14px; padding-top: 1px; }
.fact-list dd { margin: 0; font-weight: 700; }
.fact-list dd .hint { display: block; font-weight: 400; margin-top: 2px; }
@media (max-width: 480px) { .fact-list { grid-template-columns: 1fr; gap: 2px 0; } }

/* Gesperrtes Feld (z. B. Wochenziel ohne Häkchen): sichtbar, aber blass. */
.field.is-disabled { opacity: 0.5; }
.field.is-disabled label { color: var(--ink-soft); }
.field input:disabled, .field select:disabled { cursor: not-allowed; background: var(--surface-soft); }

/* --- Einrichtungs-Assistent (erster Login der Lehrperson) ------------------
   Bewusst ohne Verwaltungsmenü: eine schmale Kopfzeile, ein Panel, unten
   genau drei Wege – später, zurück, weiter. */
.wiz-head {
  width: min(720px, 100%); margin: 0 auto clamp(16px, 3vw, 26px);
  display: flex; align-items: center; gap: 10px; color: var(--on-dark);
}
.wiz-head .octo { font-size: 34px; line-height: 1; }
.wiz-head-title { font-size: clamp(17px, 3vw, 22px); font-weight: 900; letter-spacing: .01em; }

.manage.wiz { width: min(720px, 100%); }

.wiz-steps { display: flex; align-items: center; gap: 18px; margin-bottom: 10px; }
.wiz-dot {
  position: relative; display: grid; place-items: center; flex: none;
  width: 30px; height: 30px; border-radius: 50%;
  font-size: 14px; font-weight: 800; line-height: 1;
  background: var(--surface-soft); color: var(--ink-soft);
  box-shadow: inset 0 0 0 2px var(--line);
  transition: background 0.2s ease, color 0.2s ease, box-shadow 0.2s ease;
}
/* Die Verbindung liegt genau in der Lücke davor (gap: 18px) – sie zeigt den
   Weg, nicht nur die Station, und färbt sich bis zum aktuellen Schritt ein. */
.wiz-dot + .wiz-dot::before {
  content: ''; position: absolute; right: 100%; top: 50%; transform: translateY(-50%);
  width: 18px; height: 3px; border-radius: 2px; background: var(--line);
}
.wiz-dot.is-done { background: var(--accent); color: var(--on-accent); box-shadow: none; }
.wiz-dot.is-done::before, .wiz-dot.is-now::before { background: var(--accent); }
.wiz-dot.is-now { background: var(--surface); color: var(--accent-strong); box-shadow: inset 0 0 0 3px var(--accent); }
.wiz-count { margin: 0 0 14px; font-size: 13px; font-weight: 700; color: var(--ink-soft); }

.wiz-hello { display: flex; align-items: center; gap: clamp(8px, 2vw, 18px); margin-bottom: 16px; }
.wiz-hello p { margin: 0; color: var(--ink-soft); line-height: 1.45; }
.wiz-hello .mascot-greet { width: clamp(80px, 20vw, 110px); height: clamp(80px, 20vw, 110px); flex: none; }

/* --- Mockups im Assistenten --------------------------------------------------
   „Geteilt-Aufgaben" und „STAR-Shop" sagen aus dem Namen heraus nichts. Diese
   kleinen Nachbauten zeigen in einem Absatz, was die Kinder später sehen –
   nachgebaut und nicht der echte Bildschirm, damit sie ohne Daten und auf jeder
   Breite funktionieren. Sie sind reine Anschauung: aria-hidden im Markup, weil
   der Text daneben dasselbe sagt. */
.wiz-demo {
  background: var(--tint); border: 1px solid var(--tint-line); border-radius: 14px;
  padding: 16px 14px; margin: 0 0 18px; text-align: center;
}
.wiz-demo-cap {
  margin: 0 0 12px; font-size: 11.5px; font-weight: 800; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-soft);
}
/* Ein ganzer Satz statt einer Überschrift – in Grossbuchstaben gesetzt würde er
   schreien, und gelesen wird er auch schlechter. */
.wiz-demo-cap.is-sentence {
  font-size: 13px; font-weight: 700; letter-spacing: 0; text-transform: none;
  line-height: 1.5; max-width: 52ch; margin-inline: auto;
}
.wiz-demo-row { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
.wiz-demo-note { margin: 12px 0 10px; font-size: 13px; color: var(--ink-soft); }

/* Eine Aufgabe, wie sie im Spiel steht. */
.demo-task {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--surface); border-radius: 10px; padding: 9px 13px;
  font-size: 16px; font-weight: 800; color: var(--ink);
  box-shadow: 0 1px 0 var(--line-soft);
}
/* Die Aufgabe, die sicher sitzt – Voraussetzung für die Geteilt-Aufgabe. */
.demo-task.is-mastered { color: var(--good); box-shadow: inset 0 0 0 2px var(--good); }
.demo-check { font-size: 13px; }
/* Die Geteilt-Aufgabe selbst: dieselbe Form, andere Farbe – sie ist neu. */
.demo-task.is-division {
  color: #fff; background: var(--accent-strong); box-shadow: none; font-size: 18px;
}

/* Der Laden: Guthaben und drei Karten. */
.demo-balance {
  display: inline-block; margin: 0 0 12px; padding: 5px 12px; border-radius: 999px;
  background: var(--surface); font-size: 13px; font-weight: 800; color: var(--ink);
}
.demo-card {
  display: flex; flex-direction: column; align-items: center; gap: 4px; width: 104px;
  background: var(--surface); border-radius: 12px; padding: 10px 8px;
  box-shadow: 0 1px 0 var(--line-soft);
}
.demo-card-pic {
  display: grid; place-items: center; width: 100%; height: 46px; border-radius: 9px; font-size: 22px;
}
.demo-pic-night { background: linear-gradient(160deg, #1b2a55, #402a63); }
.demo-pic-title { background: linear-gradient(160deg, #ffd98a, #ffb03a); }
.demo-pic-deco { background: linear-gradient(160deg, #cdeafc, #a5d8f3); }
.demo-card-name { font-size: 11.5px; font-weight: 700; color: var(--ink); line-height: 1.25; }
.demo-card-price { font-size: 11.5px; font-weight: 800; color: var(--ink-soft); }
/* Noch nicht zu bezahlen – zeigt, dass Sparen dazugehört. */
.demo-card.is-locked { opacity: .5; }

/* Die Stufen: je Zeile eine Stufe mit ihren Reihen. Absichtlich untereinander –
   die Reihenfolge der Stufen ist die Aussage, und sie liest sich von oben nach
   unten wie der Aufbau des Schuljahres. */
.demo-levels { display: grid; gap: 6px; max-width: 320px; margin: 0 auto; }
.demo-level {
  display: flex; align-items: center; gap: 6px; background: var(--surface);
  border-radius: 10px; padding: 7px 10px;
}
.demo-level-no {
  flex: none; width: 62px; text-align: left; font-size: 11.5px; font-weight: 800;
  color: var(--ink-soft);
}
.demo-row {
  display: grid; place-items: center; min-width: 26px; height: 26px; padding: 0 5px;
  border-radius: 8px; background: var(--tint); font-size: 13px; font-weight: 800; color: var(--ink);
}

/* Profilbild: Auswahl links, Anmeldekachel rechts. Unter 720px untereinander,
   Bild zuerst – dann sieht man beim Tippen auf eine Möglichkeit, was passiert. */
.wiz-avatar { display: grid; gap: 18px; grid-template-columns: minmax(0, 1fr) 200px; align-items: start; }
.wiz-avatar-preview {
  background: var(--tint); border: 1px solid var(--tint-line); border-radius: 14px;
  padding: 14px 12px; text-align: center;
}
.demo-tile {
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  background: var(--surface); border-radius: 14px; padding: 14px 10px;
}
.demo-tile-pic {
  display: grid; place-items: center; width: 96px; height: 96px; border-radius: 50%;
  background: var(--surface-soft); color: var(--ink-soft); font-size: 34px; overflow: hidden;
}
.demo-tile-pic svg { display: block; }
.demo-figure { display: grid; place-items: center; width: 100%; height: 100%; }
.demo-figure[hidden] { display: none; }
.demo-figure svg { width: 100%; height: 100%; }
.demo-tile-name { font-size: 15px; font-weight: 800; color: var(--ink); }

@media (max-width: 720px) {
  .wiz-avatar { grid-template-columns: 1fr; }
  .wiz-avatar-preview { order: -1; }
}

.wiz-final { text-align: center; }
.wiz-final .lead { max-width: 46ch; margin-inline: auto; }
.wiz-final .form-actions { justify-content: center; }
.wiz-final .mascot-greet { margin-bottom: clamp(-20px, -4vw, -8px); }

.wiz-nav {
  display: flex; flex-wrap: wrap; gap: 12px; align-items: center; justify-content: space-between;
  margin-top: 24px; padding-top: 18px; border-top: 1px solid var(--line-soft);
}
.wiz-nav-go { display: flex; flex-wrap: wrap; gap: 10px; align-items: stretch; }
/* „Zurück" ist ein Link, „Weiter" ein Button – dass beide gleich hoch sind,
   regelt jetzt .btn selbst; hier stand dafür früher eine eigene Regel. */
.wiz-nav-go form { display: flex; }
/* „Später erledigen" ist ein Ausweg, kein Ziel – deshalb schlicht und leise. */
.wiz-skip {
  border: 0; background: none; padding: 6px 2px; cursor: pointer;
  color: var(--ink-soft); font: inherit; font-size: 14px; font-weight: 600;
  text-decoration: underline; text-underline-offset: 3px;
}
.wiz-skip:hover { color: var(--ink); }

@media (prefers-reduced-motion: reduce) { .wiz-dot { transition: none; } }

/* --------------------------------------------------------------------------
   STAR-Shop
   --------------------------------------------------------------------------
   Bewusst NICHT wie die Medaillen-Vitrine. Dort sind runde Scheiben mit Band
   das Motiv und Gesperrtes wird abgedunkelt – richtig, denn dort soll das
   Verdiente den Blick bekommen. Ein Laden funktioniert umgekehrt: Gerade die
   Ware, die man noch nicht hat, muss man klar sehen können, sonst weiss
   niemand, wofür sich das Sparen lohnt.

   Deshalb: liegende Karten statt Discs, Regalzeilen statt einem dichten
   Raster, und Emoji, Name und Beschreibung in JEDEM Zustand gleich hell.
   Unterschieden wird allein über Fusszeile und Rahmen. */

.shop { width: 100%; display: flex; flex-direction: column; gap: clamp(18px, 3vw, 28px); }

/* Das Konto: die grosse Zahl, daneben die Einordnung. */
.shop-wallet {
  display: flex; align-items: center; gap: clamp(14px, 3vw, 26px); flex-wrap: wrap;
  background: var(--surface); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: clamp(16px, 3vw, 24px) clamp(18px, 3.5vw, 28px);
}
.shop-wallet-main { display: flex; align-items: baseline; gap: 6px; }
.shop-wallet-num { font-size: clamp(40px, 8vw, 60px); font-weight: 900; line-height: 1; color: var(--ink); }
.shop-wallet-star { font-size: clamp(24px, 5vw, 34px); }
.shop-wallet-side { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.shop-wallet-side strong { font-size: 17px; color: var(--ink); }
.shop-wallet-side span { font-size: 14px; color: var(--ink-soft); }
/* Der wichtigste Satz im Geldbeutel, aber keine Erfolgsmeldung: Grün gehört
   dem, was gerade gut ausgegangen ist. Hier steht eine Beruhigung, die immer
   gilt – sie hebt sich über die Schrift ab, nicht über die Farbe.

   Mit dem Elternteil geschrieben statt mit `!important`: `.shop-wallet-side
   span` ist als Klasse plus Element spezifischer als eine Klasse allein, und
   dagegen half vorher nur die Brechstange. */
.shop-wallet-side .shop-wallet-note { font-weight: 700; color: var(--ink); }

/* Gesetzte Sterne sind kein Kontostand, sondern ein Zustand, der vorbeigeht –
   und stünden sie in derselben Schrift wie „gesammelt, ausgegeben", ginge
   genau das unter. Deshalb eine eigene ruhige Fläche innerhalb der Karte, die
   Zahl darin betont: Das Auge findet zuerst, wie viele fehlen, und liest erst
   dann, warum.

   Mit dem Elternteil geschrieben, sonst gewönne `.shop-wallet-side span`
   (Klasse plus Element) über Schriftgrösse und Farbe. */
.shop-wallet-side .shop-wallet-hold {
  /* Nur so breit wie nötig, damit der Kasten bei kurzem Text nicht über die
     ganze Karte läuft. `max-width` ist dabei ein Riegel und keine Reparatur:
     `flex-start` lässt ein Flex-Element auf seine Textbreite wachsen –
     nachgemessen bei 330 px Karte bricht der Satz sauber um, aber der Riegel
     kostet nichts und hält auch einen längeren aus. */
  align-self: flex-start;
  max-width: 100%;
  margin: 6px 0 2px;
  padding: 7px 11px;
  border-radius: 10px;
  background: var(--surface-soft);
  color: var(--ink-soft);
  font-size: 13px;
  line-height: 1.45;
}
.shop-wallet-hold b { color: var(--ink); font-weight: 800; }

/* Ein Regal je Art. */
.shop-rack { display: flex; flex-direction: column; gap: 12px; }
.shop-rack-head { padding: 0 4px; }
.shop-rack-head h2 { margin: 0; font-size: 20px; color: var(--on-dark); }
.shop-rack-head p { margin: 2px 0 0; font-size: 14px; color: var(--on-dark-soft); }

/* Breite, liegende Karten – nicht das enge Disc-Raster der Vitrine. */
.shop-cards {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
}
.shop-card {
  display: flex; flex-direction: column; gap: 6px;
  background: var(--surface); border-radius: 18px; padding: 16px 18px 14px;
  box-shadow: var(--shadow);
  /* Der Rahmen ist der einzige Zustandsträger neben der Fusszeile. Er ist
     immer da, damit die Karte beim Kauf nicht springt. */
  border: 2px solid transparent;
}
.shop-card.is-owned { border-color: color-mix(in srgb, var(--accent) 45%, transparent); }
.shop-card.is-worn { border-color: var(--accent); box-shadow: 0 10px 30px color-mix(in srgb, var(--accent) 30%, transparent); }

/* Die Karte eines Themes IST das Theme: Sie trägt dessen Klasse, also
   greifen darin alle Marken der Welt – Karton, Schrift, Ecken, Knopf. Der
   Streifen oben zeigt dazu den Himmel, den man bekommt. Nur die beiden
   Stimmungsebenen bleiben aussen vor: Die hängen an <body> und beginnen erst
   zu ziehen, wenn die Welt wirklich getragen wird. */
.shop-card--theme { overflow: hidden; border-radius: var(--radius); }
.shop-theme-himmel {
  display: block; height: 64px; margin: -16px -18px 12px;
  background:
    radial-gradient(80% 90% at 50% 118%, color-mix(in srgb, var(--accent) 55%, transparent), transparent 72%),
    linear-gradient(168deg, var(--bg-top), var(--bg) 52%, var(--bg-deep));
}

.shop-card-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
.shop-card-emoji { font-size: 34px; line-height: 1; }
.shop-flag {
  font-size: 12px; font-weight: 800; color: var(--accent-strong);
  background: var(--surface-soft); border-radius: 999px; padding: 4px 9px; white-space: nowrap;
}
.shop-card-name { margin: 0; font-size: 17px; color: var(--ink); }
.shop-card-desc { margin: 0; font-size: 14px; line-height: 1.45; color: var(--ink-soft); flex: 1; }
.shop-card-foot { margin-top: 8px; }
.shop-card-foot form { margin: 0; }

/* Der Knopf trägt den Zustand. Kaufbar = Akzentfarbe, zu teuer = grau, aber
   in beiden Fällen bleibt die Karte darüber unverändert hell. */
.shop-buy {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  width: 100%; border: 0; border-radius: var(--btn-radius); padding: 10px 14px;
  font: inherit; font-weight: 800; font-size: 15px; cursor: pointer;
  background: var(--accent); color: var(--on-accent);
}
.shop-buy:hover { background: var(--accent-strong); }
.shop-buy:active { transform: translateY(1px); }
.shop-buy.is-wear { background: var(--good); }
.shop-buy.is-wear:hover { background: color-mix(in srgb, var(--good) 84%, #000); }
/* Zu teuer und „schon getragen" sind keine Knöpfe, sondern Zustände. */
.shop-buy.is-off, .shop-buy.is-done { cursor: default; }
.shop-buy.is-off { background: var(--surface-soft); color: var(--ink-soft); }
.shop-buy.is-done { background: var(--surface-soft); color: var(--accent-strong); }
.shop-lock { font-size: 13px; }
.shop-missing { margin: 6px 0 0; font-size: 13px; font-weight: 700; color: var(--ink-soft); text-align: center; }

/* Rückfrage vor dem Kauf – nutzt den Rahmen des Avatar-Baukastens, aber in
   festlich: Das ist der Moment, in dem aus gesammelten Sternen etwas
   Sichtbares wird. Also gross, mittig und freundlich statt Warnton. */
.shop-confirm { max-width: 420px; text-align: center; padding-top: 44px; }
.shop-confirm-emoji {
  font-size: 64px; line-height: 1; margin-bottom: 8px;
  animation: shop-pop 0.32s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.shop-confirm-title { margin: 0 0 6px; font-size: 24px; color: var(--ink); }
.shop-confirm-text { margin: 0 0 20px; font-size: 16px; line-height: 1.5; color: var(--ink-soft); }
.shop-confirm-text strong { color: var(--ink); }
.shop-confirm-actions { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; }
.shop-confirm-actions .btn { margin-top: 0; flex: 1 1 130px; }

/* Das Symbol hüpft einmal herein – kurz genug, um nicht aufzuhalten. */
@keyframes shop-pop {
  0% { transform: scale(0.4); opacity: 0; }
  100% { transform: scale(1); opacity: 1; }
}

/* Wochenziel und Guthaben zugleich: Statt drei langer Kästen untereinander
   ein Block – links schmal die Sterne, rechts Ziel und Lern-Knopf. Die rechte
   Spalte steht im HTML zuerst und wird hier nach rechts geschoben; bricht der
   Block auf schmalen Geräten um, steht sie dadurch wieder oben. */
/* Kopfblock: Lern-Knopf breit, daneben das Guthaben als kompakte Kachel.
   Auf schmalen Geräten untereinander, das Guthaben dann als Zeile. */
.home-top { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 12px; align-items: stretch; }
.home-top .home-stars {
  flex-direction: column; justify-content: center; text-align: center; gap: 4px;
  min-width: 132px; max-width: 180px; padding: 12px 16px;
}
.home-top .home-stars-num { font-size: clamp(24px, 5vw, 30px); line-height: 1.05; }
.home-top .home-stars-text { flex: 0 1 auto; font-size: 12.5px; line-height: 1.3; }
.home-top .home-stars-go { display: none; }
@media (max-width: 560px) {
  .home-top { grid-template-columns: 1fr; }
  .home-top .home-stars { flex-direction: row; text-align: left; max-width: none; padding: 12px 18px; }
  .home-top .home-stars-go { display: inline; }
}

/* Die Ziele als Raster gleich grosser Kacheln: Wochenziel, Tages-Aufgabe,
   Klassenziel – zwei nebeneinander, wo Platz ist, sonst eine Spalte. Eine
   einzelne Kachel nimmt die ganze Breite. */
.home-goals { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr)); gap: 12px; align-items: stretch; }
.home-goals > * { margin: 0; min-width: 0; }
.home-goals > :only-child { grid-column: 1 / -1; }
.home-goals .home-goal-head strong { text-wrap: balance; }

/* Guthaben-Zeile auf der Startseite: führt in den Shop. Ruhiger als der grosse
   Lern-Knopf darüber – der bleibt das Ziel der Seite. */
.home-stars {
  display: flex; align-items: center; gap: 14px;
  padding: 14px 18px; border-radius: 16px; text-decoration: none;
  background: var(--menu-bg); border: 1px solid var(--menu-line);
  color: var(--on-dark); transition: background 0.15s ease, transform 0.15s ease;
}
.home-stars:hover { background: var(--menu-hover); transform: translateY(-1px); }
.home-stars-num { font-size: 22px; font-weight: 900; color: var(--star-on-bg); flex: none; }
.home-stars-text { flex: 1 1 auto; font-size: 14px; opacity: 0.88; min-width: 0; }
.home-stars-go { font-size: 20px; opacity: 0.7; flex: none; }

/* Guthaben nach der STAR-Phase – die Zahl, wegen der sich die nächste Serie
   lohnt. Steht unter dem Ergebnis, nicht darüber: Zuerst zählt, was geschafft
   wurde, dann was es eingebracht hat. */
.star-balance { margin: 4px 0 0; font-size: 15px; color: var(--ink); }
.star-balance b { color: var(--star); font-size: 18px; }

/* Sagt, dass gerade nichts im Regal steht – ohne zu klingen, als wäre etwas
   kaputt. Steht auf dem Seitenhintergrund, nicht auf einer Karte. */
.shop-empty {
  margin: 0; padding: 4px 2px; color: var(--on-dark-soft);
  font-size: 15px; line-height: 1.5; text-wrap: balance;
}

/* Avatar-Abschnitt im Shop. Statt Kacheln für Farbverläufe, die man an nichts
   sieht, steht hier die eigene Figur und der Knopf in den Baukasten – dort
   zeigt jeder Klick den Verlauf sofort am eigenen Bild. */
.shop-avatar {
  background: var(--surface); border-radius: 18px; padding: 18px;
  box-shadow: var(--shadow);
}
.shop-avatar .profile-row { margin: 0; }
.shop-avatar-count { margin: 12px 0 0; font-size: 14px; color: var(--ink-soft); }
.shop-avatar-count strong { color: var(--ink); }

/* --------------------------------------------------------------------------
   Namens-Dekorationen aus dem STAR-Shop
   --------------------------------------------------------------------------
   Eine gekaufte Dekoration setzt ihre Klasse auf den Namen des Kindes – auf
   der eigenen Startseite und auf der Anmeldekachel. Jede ist ein ganzes Stück
   aus Schriftart, Farbe und Effekt; einzeln wählbar wären es zu viele Regler
   für zu wenig Wirkung.

   Die Schlüssel müssen zu ShopCatalog::DECOS passen. „deco-schlicht" braucht
   keine Regel: Das ist der Name, wie er immer war.

   Keine Webfonts – die Seite lädt nichts von aussen. Also durchweg Stapel aus
   Schriften, die auf iPad, Windows und Mac vorhanden sind, mit einer
   allgemeinen Familie als letzter Rückfallebene. */

/* Verlaufsschriften teilen sich denselben Trick: Der Verlauf liegt hinter dem
   Text, der Text selbst wird durchsichtig und schneidet ihn aus. */
.deco-gold, .deco-eis, .deco-glut, .deco-regenbogen {
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}

.deco-gold {
  background-image: linear-gradient(180deg, #fff3b0 0%, #f5b301 45%, #b07d00 100%);
  filter: drop-shadow(0 1px 1px rgba(80, 55, 0, 0.35));
}
.deco-eis {
  background-image: linear-gradient(180deg, #ffffff 0%, #a9e6ff 45%, #2f9fd0 100%);
  filter: drop-shadow(0 0 6px rgba(120, 210, 255, 0.75));
}
.deco-glut {
  background-image: linear-gradient(180deg, #ffe066 0%, #ff8c1a 50%, #e02d1b 100%);
  filter: drop-shadow(0 0 7px rgba(255, 120, 20, 0.6));
}
.deco-regenbogen {
  background-image: linear-gradient(100deg, #ff4d4d, #ffa62b, #ffe14d, #45d97a, #38b6ff, #a06bff, #ff5fb0);
}

.deco-neon {
  color: #eafcff;
  text-shadow:
    0 0 4px #7ff0ff,
    0 0 12px #33d6ff,
    0 0 26px #0aa8e0;
}

/* Versetzte Schatten statt echter Tiefe – billig zu zeichnen und auf jedem
   Hintergrund lesbar. */
.deco-3d {
  color: #fff;
  text-shadow: 1px 1px 0 #1f7f99, 2px 2px 0 #176275, 3px 3px 0 #0f4557, 4px 5px 8px rgba(4, 20, 28, 0.45);
  letter-spacing: 0.01em;
}

.deco-schreibschrift {
  font-family: "Snell Roundhand", "Segoe Script", "Brush Script MT", cursive;
  font-weight: 600;
  letter-spacing: 0.02em;
}
.deco-schreibmaschine {
  font-family: "Courier New", ui-monospace, monospace;
  font-weight: 700;
  letter-spacing: -0.02em;
}

/* Titel auf der Anmeldekachel – klein unter dem Namen, damit die Kachel nicht
   auseinanderläuft. Die Dekoration sitzt auf .name selbst. */
.student-shop-title {
  margin-top: 2px; font-size: 9.5px; line-height: 1.25;
  font-family: Karla, "Segoe UI", system-ui, sans-serif;
  font-weight: 800; text-transform: uppercase; letter-spacing: 0.1em;
  color: var(--star-ink);
  display: flex; align-items: center; justify-content: center; gap: 0.3em;
  max-width: 100%;
}
/* Gekürzt wird der Text, nicht das Symbol – deshalb trägt er die Regel und
   nicht der Kasten. */
.student-shop-title .t { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* In der Ladenkarte trägt der Kartenname die Dekoration. Etwas grösser, damit
   Verläufe und Leuchteffekte überhaupt zur Geltung kommen. */
.shop-card-name[class*="deco-"] { font-size: 21px; line-height: 1.25; }

/* --------------------------------------------------------------------------
   Gutscheine
   --------------------------------------------------------------------------
   Das Einzige im Shop, was den Bildschirm verlässt. Entsprechend sieht es
   nicht aus wie eine Karte im Regal, sondern wie ein abreissbarer Coupon: ein
   breiter Teil mit dem, was man bekommt, und ein Abschnitt rechts mit dem
   Code – getrennt durch eine gestrichelte Perforation. */

.coupons { display: flex; flex-direction: column; gap: 10px; }
.coupon {
  display: flex; align-items: stretch; overflow: hidden;
  background: var(--surface); border-radius: 14px; box-shadow: var(--shadow);
  border: 2px solid var(--star);
}
.coupon-main { display: flex; align-items: center; gap: 12px; padding: 12px 14px; flex: 1 1 auto; min-width: 0; }
.coupon-emoji { font-size: 28px; line-height: 1; flex: none; }
.coupon-text { display: flex; flex-direction: column; min-width: 0; }
.coupon-text strong { font-size: 16px; color: var(--ink); }
.coupon-text span { font-size: 13px; color: var(--ink-soft); }
.coupon-owner { margin-top: 2px; font-weight: 700; }

/* Der Abschnitt mit dem Code. Die Perforation ist eine gestrichelte Linie –
   dasselbe Bild wie bei einem Coupon aus Papier. */
.coupon-stub {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px; flex: none; padding: 10px 16px;
  border-left: 2px dashed var(--star); background: #fffaec;
}
.coupon-code {
  font-family: "Courier New", ui-monospace, monospace;
  font-size: 26px; font-weight: 700; letter-spacing: 0.14em;
  color: #8a6d00; line-height: 1;
  /* Der Code wird vorgelesen und abgetippt – die letzte Ziffer darf nicht am
     Rand kleben, sonst liest man den Buchstabenabstand als Leerzeichen. */
  padding-left: 0.14em;
}
.coupon-state { font-size: 11px; font-weight: 800; text-transform: uppercase; color: var(--ink-soft); }

/* Eingelöst: durchgestrichen und ohne Farbe – er hat seinen Zweck erfüllt. */
.coupon.is-used { border-color: #d7e0e6; }
.coupon.is-used .coupon-stub { border-left-color: #d7e0e6; background: var(--surface-soft); }
.coupon.is-used .coupon-code { color: var(--ink-soft); text-decoration: line-through; }
.coupon.is-used .coupon-emoji { filter: grayscale(1); opacity: 0.6; }

.coupon-big { margin: 4px 0 18px; text-align: left; }
.coupon-big .coupon-code { font-size: 34px; }
.coupon-code-inline {
  font-family: "Courier New", ui-monospace, monospace; font-weight: 700;
  letter-spacing: 0.1em; background: #fffaec; color: #8a6d00;
  border-radius: 6px; padding: 2px 7px;
}

/* Das Eingabefeld für den Code: gross und in derselben Schrift wie der Code
   selbst, damit Abtippen und Vergleichen leichtfallen. */
.redeem-form { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.redeem-form .btn { margin-top: 0; }
.redeem-code {
  font-family: "Courier New", ui-monospace, monospace;
  font-size: 30px; font-weight: 700; letter-spacing: 0.2em; text-transform: uppercase;
  width: 6em; padding: 10px 14px; text-align: center;
  border: 2px solid var(--surface-soft); border-radius: 12px; color: var(--ink);
}
.redeem-code:focus { outline: none; border-color: var(--accent); }
.redeem-found { margin-top: 16px; }

/* Sortiment: ein Knopf je Stück, an/aus auf einen Klick. */
.assort-grid { display: grid; gap: 8px; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); }
.assort-item { margin: 0; display: flex; }
.assort-toggle {
  display: flex; align-items: center; gap: 8px; width: 100%; height: 100%;
  padding: 9px 12px; border: 2px solid var(--good); border-radius: 12px;
  background: var(--surface); color: var(--ink);
  font: inherit; font-size: 14px; font-weight: 700; text-align: left; cursor: pointer;
}
.assort-toggle:hover { background: var(--surface-soft); }
.assort-emoji { font-size: 18px; line-height: 1; flex: none; }
.assort-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.assort-state {
  flex: none; font-size: 11px; font-weight: 800; text-transform: uppercase;
  color: var(--good);
}
/* Aus dem Regal genommen: grau und blass, aber lesbar – man muss es ja
   wiederfinden, um es zurückzustellen. */
.assort-toggle.is-off { border-color: #d7e0e6; color: var(--ink-soft); }
.assort-toggle.is-off .assort-state { color: var(--ink-soft); }
.assort-toggle.is-off .assort-emoji { filter: grayscale(1); opacity: 0.6; }

/* Anlegen und Bearbeiten laufen über denselben Überlagerungs-Dialog wie die
   Benutzerliste im Admin (.edit-pop / .edit-dialog) – eigene Regeln braucht es
   dafür keine mehr. */

/* Kategorie-Schalter: vier statt fünfzig. Jeder trägt seinen Namen, was die
   Kategorie tut und wie viele Stücke dahinterstehen – wer eine Kategorie
   abschaltet, soll wissen, wie viel er wegnimmt. */
.assort-toggle { flex-direction: column; align-items: stretch; gap: 4px; padding: 12px 14px; }
.assort-head { display: flex; align-items: center; gap: 8px; }
.assort-head .assort-name { font-size: 15px; }
.assort-hint { font-size: 12px; font-weight: 600; color: var(--ink-soft); line-height: 1.4; }
/* Die Stückzahl sitzt unten am Rand, damit alle Kacheln einer Reihe gleich
   hoch sind, auch wenn der Hinweis mal zwei und mal drei Zeilen braucht. */
.assort-count { margin-top: auto; padding-top: 2px; font-size: 11px; font-weight: 800; color: var(--ink-soft); text-transform: uppercase; }

/* Restzahl auf der Ladenkarte – nur bei begrenzten Artikeln. Zurückhaltend,
   solange es genug gibt; deutlich, wenn nichts mehr da ist. */
.shop-stock {
  margin: 6px 0 0; font-size: 12px; font-weight: 800;
  text-transform: uppercase; letter-spacing: 0.03em; color: var(--star);
}
.shop-stock.is-out { color: var(--ink-soft); }

/* Farbverläufe in der Shop-Verwaltung: Man muss sie sehen, nicht lesen. Ein
   Feld je Figuren-Stil, der den Verlauf kennt – die Töne unterscheiden sich
   zwischen Figuren und Emojis. */
.bg-swatches { display: inline-flex; gap: 4px; vertical-align: middle; margin-right: 10px; }
.bg-swatch {
  display: block; width: 26px; height: 26px; border-radius: 8px;
  box-shadow: inset 0 0 0 1px rgba(6, 30, 40, 0.18);
}
/* Klein, in einer Textzeile (Inhalt einer Mystery-Box bei der Lehrperson). */
.bg-swatches--sm { gap: 3px; margin-right: 6px; }
.bg-swatches--sm .bg-swatch { width: 14px; height: 14px; border-radius: 4px; }

/* Gewinn-Auswahl im Box-Editor: eigenes Menü statt <select>, weil ein natives
   Menü keine Farbfelder zeigen kann. Das <select> bleibt versteckt als Träger
   des Werts (und für Seiten ohne JavaScript). Das Menü liegt fixiert über
   der Seite, damit der scrollende Dialog es nicht abschneidet. */
.prize-pick { display: inline-block; vertical-align: middle; max-width: 100%; }
.prize-pick-btn {
  display: inline-flex; align-items: center; gap: 8px; max-width: 280px;
  font: inherit; font-size: 13px; padding: 6px 10px; cursor: pointer;
  border: 1px solid var(--line); border-radius: 10px; background: var(--surface); color: var(--ink);
}
.prize-pick-btn:hover { background: var(--surface-soft); }
.prize-pick-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.prize-pick-btn .bg-swatches { margin-right: 0; }
.prize-pick-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.prize-pick-name.is-empty { color: var(--ink-soft); }
.prize-pick-caret { color: var(--ink-soft); font-size: 11px; margin-left: 2px; }
.prize-pick-emoji { width: 22px; text-align: center; flex: none; }
.prize-pick-menu {
  position: fixed; z-index: 70; min-width: 240px; max-width: min(360px, calc(100vw - 20px));
  max-height: 320px; overflow: auto; padding: 6px;
  background: var(--surface); color: var(--ink); border: 1px solid var(--line); border-radius: 12px; box-shadow: var(--shadow);
}
.prize-pick-group {
  padding: 8px 8px 2px; font-size: 11px; font-weight: 800; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-soft);
}
.prize-pick-opt {
  display: flex; align-items: center; gap: 8px; width: 100%; text-align: left;
  font: inherit; font-size: 13px; padding: 6px 8px; border: 0; border-radius: 8px; background: none; color: var(--ink); cursor: pointer;
}
.prize-pick-opt:hover, .prize-pick-opt:focus-visible { background: var(--press); outline: none; }
.prize-pick-opt.is-current { font-weight: 800; }
.prize-pick-opt .bg-swatches { margin-right: 0; }
.shop-admin-table code { vertical-align: middle; }

/* „Standard" in der Shop-Verwaltung: die Kennzeichnung, nicht der Knopf. */
.shop-default-flag {
  display: inline-block; font-size: 11px; font-weight: 800;
  padding: 3px 9px; border-radius: 999px;
  background: var(--surface-soft); color: var(--accent-strong); white-space: nowrap;
}
/* Im Laden trägt der Standard eine leisere Marke als das aktive Stück – er ist
   die Rückfallebene, keine Auszeichnung. */
.shop-flag.is-default { color: var(--ink-soft); }

/* ---------------------------------------------------------------------------
   Schulen: Lizenzbalken und Filtermarken
   --------------------------------------------------------------------------- */

/* Wie voll die Lizenzen einer Schule sind. Der Balken zeigt den Anteil, die
   Zahl darüber die genauen Werte – überbelegt bleibt er bei 100 %, weil voller
   als voll nichts mehr aussagt. */
.lic-bar {
  height: 8px; border-radius: 999px; overflow: hidden;
  background: var(--surface-soft); margin: 0;
}
.lic-fill { display: block; height: 100%; border-radius: 999px; background: #1f9d5b; }
.lic-fill.warn { background: #d99a00; }
.lic-fill.bad { background: var(--bad); }

/* Die Schulmarken über der Klassenliste. Dieselbe Optik wie die Zeilenaktionen,
   nur als Zeile über der Tabelle. */
.chip-filter {
  display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 14px;
}

/* ==========================================================================
   Der Riesenkrake – ein eigenes Spiel neben dem Training.

   Es benutzt bewusst dieselben Bausteine wie die Übung (.game, .question,
   .keypad), damit ein Kind sich nicht neu zurechtfinden muss. Neu sind nur der
   gemeinsame Lebensbalken, die Zeit pro Rechnung und die Angriffe.

   Signalfarben kommen aus den vorhandenen Tokens (--good/--bad/--star): Rot
   heisst falsch, in jeder Welt. Nur so bleibt die Rückmeldung lesbar, egal
   welches Theme das Kind gekauft hat.
   ========================================================================== */

/* --- Bühne: Karte, Wartehalle und der Octopus darüber ---------------------- */
/* Eigener Bezugsrahmen statt wie beim Üben direkt in .game: Über der Karte
   liegen hier Kopfzeile, Lebensbalken UND die Angriffsanzeige, die kommt und
   geht. Ein fester Abstand von oben würde bei jedem Angriff verrutschen. */
.boss-stage { position: relative; }
/* Höher als beim Üben: Dort sitzt der Octopus an der Kante einer Karte, die
   gleich oben anfängt. Hier liegen Kopfzeile und Lebensbalken darüber, und auf
   gleicher Höhe wirkte er angeklebt statt darüber schwebend. */
.game.boss .mascot-stage { top: clamp(-58px, -4.2vw, -30px); }
@media (max-width: 560px) {
  .game.boss .mascot-stage { top: clamp(-46px, -5vw, -24px); }
}

/* --- Kopfzeile ----------------------------------------------------------- */
.boss-arm b { font-weight: 800; }
.boss-arm .hud-lb { opacity: .7; margin-left: .35em; }

/* --- Der gemeinsame Lebensbalken: ein Balken, zwei Enden ------------------ */
/* Links leer heisst „Stufe besiegt", rechts voll heisst „der Krake hat
   gewonnen". Die Beschriftung darunter ist kein Schmuck, sie IST die
   Spielregel – ohne sie wüsste ein Kind nicht, wofür es rechnet. */
.boss-bar-wrap { margin: 10px 0 14px; }
.boss-bar {
  position: relative;
  height: 18px;
  border-radius: 999px;
  background: var(--track-on-bg);
  overflow: hidden;
}
/* Die rote Zone kündigt das Ende an, BEVOR es da ist. Ihre Breite muss zu
   WARN_ANTEIL in boss.js und beamer.js passen (0.85 → 15 %). */
.boss-bar-zone, .beamer-bar-zone {
  position: absolute;
  right: 0; top: 0; bottom: 0;
  width: 15%;
  background: repeating-linear-gradient(
    -45deg, rgba(239, 91, 87, .30) 0 6px, rgba(239, 91, 87, .12) 6px 12px);
  pointer-events: none;
}
/* Die Startmarke: hier stand der Balken, als die Stufe begann. */
.boss-bar-mark, .beamer-bar-mark {
  position: absolute;
  top: 0; bottom: 0;
  width: 2px;
  margin-left: -1px;
  background: var(--on-dark-faint);
  pointer-events: none;
  z-index: 2;
}
.boss-bar-ends, .beamer-bar-ends {
  display: flex;
  justify-content: space-between;
  margin: 4px 0 0;
  color: var(--on-dark-soft);
}
.boss-bar-ends { font-size: 11px; }
.boss-bar-fill {
  height: 100%;
  width: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--bad), #ff8a5c);
  transition: width .35s ease, background-color .2s ease;
}
/* Wächst der Balken, MUSS man das sehen: Sonst wirkt ein Rückschritt wie ein
   Anzeigefehler, und niemand versteht, warum Raten dem Kraken hilft. */
.boss-bar-fill.is-healing,
.beamer-bar-fill.is-healing {
  background: linear-gradient(90deg, #7ad0a0, var(--good));
  box-shadow: 0 0 14px rgba(47, 191, 113, .7);
}

/* --- Zeit pro Rechnung ---------------------------------------------------- */
.boss-timer {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 6px;
  background: var(--track-on-card);
  /* Über der Tinte: Wer seine verbleibende Zeit nicht sieht, ist verloren.
     Die Kleckse lassen den Balken zwar ohnehin frei (INK_BOTTOM_KEEP), aber
     darauf soll sich die Lesbarkeit nicht allein verlassen. */
  z-index: 4;
}
.boss-timer-fill {
  height: 100%;
  width: 100%;
  background: var(--accent);
}
/* Kein Blinken, kein Ton: Wer knapp dran ist, sieht die Farbe. Ein Alarm würde
   genau die Kinder aus dem Konzept bringen, die ohnehin schon hetzen. */
.boss-timer-fill.is-low { background: var(--bad); }

.game.boss .question { position: relative; overflow: hidden; }
.game.boss .question.right { box-shadow: 0 0 0 3px var(--good) inset; }
.game.boss .question.wrong { box-shadow: 0 0 0 3px var(--bad) inset; }
/* Richtig, aber zu spät – ein eigener Ton. Ohne ihn hielte das Kind die
   Anzeige für kaputt: „Ich hatte es doch!" */
.game.boss .question.too-slow { box-shadow: 0 0 0 3px var(--star) inset; }

/* Der Balken nähert sich dem oberen Ende: Gleich gewinnt der Krake. Das muss
   man sehen, BEVOR es passiert – am Anschlag ist es vorbei, dort käme die
   Warnung zu spät. */
.boss-bar-fill.is-danger,
.beamer-bar-fill.is-danger {
  background: linear-gradient(90deg, #c0392b, var(--bad));
  animation: boss-danger 1.1s ease-in-out infinite;
}
/* Ganz oben: der letzte Augenblick, ohne Pulsieren – jetzt ist es entschieden. */
.boss-bar-fill.is-full,
.beamer-bar-fill.is-full {
  background: #c0392b;
  animation: none;
}
@keyframes boss-danger {
  0%, 100% { box-shadow: 0 0 0 0 rgba(239, 91, 87, .0); }
  50% { box-shadow: 0 0 16px 3px rgba(239, 91, 87, .75); }
}

/* --- Die Kampfuhr und ihr Verlust ----------------------------------------- */
/* Die Kampfuhr und der Zeitklau.

   Der Krake frisst Zeit – und das muss AN DER UHR passieren, nicht in einem
   Text daneben. Eine Zeile „−20 Sekunden" liest im Eifer des Gefechts niemand;
   eine Uhr, die rot aufspringt und eine Zahl nach oben wegwirft, sieht man auch
   aus dem Augenwinkel.

   Der Zeitklau ist jetzt ein angesagter Angriff wie Tinte oder Zorn. Vorher
   hing er heimlich an „Balken voll" – dem Zustand, der heute das Spielende
   bedeutet. */
.boss-clock { position: relative; font-variant-numeric: tabular-nums; }
.boss-clock.is-low b { color: var(--bad); }
.boss-clock.is-hit {
  animation: boss-clock-hit .6s cubic-bezier(.34, 1.56, .64, 1);
  background: var(--bad);
  color: #fff;
}
@keyframes boss-clock-hit {
  0% { transform: scale(1); }
  35% { transform: scale(1.45); }
  100% { transform: scale(1); }
}

/* Die weggeworfene Zahl. Sie steigt auf und verblasst – die Bewegung nach oben
   sagt „weg", ohne dass jemand ein Wort lesen muss. */
.boss-timeloss, .beamer-timeloss {
  position: absolute;
  left: 50%;
  top: 0;
  transform: translate(-50%, 0);
  font-weight: 900;
  color: var(--bad);
  text-shadow: 0 2px 10px rgba(0, 0, 0, .35);
  pointer-events: none;
  animation: boss-timeloss 1.4s ease-out forwards;
}
.boss-timeloss { font-size: 1.5rem; }
.beamer-timeloss { font-size: clamp(2rem, 5vw, 4rem); }
@keyframes boss-timeloss {
  0% { opacity: 0; transform: translate(-50%, 0) scale(.7); }
  20% { opacity: 1; transform: translate(-50%, -40%) scale(1.15); }
  100% { opacity: 0; transform: translate(-50%, -220%) scale(1); }
}
@keyframes boss-timeloss-calm {
  0%, 70% { opacity: 1; transform: translate(-50%, -60%); }
  100% { opacity: 0; transform: translate(-50%, -60%); }
}

/* Vorn an der Wand trägt die Uhr die Spannung – sie ist keine Fussnote. */
.beamer-clock {
  position: relative;
  font-size: clamp(2.2rem, 7vw, 5.5rem);
  font-weight: 900;
  line-height: 1;
  color: var(--on-dark);
  font-variant-numeric: tabular-nums;
  padding: .05em .25em;
  border-radius: 12px;
  transition: color .2s ease, background-color .2s ease;
}
.beamer-clock.is-low { color: var(--bad); }
.beamer-clock.is-hit {
  animation: boss-clock-hit .7s cubic-bezier(.34, 1.56, .64, 1);
  background: var(--bad);
  color: #fff;
}
.beamer-foot { align-items: baseline; }

@media (prefers-reduced-motion: reduce) {
  .boss-bar-fill.is-danger,
  .beamer-bar-fill.is-danger { animation: none; }
  .boss-clock.is-hit, .beamer-clock.is-hit { animation: none; }
  .boss-timeloss, .beamer-timeloss { animation: boss-timeloss-calm 1.4s ease-out forwards; }
}

/* --- Angriffe ------------------------------------------------------------- */
/* Der Streifen steht IMMER im Aufbau, auch wenn gerade kein Angriff läuft –
   sichtbar wird er über `is-on`, nicht über `display`. Sonst schob er sich bei
   jedem Angriff zwischen Lebensbalken und Karte, alles darunter rutschte um
   seine Höhe nach unten und beim Verschwinden wieder zurück. Genau in dem
   Moment, in dem das Kind auf die Rechnung schaut, sprang sie ihm weg.
   Dieselbe Überlegung wie bei der festen Höhe der Aufgabenkarte. */
.boss-attack {
  text-align: center;
  font-weight: 800;
  color: var(--on-dark);
  background: rgba(239, 91, 87, .25);
  border: 1px solid rgba(239, 91, 87, .5);
  border-radius: 999px;
  padding: 6px 14px;
  margin-bottom: 10px;
  visibility: hidden;
}
.boss-attack.is-on { visibility: visible; }

/* Tintenkleckse über der Rechnung. Sie erscheinen erst nach einer Sekunde –
   die Aufgabe war also lesbar – und decken nie das Ganze ab. */
.ink-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity .45s ease;
  /* Muss ausdrücklich über die Rechnung.
     Ohne z-index lag das Rechenzeichen ÜBER der Tinte, die Ziffern darunter –
     ein verblüffendes Bild mit einer schlichten Ursache: `.term-op` trägt ein
     transform (die Zeichen hängen optisch zu tief und werden angehoben). Ein
     transform erzeugt einen Stapelkontext, das Zeichen malt damit in derselben
     Ebene wie diese absolut gesetzte Schicht – und weil es später im Dokument
     steht, darüber. */
  z-index: 3;
}
.ink-layer.is-on { opacity: 1; }
/* „Weniger Bewegung": Der Klecks liegt einfach da statt aufzublenden. Die
   Sekunde davor bleibt aber – sie ist die Spielregel, nicht die Animation
   (siehe boss-fx.js). */
.ink-layer.is-instant { transition: none; }
/* Die Kleckse zielen auf die Rechnung (siehe boss-fx.js) und müssen darum
   auch wirklich decken: voll deckend, keine Transparenz. Der erste Entwurf war
   klein und halb durchsichtig – man las die Ziffer mühelos hindurch, und der
   Angriff war eine Behauptung.

   Die Form kommt als SVG-Pfad aus boss-fx.js. Ein border-radius kann sie nicht
   liefern: Er ergibt immer einen weichen Fleck mit vier gleichmässigen Ecken,
   und der sah nach Punkt aus, nicht nach Tinte. */
.ink-blot { position: absolute; }
.ink-blot svg {
  display: block;
  width: 100%;
  height: 100%;
  /* Der Schatten folgt der Pfadkontur, nicht dem Kasten – ein box-shadow
     zeichnete das Rechteck nach und verriet die Konstruktion. */
  filter: drop-shadow(0 0 5px rgba(22, 48, 61, .6));
  overflow: visible;
}
.ink-blot svg path, .ink-blot svg ellipse { fill: #16303d; }

/* Blindflug: Das Wasser trübt sich, die eigene Zahl steht verschwommen da.
   Die Unschärfe hängt an der Schriftgrösse (em), damit sie auf dem Handy
   gleich stark wirkt wie auf dem Tablet.

   Nicht durch Punkte ersetzen – das sah nach Passwortfeld aus. Und nicht ganz
   unlesbar machen: Das Kind soll seine Zahl erahnen, nur nicht mehr sicher
   ablesen. Die Einfärbung der Karte sagt dazu, WARUM man schlecht liest –
   sonst wirkt es wie ein Fehler. */
body.boss-blind .answer {
  letter-spacing: .18em;
  color: var(--ink-soft);
  filter: blur(.14em);
  opacity: .8;
  transition: filter .35s ease;
}
body.boss-blind .question { background: linear-gradient(180deg, var(--surface), #dfeaf0); }

/* Regeneration und Zorn wirken auf den Lebensbalken, nicht auf die Rechnung –
   sie färben darum den Balken ein: Das Kind soll sehen, warum er plötzlich
   wächst. */
body.boss-regen .boss-bar { box-shadow: 0 0 0 2px rgba(47, 191, 113, .6); }

/* Der Zorn ist der härteste Angriff des Spiels: Jeder Fehlversuch lässt den
   Kraken um 3 statt 2 nachwachsen, und das bei der ganzen Klasse gleichzeitig.
   Ein 2px-Rand am Balken war dafür zu leise – man übersah ihn und verstand
   hinterher nicht, warum der Balken davongelaufen war. Also pocht die ganze
   Karte rot, solange er läuft.

   Die Rechnung bleibt dabei lesbar: nur ein Rand und ein Hauch Rot von den
   Kanten her, nichts über der Mitte. Ein Angriff darf erschweren, nie
   verdecken – dafür ist die Tinte da, und die trifft gezielt. */
body.boss-rage .boss-bar {
  box-shadow: 0 0 0 2px var(--bad);
  animation: boss-rage 1.1s ease-in-out infinite;
}
body.boss-rage .game.boss .question::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  /* Unter dem Zeitbalken (z-index 4) und unter der Tinte (3): Beide müssen
     scharf bleiben, der Zorn ist nur die Umgebung. */
  z-index: 2;
  box-shadow: inset 0 0 0 3px var(--bad), inset 0 0 44px rgba(239, 91, 87, .28);
  animation: boss-rage 1.1s ease-in-out infinite;
}
/* Rückmeldung geht vor: Richtig, falsch und „zu spät" zeichnen ihren Rand auf
   dieselbe Kante. Im Zorn bliebe sonst alles rot, und ein Kind sähe nicht mehr,
   ob seine Antwort gestimmt hat. Der Rand gehört dann der Rückmeldung, das
   Glimmen bleibt. */
body.boss-rage .game.boss .question.right::after,
body.boss-rage .game.boss .question.wrong::after,
body.boss-rage .game.boss .question.too-slow::after {
  box-shadow: inset 0 0 44px rgba(239, 91, 87, .28);
}
/* Die Ansage wird zur roten Fahne statt zur blassen Pille. */
body.boss-rage .boss-attack {
  background: var(--bad);
  border-color: var(--bad);
  color: #fff;
  animation: boss-rage 1.1s ease-in-out infinite;
}
@keyframes boss-rage {
  0%, 100% { opacity: 1; }
  50% { opacity: .45; }
}
/* Bei „weniger Bewegung" bleibt das Rot, nur ohne Pochen: Der Angriff ist Teil
   des Spiels, nicht Zierde – wer ihn nicht sieht, versteht den Balken nicht. */
@media (prefers-reduced-motion: reduce) {
  body.boss-rage .boss-bar,
  body.boss-rage .game.boss .question::after,
  body.boss-rage .boss-attack { animation: none; }
}

/* --- Kampfmodus: die Seite selbst wird zum Spiel --------------------------
   Beim Üben sieht ein Kind seine Welt (Ozean, Vulkan, …) und das Menü. Im
   Krakenkampf wechselt die ganze Seite in die violette Tiefe des Kraken, mit
   rotem Rand und aufsteigenden Blasen – und das Menü ist weg. So merkt man
   den Unterschied, bevor man die erste Rechnung liest.

   Die Klasse sitzt am <body> (student/boss.php) und stellt die Marken der Welt
   um. body.boss-mode schlägt jede .theme-*-Klasse, weil ein Element mehr in
   der Auswahl steht – deshalb gilt der Kampf in allen Welten gleich, auch in
   den hellen: Die Schrift auf dem Hintergrund wird darum hier ausdrücklich
   wieder hell gesetzt. */
body.boss-mode {
  --bg-top: #4b2a80;
  --bg: #2a1652;
  --bg-deep: #140a2a;
  --page: radial-gradient(120% 120% at 50% 0%, var(--bg-top) 0%, var(--bg) 45%, var(--bg-deep) 100%);
  --on-dark: #f2eaff;
  --on-dark-soft: rgba(242, 234, 255, 0.82);
  --on-dark-faint: rgba(242, 234, 255, 0.66);
  --star-on-bg: var(--star);
  --track-on-bg: rgba(255, 255, 255, 0.18);
  --shell-bg: linear-gradient(180deg, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.36));
  --shell-line: rgba(200, 150, 255, 0.26);
  --shell-shadow: 0 30px 90px rgba(0, 0, 0, 0.6), 0 0 80px rgba(150, 70, 255, 0.16), inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
/* Die Ferne: Licht von oben in Violett, schwarze Tiefe unten – und an beiden
   Rändern ein roter Schein, der langsam atmet. Jede Eigenschaft, die eine
   Welt auf ihren Ebenen setzt, wird hier überschrieben, sonst schiene die
   Welt durch (Lava, Sterne, Blätter). */
body.boss-mode::before {
  --fx-drift: 0px;
  left: -10vw;
  background-image:
    radial-gradient(70% 45% at 50% -10%, rgba(210, 140, 255, 0.24), transparent 70%),
    radial-gradient(42% 60% at -6% 55%, rgba(255, 70, 100, 0.16), transparent 70%),
    radial-gradient(42% 60% at 106% 55%, rgba(255, 70, 100, 0.16), transparent 70%),
    radial-gradient(90% 55% at 50% 112%, rgba(0, 0, 0, 0.6), transparent 72%);
  background-size: auto;
  background-position: 0 0;
  background-repeat: no-repeat;
  animation: boss-tiefe 9s ease-in-out infinite alternate;
}
/* Das Nahe: kleine Blasen, die stetig steigen – genau eine Kachel weit, dann
   steht das Muster wieder deckungsgleich (siehe fx-steigen). */
body.boss-mode::after {
  --fx-tile: 360px;
  --fx-x: 0px;
  left: -10vw;
  background-image:
    radial-gradient(2.5px 2.5px at 12% 88%, rgba(255, 255, 255, 0.5), transparent 70%),
    radial-gradient(1.5px 1.5px at 30% 40%, rgba(255, 255, 255, 0.45), transparent 70%),
    radial-gradient(3px 3px at 52% 72%, rgba(230, 200, 255, 0.4), transparent 70%),
    radial-gradient(2px 2px at 70% 18%, rgba(255, 255, 255, 0.45), transparent 70%),
    radial-gradient(1.5px 1.5px at 84% 56%, rgba(255, 255, 255, 0.4), transparent 70%),
    radial-gradient(2.5px 2.5px at 94% 92%, rgba(230, 200, 255, 0.35), transparent 70%);
  background-size: var(--fx-tile) var(--fx-tile);
  background-position: 0 0;
  background-repeat: repeat;
  animation: fx-steigen 16s linear infinite;
}
@keyframes boss-tiefe {
  from { opacity: 0.7; }
  to   { opacity: 1; }
}

/* Die Kopfzeile, die das Menü ersetzt: links der Weg zurück, mittig die
   Ansage. Ein Raster mit drei Spalten, damit die Ansage wirklich in der
   Mitte steht und nicht „zwischen" den beiden anderen. */
.boss-top {
  display: flex;
  justify-content: center;
  width: min(720px, 100%);
  margin: 0 auto clamp(12px, 3vw, 22px);
}
/* „Verlassen" sitzt in der Ecke des Rahmens, nicht in der Zeile der Ansage:
   Ein Ausgang gehört an den Rand, wo man ihn sucht – und nicht dorthin, wo
   der Blick beim Spielen hängt. */
body.boss-mode .shell { position: relative; }
.boss-leave {
  position: absolute;
  top: 12px;
  left: 12px;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  padding: 8px 13px;
  border-radius: 999px;
  font-size: 14px;
  font-weight: 700;
  text-decoration: none;
  color: var(--on-dark-soft);
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.12);
  transition: background .15s, color .15s;
}
.boss-leave:hover { background: rgba(255, 255, 255, 0.16); color: var(--on-dark); }
.boss-title {
  display: inline-flex;
  align-items: center;
  gap: .5em;
  padding: 7px 16px;
  border-radius: 999px;
  font-family: var(--font-display);
  font-size: 13px;
  font-weight: 900;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--on-dark);
  background: linear-gradient(90deg, rgba(255, 80, 110, 0.38), rgba(160, 80, 255, 0.38));
  border: 1px solid rgba(255, 255, 255, 0.16);
  box-shadow: 0 0 22px rgba(255, 80, 110, 0.18);
}
/* Das Auge: pulsiert leise, wie ein Warnlicht unter Wasser. */
.boss-title::before {
  content: "";
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--bad);
  box-shadow: 0 0 10px var(--bad);
  animation: boss-puls 1.6s ease-in-out infinite;
}
@keyframes boss-puls {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: .55; transform: scale(.8); }
}
@media (prefers-reduced-motion: reduce) {
  .boss-title::before { animation: none; }
}

/* --- Zwischenkarte und Ende ---------------------------------------------- */
/* Beide liegen als Schleier über dem ganzen Spiel und tragen dieselbe Karte
   (.boss-card): dunkel wie die Tiefe, nicht weiss wie ein Formular. Ein Kampf
   endet nicht mit einem Dialogfenster. Die Stimmung – gewonnen, verloren,
   entkommen, Stufe gefallen, Krake zurück – sitzt in EINER Klasse auf der
   Karte und steuert die Farbe des Scheins (--boss-glow) und die Bewegung
   des Kraken. */
.boss-banner, .boss-end {
  position: fixed;
  inset: 0;
  display: grid;
  /* Eine feste Spur, nicht `auto`: Sonst löst sich `width: 100%` der Karte
     gegen ihre eigene Wunschbreite auf, und sie ragt auf dem Handy rechts
     aus dem Bild. */
  grid-template-columns: minmax(0, 30rem);
  justify-content: center;
  align-items: center;
  padding: 16px;
  background: radial-gradient(120% 120% at 50% 30%,
    color-mix(in srgb, var(--bg-top) 78%, transparent),
    color-mix(in srgb, var(--bg-deep) 92%, transparent));
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  z-index: 60;
}
.boss-card {
  --boss-glow: var(--accent);
  position: relative;
  overflow: hidden;
  box-sizing: border-box;
  width: 100%;
  padding: 30px 22px 26px;
  border-radius: 28px;
  text-align: center;
  color: var(--on-dark);
  background:
    linear-gradient(170deg, color-mix(in srgb, var(--bg-top) 86%, #fff 14%), var(--bg) 48%, var(--bg-deep));
  border: 1px solid rgba(255, 255, 255, .16);
  box-shadow:
    0 30px 80px rgba(0, 0, 0, .55),
    0 0 0 1px rgba(0, 0, 0, .25),
    inset 0 1px 0 rgba(255, 255, 255, .18);
  animation: boss-card-in .5s cubic-bezier(.2, .9, .3, 1.15) both;
}
/* Der Schein: ein weicher Fleck oben hinter dem Kraken, in der Farbe der
   Stimmung. Er atmet leicht, damit die Karte nicht wie ein Standbild wirkt. */
.boss-card::before {
  content: "";
  position: absolute;
  inset: -30% -10% auto;
  height: 85%;
  background: radial-gradient(closest-side, color-mix(in srgb, var(--boss-glow) 70%, transparent), transparent 72%);
  opacity: .95;
  pointer-events: none;
  animation: boss-glow-breathe 3.2s ease-in-out infinite;
}
.boss-card > * { position: relative; }

/* Der Krake der Stufe als Bild (boss.js: krakeImg). Er ist der Held der
   Karte und darf gross sein – die Schlagzeile steht darunter, nicht daneben. */
.boss-card-octo {
  display: block;
  width: clamp(150px, 46vw, 210px);
  height: auto;
  margin: -8px auto 2px;
  filter: drop-shadow(0 16px 24px rgba(0, 0, 0, .5));
  user-select: none;
  -webkit-user-drag: none;
}
.boss-card h2 {
  margin: 0 0 .2em;
  font-size: clamp(1.7rem, 7vw, 2.3rem);
  font-weight: 900;
  line-height: 1.1;
  letter-spacing: -.01em;
  text-shadow: 0 3px 18px rgba(0, 0, 0, .35);
}
.boss-card p { margin: 0 0 .45em; color: var(--on-dark-soft); font-size: 1.05rem; line-height: 1.4; }
.boss-card-sub { font-size: 1.05rem; }

/* Stimmungen */
.boss-card.is-won   { --boss-glow: var(--star); }
.boss-card.is-krake { --boss-glow: var(--bad); }
.boss-card.is-zeit  { --boss-glow: var(--accent); }
.boss-card.is-abbruch { --boss-glow: var(--on-dark-faint); }
.boss-card.is-fall  { --boss-glow: var(--good); }
.boss-card.is-rise  { --boss-glow: var(--bad); }

/* Besiegt: der Krake kippt zur Seite und sackt ab, entfärbt. Die Entfärbung
   steht fest und läuft NICHT in den Keyframes mit: Ein `filter` in einer
   Animation neben `drop-shadow` liess Chrome das Bild gar nicht mehr malen. */
.boss-card.is-won .boss-card-octo {
  filter: drop-shadow(0 10px 14px rgba(0, 0, 0, .5)) grayscale(.55) brightness(.85);
  animation: boss-octo-ko 1.4s cubic-bezier(.3, 1.2, .5, 1) .15s both;
}
/* Gewonnen hat er: gross, nah und immer wieder ein Ruck nach vorn. */
.boss-card.is-krake .boss-card-octo,
.boss-card.is-rise .boss-card-octo { animation: boss-octo-loom 1.6s ease-in-out .35s infinite; }
/* Entkommen und Stufe gefallen: er sinkt aus dem Bild. */
.boss-card.is-zeit .boss-card-octo,
.boss-card.is-fall .boss-card-octo { animation: boss-octo-sink 3.6s ease-in .2s both; }
/* Kommt zurück: steigt von unten auf – gleich nach dem Klassenwechsel –
   und drängt dann wie der Sieger nach vorn. Beides in EINER Animation:
   Zwei nacheinander, die beide `transform` setzen, überschreiben einander. */
.boss-card.is-rise .boss-card-octo { animation: boss-octo-rise-loom 2.6s ease-in-out infinite; }
.boss-card.is-abbruch .boss-card-octo { opacity: .55; filter: grayscale(.4) drop-shadow(0 14px 22px rgba(0, 0, 0, .45)); }

/* Die Trefferzahl ist das Ergebnis des Kindes – gross, golden, im eigenen
   Schild, auch wenn der Krake gewonnen hat. */
.boss-end-score {
  display: inline-flex;
  align-items: center;
  gap: .4em;
  margin: 14px auto 4px;
  padding: 8px 24px 10px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .1);
  border: 1px solid rgba(255, 255, 255, .16);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .12);
  animation: boss-card-in .5s cubic-bezier(.2, .9, .3, 1.2) .25s both;
}
.boss-end-score b {
  font-size: clamp(2.2rem, 10vw, 3rem);
  font-weight: 900;
  line-height: 1;
  color: var(--star-on-bg);
  font-variant-numeric: tabular-nums;
  text-shadow: 0 0 22px color-mix(in srgb, var(--star) 55%, transparent);
}
.boss-end-score span {
  font-size: .95rem;
  font-weight: 800;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--on-dark-soft);
}
/* Was das Kind mitnimmt: Sterne gross und golden wie beim Duell, der Preis
   darunter. Eine Zeile «warum nichts» in gedämpftem Ton, wenn die Schwelle
   nicht erreicht war – sonst sucht das Kind seine Sterne. */
.boss-end-reward { margin-top: 6px; }
.boss-end-reward:empty { display: none; }
/* Eine gewonnene Kiste steht anstelle des Kraken auf der dunklen Karte –
   Name und Hinweis der Kiste sind für helle Seiten gefärbt, hier hell. */
.boss-card .mbox-stage { margin: 0 0 4px; }
.boss-card .mbox { --mbox-size: 200px; }
.boss-card .mbox-lead { color: var(--on-dark) !important; font-weight: 800; }
.boss-card .mbox-name { color: var(--on-dark); }
.boss-card .mbox-hint { color: var(--on-dark-soft); }
.boss-end-stars {
  margin: 0 0 2px !important;
  font-size: clamp(1.4rem, 6.5vw, 2rem) !important;
  font-weight: 900;
  line-height: 1.15 !important;
  color: var(--star-on-bg) !important;
  animation: boss-card-in .5s cubic-bezier(.2, .9, .3, 1.2) .35s both;
}
.boss-end-stars.is-item { font-size: clamp(1.1rem, 4.5vw, 1.4rem) !important; }
.boss-end-note { font-size: .95rem !important; color: var(--on-dark-soft) !important; }
.boss-end-code {
  display: inline-block;
  padding: 0 .4em;
  border-radius: 6px;
  background: rgba(255, 255, 255, .14);
  color: var(--on-dark);
  letter-spacing: .12em;
  font-variant-numeric: tabular-nums;
}
.boss-end-btn {
  margin-top: 20px;
  padding: 17px 20px;
  font-size: 19px;
  font-weight: 800;
  border-radius: 18px;
  background: linear-gradient(180deg, var(--accent-lift), var(--accent));
  box-shadow: 0 10px 28px color-mix(in srgb, var(--accent) 45%, transparent), inset 0 1px 0 rgba(255, 255, 255, .35);
  animation: boss-card-in .5s cubic-bezier(.2, .9, .3, 1.2) .4s both;
}
.boss-end-btn:hover { background: linear-gradient(180deg, var(--accent), var(--accent-strong)); }

/* Zwischenkarte: Stufe x von y als kleine Zeile über der Schlagzeile. */
.boss-banner-kicker {
  display: inline-block;
  margin: 0 0 8px !important;
  padding: 4px 12px;
  border-radius: 999px;
  font-size: .8rem !important;
  font-weight: 800;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--on-dark) !important;
  background: color-mix(in srgb, var(--boss-glow) 35%, transparent);
  border: 1px solid color-mix(in srgb, var(--boss-glow) 55%, transparent);
  transition: background .4s, border-color .4s;
}
/* Der Name des neuen Arms trägt die Karte – er ist die eigentliche Nachricht. */
.boss-banner-arm { font-size: 1.4rem !important; font-weight: 800; color: var(--on-dark) !important; }
.boss-banner-rule { font-size: 1.05rem; min-height: 1.4em; }
/* Der Stern (und der Preis) auf der Zwischenkarte: eine Zeile je Nachricht.
   Golden, wenn er sicher ist; leise, wenn noch Treffer fehlen. */
.boss-banner-star { display: grid; gap: 2px; min-height: 1.4em; }
.boss-banner-star:empty { display: none; }
.boss-star-got { font-weight: 800; color: var(--star-on-bg); }
.boss-prize-got { font-size: 1.05rem; }
.boss-star-miss { font-size: .95rem; color: var(--on-dark-soft); }
/* Der kleine Countdown darunter: Er sagt, dass es weitergeht, ohne die
   Nachricht zu überschreien. Deshalb klein und leise – die Leiste am Fuss
   der Karte läuft stetig mit. */
.boss-banner-count {
  margin-top: 1em;
  color: var(--on-dark-soft);
  font-size: .95rem;
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: .4em;
}
.boss-banner-count b {
  font-size: 1.6rem;
  font-weight: 900;
  color: var(--on-dark);
  font-variant-numeric: tabular-nums;
  min-width: 1.2ch;
}
.boss-banner-bar {
  position: absolute !important;
  left: 0; right: 0; bottom: 0;
  height: 6px;
  background: rgba(255, 255, 255, .1);
}
.boss-banner-bar i {
  display: block;
  height: 100%;
  transform-origin: left center;
  background: linear-gradient(90deg, var(--boss-glow), color-mix(in srgb, var(--boss-glow) 60%, #fff));
  box-shadow: 0 0 12px var(--boss-glow);
  transition: background .4s;
}
/* Nicht anklickbar: Die Karte geht von selbst weg, und ein Fingertipp
   daneben soll nicht aus Versehen eine Antwort auslösen. */
.boss-banner { pointer-events: none; }

@keyframes boss-card-in {
  0% { transform: translateY(26px) scale(.9); opacity: 0; }
  100% { transform: translateY(0) scale(1); opacity: 1; }
}
@keyframes boss-glow-breathe {
  0%, 100% { opacity: .75; transform: scale(1); }
  50% { opacity: 1; transform: scale(1.08); }
}
@keyframes boss-octo-ko {
  0% { transform: rotate(0) translateY(-24px) scale(1.05); opacity: 0; }
  45% { transform: rotate(-16deg) translateY(4px) scale(1); opacity: 1; }
  70% { transform: rotate(-9deg) translateY(10px) scale(.97); }
  100% { transform: rotate(-12deg) translateY(12px) scale(.96); opacity: 1; }
}
@keyframes boss-octo-loom {
  0%, 100% { transform: scale(1) translateY(0) rotate(-2deg); }
  50% { transform: scale(1.12) translateY(-6px) rotate(3deg); }
}
@keyframes boss-octo-sink {
  0% { transform: translateY(0) rotate(0); opacity: 1; }
  100% { transform: translateY(58px) rotate(-14deg) scale(.7); opacity: .25; }
}
@keyframes boss-octo-rise {
  0% { transform: translateY(70px) scale(.55); opacity: 0; }
  100% { transform: translateY(0) scale(1); opacity: 1; }
}
/* Aufstieg (erste 36 %), dann das Drängen – und beim Wiederholen bleibt er
   oben: Ab dem zweiten Durchlauf ist der Anfang nur noch ein Wippen. */
@keyframes boss-octo-rise-loom {
  0% { transform: translateY(70px) scale(.55) rotate(0); opacity: 0; }
  36% { transform: translateY(0) scale(1) rotate(-2deg); opacity: 1; }
  68% { transform: translateY(-6px) scale(1.12) rotate(3deg); opacity: 1; }
  100% { transform: translateY(0) scale(1) rotate(-2deg); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .boss-card, .boss-end-score, .boss-end-btn, .boss-card::before,
  .boss-card .boss-card-octo { animation: none !important; }
  .boss-card.is-won .boss-card-octo { transform: rotate(-12deg); }
  .boss-card.is-zeit .boss-card-octo, .boss-card.is-fall .boss-card-octo { opacity: .5; }
}
/* Die Zwischenkarte trägt unten die Leiste – der Countdown braucht Luft dazu. */
.boss-banner-in { padding-bottom: 32px; }

/* --- Hinweis auf der Startseite ------------------------------------------ */
/* Dieselben Farben wie der Kampfmodus (body.boss-mode): violette Tiefe,
   roter Schein am Rand. Wer die Kachel antippt, landet in genau dieser Welt –
   die Kachel ist ihr Fenster. Feste Werte, keine Marken: Sie soll in jeder
   Welt des Kindes gleich aussehen, auch in den hellen. */
/* Hinter dem Kraken links ist die Kachel fast schwarz und hat einen Hauch
   Türkis: Er ist selbst violett und hebt sich nur von Dunklem ab – dieselbe
   Überlegung wie am Beamer. Nach rechts hin wird es violett mit rotem Schein. */
.home-boss {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 18px 14px 14px;
  margin-bottom: 16px;
  border-radius: var(--radius);
  background:
    radial-gradient(60% 120% at 100% 50%, rgba(255, 70, 100, .3), transparent 70%),
    radial-gradient(48px 48px at 56px 50%, rgba(31, 182, 214, .16), transparent 70%),
    radial-gradient(150px 140px at 52px 50%, rgba(6, 2, 16, .92), rgba(6, 2, 16, .45) 55%, transparent 100%),
    linear-gradient(100deg, #3a1f66, #2a1652 55%, #1c0e3a);
  color: #f2eaff;
  text-decoration: none;
  border: 1px solid rgba(200, 150, 255, .28);
  box-shadow: var(--shadow), 0 0 40px rgba(150, 70, 255, .18);
}
/* Der Clip ist quadratisch, die Figur steht unten mittig darin. Er ragt oben
   und unten über die Kachel hinaus – wie der Octopus an der Kante der
   Übungskarte – und wird nicht abgeschnitten. */
.home-boss-ic {
  flex: none;
  width: 100px;
  height: 100px;
  margin: -24px 0 -24px -8px;
  object-fit: contain;
  object-position: 50% 100%;
  background: transparent;
  /* Der Clip hat viel Luft um die Figur – vergrössert, mit dem Fuss als
     Drehpunkt, damit der Krake stehen bleibt, wo er steht. */
  transform: scale(1.45);
  transform-origin: 50% 82%;
  filter: drop-shadow(0 8px 12px rgba(0, 0, 0, .5));
  -webkit-user-drag: none;
  pointer-events: none;
}
.home-boss-txt { display: flex; flex-direction: column; }
.home-boss-txt strong { font-size: 1.05rem; }
.home-boss-txt small { opacity: .82; }
.home-boss-go { margin-left: auto; font-size: 1.4rem; }


/* ==========================================================================
   Beamer-Ansicht (Vollbild, ein Gerät vorn im Zimmer)

   Hier stehen bewusst KEINE Namen und keine Einzelzahlen: Der Kampf ist
   kooperativ, vorn hängt keine Rangliste.
   ========================================================================== */
.beamer {
  position: fixed;
  inset: 0;
  display: flex;
  flex-direction: column;
  background: var(--page);
  color: var(--on-dark);
  font-family: var(--font-display);
}
/* Im Kampfmodus (live/beamer.php) malt der <body> die Welt – violette Tiefe,
   roter Rand, Blasen. Die Wand selbst bleibt durchsichtig, sonst deckte sie
   das alles zu. */
body.boss-mode .beamer { background: transparent; }
.beamer-stage {
  position: relative;
  flex: 1;
  display: grid;
  place-items: center;
  overflow: hidden;
}
/* Der Krake ist selbst violett. Damit er sich von der violetten Welt abhebt,
   wird es hinter ihm DUNKEL, nicht hell: Die Figur ist deutlich heller als
   fast schwarzes Wasser, ein heller Schein dahinter fräse sie dagegen weg.
   Dazu ein Hauch Türkis als Gegenfarbe zum Violett – ein Rücklicht, das
   die Kanten löst, ohne die Fläche aufzuhellen. */
body.boss-mode .beamer::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  /* Auf der ganzen Wand, nicht nur auf der Bühne: Die schneidet mit
     overflow: hidden ab, und der Fleck hätte an ihrer Unterkante eine
     sichtbare Kante gehabt. Mittelpunkt dort, wo die Figur steht. */
  background:
    radial-gradient(34% 40% at 50% 46%, rgba(31, 182, 214, .12), transparent 70%),
    radial-gradient(60% 70% at 50% 44%, rgba(8, 3, 20, .8), rgba(8, 3, 20, .3) 62%, transparent 100%);
}
/* Alles Sichtbare über den Fleck. Angriff und Ende sind ohnehin absolut
   positioniert und kommen später im Baum – sie liegen von selbst darüber. */
body.boss-mode .beamer-stage,
body.boss-mode .beamer-hud { position: relative; z-index: 1; }
/* Der gezeichnete Krake liegt darunter und erscheint nur, wenn feststeht,
   dass es für diese Stufe keine Clips gibt (beamer.js setzt .no-clips). Nicht
   von Anfang an sichtbar – sonst blitzt beim Laden erst das Emoji auf und
   wird dann vom Video abgelöst. */
.beamer-fallback {
  font-size: min(38vh, 30vw);
  filter: drop-shadow(0 20px 40px rgba(0, 0, 0, .45));
  opacity: 0;
}
.beamer-stage.no-clips .beamer-fallback { opacity: 1; }
.beamer-stage.no-clips .beamer-clip { display: none; }
/* Die Clips sind freigestellt (Alphakanal), die Figur steht unten mittig im
   Bild.

   Die Box ist so gross wie das Bild: Die Höhe gibt die Stufe vor (unten), die
   Breite folgt daraus über `aspect-ratio` (alle dreissig Clips sind
   quadratisch, 1440 × 1440). Zentriert wird über die beiden `auto`-Ränder –
   der übrige Platz wird links und rechts gleich verteilt.

   Das Seitenverhältnis steht FEST und wird nicht vom Video abgelesen. Ein
   Videoelement ohne geladene Metadaten ist 300 px breit – das ist der Notwert
   des CSS für Ersatzelemente ohne eigene Masse, nicht etwa die alte Breite.
   Genau in diesem Zustand steckt jeder Clipwechsel für einen Moment: Beim
   Setzen von `src` schnappt die Box auf 300 px, beim Eintreffen der Metadaten
   wieder zurück. Gemessen auf einer Bühne von 1600 px, Stufe 1: Box 300 px
   breit bei x = 650, dann 530 px breit bei x = 535 – das Bild sprang also erst
   nach rechts und dann um 115 px nach links, bei JEDEM Wechsel, auf jeder
   Stufe. Mit festem Verhältnis steht die Box vom ersten Moment an dort, wo sie
   hingehört, und der Wechsel bewegt nichts mehr.

   Vorher war die Box brettbreit (`left/right: 0; width: 100%`) und `object-fit`
   rückte das Bild darin zurecht. Auf dem Beamer in Safari rutschte es beim
   Wachsen aus dieser Box nach rechts, bis die halbe Figur weg war: Während der
   Höhen-Animation verrechnet WebKit die Lage des Videobildes im viel grösseren
   Kasten. Hat der Kasten keinen freien Platz mehr, gibt es auch nichts mehr zu
   verrücken – die Figur steht während des ganzen Wachsens still.

   `object-fit` bleibt als Netz: Ist die Bühne einmal schmaler als hoch, greift
   `max-width` und das Bild wird darin wieder mittig unten eingepasst, statt
   seitlich hinauszuragen. */
.beamer-clip {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  margin-left: auto; margin-right: auto;
  aspect-ratio: 1 / 1;
  width: auto;
  max-width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: 50% 100%;
  opacity: 0; /* harter Schnitt, kein Fade – wie .mascot-vid */
}
.beamer-clip.is-front { opacity: 1; }
/* Der Krake wächst von Stufe zu Stufe. Jede Figur füllt ihr Video; die Grösse
   an der Wand macht das CSS über die Stufe an der Bühne (beamer.js setzt
   data-level). Über die HÖHE des Elements, nicht über transform: scale –
   ein skaliertes Video malt der Browser erst in voller Grösse und schrumpft
   dann die fertige Ebene mit grober Filterung, das wird unscharf. Bei einer
   Layout-Höhe rechnet der Videopfad direkt auf die Zielgrösse. Am Boden
   verankert, damit er nach oben wächst. */
/* Der Grössen-Übergang erst, wenn der erste Clip steht (.has-clips) – beim
   Laden soll der Krake gleich in seiner Grösse erscheinen, nicht schrumpfen. */
.beamer-stage.has-clips .beamer-clip { transition: filter 0.6s ease, height 1.2s ease-in-out; }
/* Die Kette endet bei 100 %: Mehr hiesse, den Videorahmen oben zu beschneiden,
   und dort ist NICHT immer leer – in den Bewegungen greift die Figur höher als
   in der Ruhepose.
   Die Stufen darunter sind darum kleiner, als es die gleichmässige Kette
   56/67/78/89 vermuten liess. Grund: Die fünf Videos sind unterschiedlich
   gefüllt. Gemessen am Alphakanal nimmt die Figur darin 56 % (Stufe 1), 65 %,
   71 %, 72 % und 66 % (Stufe 5) der Bildhöhe ein – der Königskrake also weniger
   als der Monsterkrake. An der Wand kam er damit gleich gross heraus wie der,
   und der letzte, grösste Schritt fiel aus.
   Die Zahlen hier sind deshalb gegengerechnet: Höhe × Füllung ergibt eine
   gleichmässige Kette von rund 30 % auf 66 % der Bühnenhöhe. Wer ein Video
   austauscht, misst dessen Füllung neu und rechnet die Zahl nach. */
.beamer-stage[data-level="1"] .beamer-clip { height: 53%; }
.beamer-stage[data-level="2"] .beamer-clip { height: 60%; }
.beamer-stage[data-level="3"] .beamer-clip { height: 68%; }
.beamer-stage[data-level="4"] .beamer-clip { height: 79%; }
.beamer-stage[data-level="5"] .beamer-clip { height: 100%; }
/* Angriffe auf dem Beamer: kein eigener Clip, der Loop läuft weiter – der
   Effekt liegt als Filter über der Figur (beamer.js setzt data-attack), wie
   die CSS-Effekte auf den Kinderbildschirmen. */
.beamer-stage[data-attack="zorn"] .beamer-clip { filter: sepia(1) saturate(4) hue-rotate(-30deg) contrast(1.1); }
.beamer-stage[data-attack="blind"] .beamer-clip { filter: blur(6px) brightness(0.55); }
.beamer-stage[data-attack="regen"] .beamer-clip { filter: drop-shadow(0 0 28px rgba(64, 224, 208, 0.85)) brightness(1.1); }

.beamer-hud { padding: 0 5vw 4vh; }
.beamer-arm {
  font-size: clamp(1.4rem, 3.4vw, 3rem);
  font-weight: 800;
  margin-bottom: .3em;
}
.beamer-arm em { opacity: .6; font-style: normal; font-size: .6em; }
.beamer-bar {
  position: relative;
  height: 3.2vh;
  min-height: 22px;
  border-radius: 999px;
  background: var(--track-on-bg);
  overflow: hidden;
}
/* Vorn an der Wand lernt die Klasse die Regel – darum gross genug zum Lesen
   aus der letzten Reihe. */
.beamer-bar-ends { font-size: clamp(.8rem, 1.4vw, 1.2rem); }
.beamer-bar-fill {
  height: 100%;
  width: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--bad), #ff8a5c);
  transition: width .35s ease;
}
.beamer-foot {
  display: flex;
  justify-content: space-between;
  margin-top: .6em;
  font-size: clamp(.9rem, 1.6vw, 1.4rem);
  color: var(--on-dark-soft);
}
.beamer-players { align-self: flex-end; }
/* Der geschaffte Preis auf der Endkarte des Beamers. */
.beamer-end-prize {
  display: block;
  margin-top: .3em;
  font-size: .38em;
  font-weight: 800;
  color: var(--on-dark-soft);
}
.beamer-end-prize.is-won { color: var(--star-on-bg); }
.beamer-attack {
  position: absolute;
  left: 0; right: 0; top: 8vh;
  text-align: center;
  font-size: clamp(2rem, 6vw, 5rem);
  font-weight: 900;
  letter-spacing: .02em;
  color: #fff;
  text-shadow: 0 6px 30px rgba(0, 0, 0, .6);
}
/* Das Ende als Schriftband oben, wie die Angriffsansage – KEIN Schleier über
   der ganzen Bühne: Darunter läuft der Krake weiter (stolz beim Entkommen,
   zu Boden beim Sieg), und den soll die Klasse sehen. */
.beamer-end {
  position: absolute;
  left: 0; right: 0; top: 8vh;
  text-align: center;
  font-size: clamp(2.4rem, 8vw, 7rem);
  font-weight: 900;
  letter-spacing: .02em;
  color: #fff;
  text-shadow: 0 6px 30px rgba(0, 0, 0, .7);
  pointer-events: none;
}

/* Der Ton-Schalter oben rechts: klein, weil er nur der Lehrperson gilt, aber
   sichtbar, weil er die Autoplay-Geste liefert (beamer-sound.js). Über dem
   dunklen Fleck (z-index 0 am ::before). */
.beamer-sound {
  position: absolute;
  top: 2vh; right: 2vw;
  z-index: 3;
  width: 44px; height: 44px;
  display: grid; place-items: center;
  padding: 0;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, .35);
  background: rgba(255, 255, 255, .12);
  color: #fff;
  cursor: pointer;
  opacity: .55;
}
.beamer-sound svg { display: block; }
.beamer-sound.is-off .ico-on { display: none; }
.beamer-sound:not(.is-off) .ico-off { display: none; }
.beamer-sound:hover, .beamer-sound:focus-visible { opacity: 1; }
.beamer-sound.is-on {
  opacity: .9;
  border-color: rgba(255, 255, 255, .8);
  background: rgba(255, 255, 255, .28);
}
/* Das Menü unter dem Icon: zwei Schalter, Musik und Geräusche getrennt. */
.beamer-sound-menu {
  position: absolute;
  top: calc(2vh + 52px); right: 2vw;
  z-index: 3;
  display: grid;
  gap: .5em;
  min-width: 13em;
  padding: .8em 1em;
  border-radius: 14px;
  border: 1px solid rgba(255, 255, 255, .25);
  background: rgba(8, 3, 20, .92);
  color: #fff;
  font-size: clamp(.95rem, 1.5vw, 1.2rem);
  box-shadow: 0 12px 30px rgba(0, 0, 0, .45);
}
.beamer-sound-menu label {
  display: flex; align-items: center; gap: .6em;
  cursor: pointer;
}
.beamer-sound-menu input { width: 1.2em; height: 1.2em; accent-color: var(--star); margin: 0; }
.beamer-sound-menu small { color: var(--star-on-bg); font-size: .75em; line-height: 1.35; max-width: 16em; }
/* Nach dem Neuladen wartet der Ton auf eine Geste (Autoplay-Sperre): Der
   Knopf pulsiert, bis jemand eine Taste drückt oder ihn anklickt. */
.beamer-sound.is-waiting {
  opacity: 1;
  border-color: var(--star-on-bg);
  color: var(--star-on-bg);
  animation: beamer-sound-wait 1.2s ease-in-out infinite;
}
@keyframes beamer-sound-wait {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); }
  50% { box-shadow: 0 0 0 6px rgba(255, 255, 255, .25); }
}
@media (prefers-reduced-motion: reduce) {
  .beamer-sound.is-waiting { animation: none; }
}
/* Entwicklerleiste (?ton=test): ein Knopf je Klang, unten links. */
.beamer-tontest {
  position: absolute;
  left: 1vw; bottom: 1vh;
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.beamer-tontest button {
  padding: .3em .7em;
  border-radius: 8px;
  border: 1px solid rgba(255, 255, 255, .4);
  background: rgba(0, 0, 0, .5);
  color: #fff;
  font: inherit;
  font-size: .85rem;
  cursor: pointer;
}

/* --- Wartehalle beim Kind -------------------------------------------------- */
/* Ein Kind vor einem leeren Bildschirm fragt sich, ob etwas kaputt ist. Also
   sagen wir ihm, dass es gleich losgeht – und dass es nicht allein ist. */
.boss-wait {
  display: grid;
  justify-items: center;
  gap: 6px;
  text-align: center;
  padding: 30px 20px;
  color: var(--on-dark);
}
/* Der Idle-Clip ist quadratisch und hat viel Luft um die Figur – deshalb
   grösser als das frühere Standbild und mit negativem Rand, damit der Krake
   nicht kleiner wirkt und der Text nicht wegrutscht. */
.boss-wait-octo {
  display: block;
  width: clamp(190px, 55vw, 260px);
  height: auto;
  aspect-ratio: 1;
  margin: -20px 0 -24px;
  object-fit: contain;
  object-position: 50% 100%;
  background: transparent;
  filter: drop-shadow(0 14px 22px rgba(0, 0, 0, .45));
  animation: boss-bob 2.6s ease-in-out infinite;
  -webkit-user-drag: none;
  pointer-events: none;
}
.boss-wait strong { font-size: 1.5rem; }
.boss-wait p { margin: 4px 0 0; font-size: 1.1rem; }
/* Der Stufen-Preis in der Wartehalle: ein eigenes Schild, damit er sich von
   der Zahl der Bereiten abhebt – er ist die Ansage, wofür gleich gerechnet
   wird. */
.boss-prize {
  display: grid;
  gap: 2px;
  margin: 10px 0 14px;
  padding: 10px 16px;
  border-radius: 14px;
  background: rgba(255, 255, 255, .1);
  border: 1px solid color-mix(in srgb, var(--star) 45%, transparent);
  font-size: 1.05rem;
  line-height: 1.35;
}
.boss-prize b { color: var(--star-on-bg); }
.boss-prize small { color: var(--on-dark-faint); font-size: .85rem; margin-top: 6px; }
.boss-prize-list { list-style: none; margin: 0; padding: 0; }
.boss-prize-list li { display: flex; justify-content: space-between; gap: 12px; }
.boss-wait p b { font-size: 1.6rem; }
.boss-wait small { color: var(--on-dark-faint); }
@keyframes boss-bob {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-8px); }
}
@media (prefers-reduced-motion: reduce) {
  .boss-wait-octo { animation: none; }
}

/* --- Wartehalle auf dem Beamer -------------------------------------------- */
/* Der Lebensbalken steht hier für nichts – er wird zur ruhigen Fläche, damit
   niemand denkt, der Kampf laufe schon. */
body.beamer-lobby .beamer-bar-fill {
  background: var(--track-on-bg);
  box-shadow: none;
}
body.beamer-lobby .beamer-foot { justify-content: center; }
body.beamer-lobby .beamer-players b { font-size: 1.6em; }

/* --- Lehrerseite ---------------------------------------------------------- */
.boss-lobby {
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 16px 20px;
  margin-bottom: 18px;
  border-radius: 14px;
  background: var(--surface-soft);
}
.boss-lobby-count b { font-size: 2.6rem; font-weight: 800; color: var(--accent-strong); }
.boss-lobby-text { display: flex; flex-direction: column; }
.boss-lobby-text span { color: var(--ink-soft); font-size: 14px; }

.boss-facts {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: .5em 1.4em;
  margin: 0 0 18px;
  padding: 14px 16px;
  border-radius: 14px;
  background: var(--surface-soft);
}
.boss-facts dt { color: var(--ink-soft); font-size: 14px; font-weight: 700; }
.boss-facts dd { margin: 0; font-weight: 700; }
.boss-actions { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
/* .btn ist sonst 100 % breit mit Abstand oben – in der Knopfzeile stehen die
   Knöpfe nebeneinander, so breit wie ihr Text. */
.boss-actions .btn { width: auto; margin-top: 0; }

/* ==========================================================================
   Kraken-Duell – 1 gegen 1 in der Klasse, asynchron als Geisterrennen.

   Wie beim Kraken dieselben Bausteine wie die Übung (.game, .question,
   .keypad, .boss-timer): Ein Kind soll sich nicht neu zurechtfinden müssen.
   Neu ist nur die Bahn mit den zwei Booten und der Vergleich am Ende.

   Was hier bewusst FEHLT, gehört zur Gestaltung: keine Bilanz, keine Tabelle
   nach Erfolg, keine Stufenangabe neben den Namen. Ein Duell ist ein
   Ereignis, kein Statuswert – und die Liste zum Herausfordern ist zwar nach
   Stufennähe sortiert, sagt das aber nirgends.
   ========================================================================== */

/* --- Übersicht ------------------------------------------------------------ */
.duel-index { width: min(760px, 100%); margin: 0 auto; color: var(--on-dark); }
/* Kopf: Text links, der Octopus rechts – wie der Kopf der Startseite. Die
   negativen Ränder holen die transparente Randfläche des Clips wieder rein. */
.duel-head { display: flex; align-items: center; gap: 12px; margin-bottom: 22px; }
.duel-head-text { flex: 1 1 auto; min-width: 0; }
.duel-head h1 { margin: 0 0 6px; }
.duel-head p { margin: 0; color: var(--on-dark-soft); line-height: 1.5; }
.duel-head .mascot-greet {
  flex: none; width: clamp(110px, 28vw, 170px); height: clamp(110px, 28vw, 170px);
  margin: clamp(-24px, -5vw, -10px) 0;
}

/* Das Band, wenn während des Zuschauens eine Herausforderung dazukommt.
   Grün wie der Anwesenheitspunkt: Dort heisst Grün „jetzt gerade", und hier
   heisst es dasselbe. */
.duel-neu {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  margin-bottom: 16px;
  border-radius: var(--radius);
  background: linear-gradient(100deg, rgba(47, 191, 113, .95), rgba(32, 140, 84, .95));
  color: #fff;
  text-decoration: none;
  box-shadow: var(--shadow);
  animation: duel-neu-auf .3s ease;
}
.duel-neu small { display: block; opacity: .85; }
.duel-neu-ic { flex: none; }
.duel-neu-ic svg { width: 26px; height: 26px; display: block; }
@keyframes duel-neu-auf {
  from { opacity: 0; transform: translateY(-6px); }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .duel-neu { animation: none; }
}

.duel-block { margin-bottom: 26px; }
.duel-block h2 { font-size: 16px; margin: 0 0 10px; color: var(--on-dark-soft); }

/* Was es zu gewinnen gibt: eine Karte wie die Duell-Karten, mit einer Zeile
   je Seite. Der Preis steht rechts in Sternfarbe – er ist die Nachricht. */
.duel-prize {
  padding: 12px 18px; border-radius: var(--radius);
  background: var(--surface); color: var(--ink); box-shadow: var(--shadow);
}
.duel-prize-row { display: flex; align-items: center; gap: 12px; padding: 6px 0; }
.duel-prize-row + .duel-prize-row { border-top: 1px solid var(--line); }
.duel-prize-ic { flex: none; font-size: 1.4rem; line-height: 1; }
.duel-prize-who { flex: 1 1 auto; min-width: 0; font-weight: 700; }
.duel-prize-val { flex: none; font-size: 17px; font-weight: 900; color: var(--star-ink); white-space: nowrap; }
.duel-prize-note { margin: 8px 0 0; font-size: 12.5px; color: var(--ink-soft); line-height: 1.4; }

.duel-card {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 18px;
  margin-bottom: 10px;
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  text-decoration: none;
  box-shadow: var(--shadow);
}
.duel-card-txt { display: flex; flex-direction: column; min-width: 0; }
.duel-card-txt small { color: var(--ink-soft); }
.duel-card-ic { font-size: 1.8rem; line-height: 1; }
.duel-card-go { margin-left: auto; font-weight: 700; color: var(--accent-strong); white-space: nowrap; }

/* Eine Herausforderung AN mich ist eine Einladung, keine Aufgabe – sie darf
   auffallen, aber nicht drängen. */
.duel-card--in { box-shadow: 0 0 0 2px var(--accent) inset, var(--shadow); }
/* Ein Live-Duell drängt doch: Der andere sitzt gerade in der Wartehalle und
   wartet. Nach fünf Minuten wird ohnehin ein gewöhnliches Geisterrennen
   daraus – bis dahin darf man es sehen. */
.duel-card--jetzt {
  box-shadow: 0 0 0 2px var(--good) inset, var(--shadow);
  animation: duel-jetzt 1.6s ease-in-out infinite;
}
@keyframes duel-jetzt {
  0%, 100% { box-shadow: 0 0 0 2px var(--good) inset, var(--shadow); }
  50% { box-shadow: 0 0 0 2px var(--good) inset, 0 0 18px rgba(47, 191, 113, .55), var(--shadow); }
}
@media (prefers-reduced-motion: reduce) {
  .duel-card--jetzt { animation: none; }
}
/* Ein Ergebnis, das noch niemand aufgedeckt hat: goldener Ring, dasselbe Gold
   wie das Band auf der Startseite (--star). Dort ist es eine gefüllte Fläche,
   hier ein Ring – die Karten dieser Seite arbeiten mit Ringen, und eine
   goldene Fläche mitten unter ihnen wäre ein Fremdkörper. Die Farbe ist das
   Gemeinsame, nicht die Form.

   Und wie beim grünen Ring: Was blinkt, ist etwas, das JETZT auf einen wartet. */
.duel-card--fertig {
  box-shadow: 0 0 0 2px var(--star) inset, var(--shadow);
  animation: duel-fertig 1.6s ease-in-out infinite;
}
@keyframes duel-fertig {
  0%, 100% { box-shadow: 0 0 0 2px var(--star) inset, var(--shadow); }
  50% { box-shadow: 0 0 0 2px var(--star) inset, 0 0 18px rgba(245, 179, 1, .55), var(--shadow); }
}
@media (prefers-reduced-motion: reduce) {
  .duel-card--fertig { animation: none; }
}

/* Was noch wartet, tritt zurück: Es gibt nichts zu tun. */
.duel-card--out { opacity: .8; }
.duel-card--past .duel-card-go { color: var(--ink-soft); }

.duel-avatar {
  position: relative;
  flex: none;
  width: 44px; height: 44px;
  border-radius: 50%;
  background: var(--surface-soft) center/cover no-repeat;
}

/* „Ist gerade da": ein Punkt am Bild, wie man ihn aus jedem Messenger kennt.
   Der Ring in der Kartenfarbe hebt ihn vom Bild darunter ab, egal wie hell
   oder dunkel dieses ist. */
.duel-online {
  position: absolute;
  right: -1px; bottom: -1px;
  width: 13px; height: 13px;
  border-radius: 50%;
  background: var(--good);
  box-shadow: 0 0 0 2.5px var(--surface);
}

/* „Steckt gerade in einem Duell": dieselbe Stelle wie der grüne Punkt, damit
   das eine offensichtlich an die Stelle des anderen tritt. Eine Uhr und kein
   rotes Zeichen – es ist keine Absage, sondern ein „gleich wieder". */
.duel-busy {
  position: absolute;
  right: -3px; bottom: -3px;
  width: 18px; height: 18px;
  border-radius: 50%;
  background: var(--surface);
  box-shadow: 0 0 0 2.5px var(--surface);
  font-size: 11px;
  line-height: 18px;
  text-align: center;
}

/* --- Wen forderst du heraus ----------------------------------------------- */
.duel-people {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  gap: 10px;
}
.duel-person { margin: 0; }
.duel-person button {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 14px 8px;
  border: 0;
  border-radius: 16px;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  cursor: pointer;
}
.duel-person button:hover { background: var(--press); }
.duel-person-name { font-weight: 700; }
.duel-person-hint { font-size: 12px; color: var(--ink-soft); }
/* Gegen wen schon ein Duell läuft, bleibt sichtbar – aber nicht wählbar.
   Ihn wegzulassen liesse ein Kind suchen, wo nichts fehlt. Und wer gerade
   selbst spielt, bleibt eine Spur heller: Er ist in ein paar Minuten wieder
   da, im Unterschied zu einem Duell, das erst gespielt werden muss. */
.duel-person.is-busy button { opacity: .5; cursor: default; }
.duel-person.is-playing button { opacity: .65; }

.duel-note, .duel-empty { color: var(--on-dark-soft); font-size: 14px; line-height: 1.5; }
/* Derselbe Punkt wie am Bild, nur mitten im Satz – damit das Kind ihn
   wiedererkennt, statt ihn sich aus einer Beschreibung zusammenzureimen. */
.duel-online--inline {
  position: static;
  display: inline-block;
  width: 10px; height: 10px;
  box-shadow: none;
  vertical-align: baseline;
}

/* --- Sterne-Duell --------------------------------------------------------- */
/* Der Einsatz-Wähler sitzt im Rückfrage-Kasten, den es vor dem Herausfordern
   ohnehin schon gibt: Es ist dieselbe Frage – gegen wen, und worum. */
.duel-wager { margin: 4px 0 2px; }
/* Erst welches Spiel, dann wie viel: zwei Reihen für zwei Fragen. Die
   Modus-Knöpfe sind breit und zweizeilig, die Beträge schmale Pillen – so
   sieht man den Unterschied, bevor man ihn liest. */
.duel-wager-modi { display: flex; gap: 8px; }
.duel-wager-modus {
  flex: 1;
  display: flex; flex-direction: column; gap: 1px;
  border: 2px solid var(--line, rgba(0,0,0,.14));
  background: var(--surface);
  color: var(--ink);
  border-radius: 14px;
  padding: .6em .5em;
  font: inherit;
  text-align: center;
  cursor: pointer;
}
.duel-wager-modus strong { font-size: 14px; }
.duel-wager-modus small { font-size: 11px; color: var(--ink-soft); }
.duel-wager-modus.on { border-color: var(--accent, #4fd0e8); background: var(--press); }
.duel-wager-modus.on small { color: inherit; }
.duel-wager-modus:disabled { opacity: .4; cursor: default; }
.duel-wager-steps {
  display: flex; flex-wrap: wrap; gap: 6px; justify-content: center;
  margin-top: 10px;
}
.duel-wager-step {
  border: 1px solid var(--line, rgba(0,0,0,.14));
  background: var(--surface-soft);
  color: var(--ink);
  border-radius: 999px;
  padding: .34em .8em;
  font: inherit; font-size: 13px; font-weight: 600;
  cursor: pointer;
}
.duel-wager-step.on { background: var(--accent, #4fd0e8); border-color: transparent; color: #06232e; }
/* Gesperrt heisst „bei euch beiden geht das nicht" – bewusst ohne Zahl:
   Der Stand des anderen Kindes geht niemanden etwas an. */
.duel-wager-step:disabled { opacity: .35; cursor: default; }
/* Abstand nach oben: Der Satz gehört nicht an die Knöpfe geklebt, er ist eine
   Auskunft und keine Beschriftung.

   Mit dem Elternteil geschrieben, nicht allein: `.confirm-box p` weiter oben
   setzt `margin: 0 0 18px` und ist als Klasse-plus-Element spezifischer als
   eine Klasse für sich – die Regel hier wäre sonst wirkungslos, egal wie weit
   unten sie steht. */
.duel-wager .duel-wager-hint {
  /* Unten dieselben 18 px wie bei jedem anderen Absatz im Kasten: Die
     Knopfreihe darunter bringt keinen eigenen Abstand mit, sie stiesse sonst
     direkt an die Zeile. */
  margin: 14px 0 18px;
  font-size: 12px;
  color: var(--ink-soft);
}

/* Ein Angebot ist keine Herausforderung, sondern eine Frage: Es trägt deshalb
   zwei Knöpfe statt eines Pfeils. */
.duel-card--offer { align-items: flex-start; }
.duel-card-tun { display: flex; flex-direction: column; gap: 6px; flex: none; }
.duel-card-tun .btn { white-space: nowrap; }
.duel-card-tun form { margin: 0; }

/* Was auf dem Spiel steht – am Kopf des Duells und im Ergebnis. */
.duel-einsatz {
  display: inline-block;
  background: var(--surface-soft);
  border-radius: 999px;
  padding: .2em .7em;
  font-weight: 700;
}

/* --- Wartehalle eines Duells in Echtzeit ---------------------------------- */
.duel-lobby .duel-result-ic { font-size: 3.4rem; }
.duel-lobby-ic { display: block; color: var(--on-dark); }
.duel-lobby-ic svg { width: 56px; height: 56px; display: block; margin: 0 auto; }
.duel-lobby-num {
  margin: 18px auto 8px;
  font-size: clamp(3rem, 18vw, 5rem);
  font-weight: 900;
  color: var(--on-dark);
  line-height: 1;
}
.duel-lobby .btn { margin-top: 10px; }

/* Die beiden Gesichter mit ihrem Haken: Ein Kind soll auf einen Blick sehen,
   worauf noch gewartet wird. „Warten auf Ben" allein sagt nicht, ob Ben
   überhaupt schon da ist. */
.duel-lobby-paar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  margin: 18px 0 10px;
}
.duel-lobby-wer { display: flex; flex-direction: column; align-items: center; gap: 6px; }
.duel-lobby-wer small { color: var(--on-dark-soft); font-weight: 700; }
.duel-lobby-wer .duel-avatar { width: 64px; height: 64px; position: relative; }
.duel-lobby-vs { color: var(--on-dark-faint); font-size: 13px; font-weight: 700; }
/* Wer noch nicht aufgemacht hat, ist blass – aber nicht grau: Ein
   ausgegrautes Gesicht sieht aus, als sei mit dem Kind etwas nicht in
   Ordnung. */
#lobbyGegnerBox .duel-avatar { opacity: .45; transition: opacity .2s ease; }
#lobbyGegnerBox.is-da .duel-avatar { opacity: 1; }
.duel-lobby-haken {
  position: absolute;
  right: -2px; bottom: -2px;
  width: 24px; height: 24px;
  border-radius: 50%;
  background: var(--good);
  color: #fff;
  font-size: 14px; font-weight: 900;
  display: grid; place-items: center;
  box-shadow: 0 0 0 3px var(--bg);
}
/* „Zurück" ist die kleinere Handlung – zwei gleich kräftige Knöpfe
   nebeneinander zwingen ein Kind zu einer Wahl, die es gar nicht hat. */
.duel-lobby .btn--leise {
  background: transparent;
  color: var(--on-dark-soft);
  box-shadow: 0 0 0 1.5px var(--on-dark-faint) inset;
}
.duel-lobby .btn--leise:hover { background: rgba(255, 255, 255, .08); }

/* --- Die Punktreihe -------------------------------------------------------- */
/* Zehn Punkte, einer je Rechnung. Der laufende ist hervorgehoben, die
   erledigten tragen ihr Häkchen. Sie sitzt hier und nicht auf der Karte, weil
   die Karte schon die NÄCHSTE Rechnung zeigt, wenn die Rückmeldung eintrifft. */
.duel-dots { display: flex; gap: 6px; justify-content: center; margin: 2px 0 10px; }
.duel-dot {
  width: 12px; height: 12px;
  border-radius: 50%;
  background: var(--track-on-bg);
  transition: background-color .2s ease, transform .2s ease;
}
.duel-dot.is-right { background: var(--good); }
.duel-dot.is-wrong { background: var(--bad); }
/* Beantwortet, aber noch nicht beurteilt. Über richtig entscheidet der Server
   – die Lösungen kommen im Duell nie ins Tablet –, und bis seine Antwort da
   ist, soll der Punkt nicht so aussehen, als sei nichts passiert. Heller und
   ruhig pulsierend: „angekommen, wird geprüft". */
.duel-dot.is-pending {
  background: var(--on-dark);
  animation: duel-dot-wait 1.1s ease-in-out infinite;
}
/* Auch im dunkelsten Augenblick heller als ein unberührter Punkt – sonst ist
   „wird geprüft" von „kommt noch" nicht zu unterscheiden. */
@keyframes duel-dot-wait {
  0%, 100% { opacity: .7; transform: scale(1); }
  50% { opacity: 1; transform: scale(1.15); }
}
/* Wo das Kind gerade steht – ein Ring, keine Farbe: Farbe heisst hier
   richtig oder falsch, und das steht noch nicht fest. */
.duel-dot.is-now {
  transform: scale(1.35);
  box-shadow: 0 0 0 2px var(--on-dark-faint);
}
@media (prefers-reduced-motion: reduce) {
  .duel-dot { transition: none; }
  .duel-dot.is-pending { animation: none; }
}

/* Der einzige Moment, in dem im Duell auf die Leitung gewartet wird. */
.duel-zaehlt { margin: 0; font-weight: 700; color: var(--ink-soft); }
/* „Ben rechnet gerade" statt des Startknopfs – solange die Gegenseite die
   Reihe hat. Zwei Zeilen, mittig, in der Deckplatte der Karte. */
.duel-reihe-warten { display: grid; gap: 6px; text-align: center; }
.duel-reihe-warten .duel-result-note { margin: 0; }

/* --- Die Bahn mit den zwei Booten ----------------------------------------- */
.duel-track { margin: 6px 0 16px; display: grid; gap: 8px; }
.duel-lane { display: grid; grid-template-columns: 5.5em 1fr; align-items: center; gap: 10px; }
.duel-lane-lb {
  font-size: 13px;
  color: var(--on-dark-soft);
  text-align: right;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.duel-rail {
  position: relative;
  height: 36px;
  border-radius: 999px;
  background: var(--track-on-bg);
}
/* Die eigentliche Laufstrecke, an beiden Enden um den halben Läufer eingerückt.
   Ohne sie stand er bei 0 % mit der halben Breite VOR der Bahn und deckte die
   Beschriftung daneben zu – und bei 100 % ragte er rechts hinaus. */
.duel-rail-track { position: absolute; inset: 0 18px; display: block; }

/* Auf der Bahn steht das Profilbild, nicht ein Symbol: Zwei gleiche Figuren
   nebeneinander müsste ein Kind erst zuordnen – zwei Gesichter nicht. */
.duel-runner {
  position: absolute;
  top: 50%;
  left: 0;
  transform: translate(-50%, -50%);
  width: 30px; height: 30px;
  border-radius: 50%;
  background: var(--surface) center/cover no-repeat;
  box-shadow: 0 0 0 2px var(--surface);
  transition: left .4s ease;
}
/* Die Gegenseite tritt zurück – aber ohne Graustufen: Ein ausgegrautes
   Gesicht sieht aus, als sei mit dem Kind etwas nicht in Ordnung. Nur der
   Ring und die Bahn sind ruhiger. */
.duel-lane--ghost .duel-rail { background: var(--track-on-card); }
.duel-lane--ghost .duel-runner { opacity: .8; box-shadow: 0 0 0 2px var(--track-on-bg); }
@media (prefers-reduced-motion: reduce) {
  .duel-runner { transition: none; }
}

/* --- Die Aufgabenkarte ----------------------------------------------------- */
/* position: relative, weil Zeitbalken und Auftakt-Deckel darin liegen. */
.game.duel .question { position: relative; overflow: hidden; }
.game.duel .question.right { box-shadow: 0 0 0 3px var(--good) inset; }
.game.duel .question.wrong { box-shadow: 0 0 0 3px var(--bad) inset; }

/* --- Ergebnis ------------------------------------------------------------- */
.duel-result { width: min(680px, 100%); margin: 0 auto; color: var(--on-dark); text-align: center; }
.duel-result-head { margin-bottom: 22px; }
.duel-result-ic { font-size: 3rem; line-height: 1; }
/* Die Kiste steht hier auf dem Seitenhintergrund, nicht auf einer weissen
   Karte wie im Laden und in der Übungspause. Ihre Beschriftung nimmt darum
   die Schriftfarben des Hintergrunds – über die Tokens, nicht als festes
   Weiss: In den hellen Welten (Pergament) wäre Weiss ebenso unlesbar, wie
   Dunkelblau es hier war. */
.duel-result .mbox-lead,
.duel-result .mbox-name { color: var(--on-dark); }
.duel-result .mbox-hint { color: var(--on-dark-soft); }

/* Der Octopus hängt sonst an der Kante einer Karte (position: absolute,
   right: -36px). Hier steht er mittig über der Überschrift und braucht
   deshalb seinen eigenen Platz im Fluss. */
.duel-result-octo {
  position: relative;
  right: auto;
  top: auto;
  margin: 2px auto 0;
  width: clamp(112px, 30vw, 152px);
}
.duel-result h1 { margin: 8px 0 4px; }
.duel-result p { color: var(--on-dark-soft); line-height: 1.5; }
.duel-result-note { font-size: 14px; }
.duel-result-score { font-size: 1.3rem; font-weight: 800; color: var(--on-dark); }

/* Die Belohnung ist die Antwort auf „was habe ich davon?" – sie darf nicht
   kleiner sein als die Zahlen darunter. Eng an den Octopus gerückt (sie
   gehört zu ihm) und mit Abstand zum Punktestand, damit sie nicht als dessen
   Unterzeile gelesen wird. Mitwachsend, weil hier auch ein langer
   Gutscheinname stehen kann. */
.duel-result-stars {
  margin: 0 0 2px;
  font-size: clamp(1.4rem, 6.5vw, 2rem);
  font-weight: 900;
  line-height: 1.15;
  color: var(--star-on-bg);
}
/* Ein Gutschein- oder Kistenname ist kein „+3 ⭐": Er ist lang und bräche in
   dieser Grösse über zwei Zeilen um. Auffällig bleibt er trotzdem. */
.duel-result-stars.is-item { font-size: clamp(1.15rem, 4.5vw, 1.5rem); }

/* Keine Punktzahl, sondern das, was zählt: richtig und Zeit. Beide Seiten
   gleich gross – auch die verlorene. */
.duel-scores { display: flex; justify-content: center; gap: 12px; margin: 48px 0 6px; }
.duel-score {
  flex: 1 1 0;
  max-width: 220px;
  padding: 14px 12px;
  border-radius: 16px;
  background: var(--surface);
  color: var(--ink);
  display: flex; flex-direction: column; gap: 2px;
}
.duel-score-who { font-size: 13px; color: var(--ink-soft); }
.duel-score strong { font-size: 1.15rem; }
.duel-score-ms { font-size: 14px; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.duel-score.is-me { box-shadow: 0 0 0 2px var(--accent) inset; }
/* Der Gewinner-Kasten leuchtet – ein goldener Rand im Sternengelb der Krone
   und ein atmender Schein rundherum. Der Schein läuft über filter statt über
   box-shadow, damit er sich mit dem blauen Innenrand des eigenen Kastens
   verträgt und die herausragende Krone gleich mit umfasst. */
.duel-score.is-winner { box-shadow: 0 0 0 2px var(--star); }
.duel-score.is-me.is-winner { box-shadow: 0 0 0 2px var(--accent) inset, 0 0 0 2px var(--star); }
/* Der Schein liegt auf einem eigenen Pseudo-Element und atmet nur über die
   Deckkraft: Die läuft flüssig auf dem Compositor. Ein animierter filter
   sprang nach ein paar Sekunden sichtbar um, wenn der Browser die Ebene neu
   rasterte. Kein Hintergrund, nur ein Schatten nach aussen – deshalb kann
   es ohne z-index-Verrenkungen über dem Kasten liegen. */
.duel-score.is-winner::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: 0 0 18px 6px rgba(245, 179, 1, .7);
  pointer-events: none;
  animation: duel-winner-glow 2.2s ease-in-out infinite;
}
@keyframes duel-winner-glow {
  0%, 100% { opacity: .4; }
  50%      { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .duel-score.is-winner::before { animation: none; opacity: .75; }
}
/* Die Krone aus dem Menü über dem Kasten, wer gewonnen hat: Sie sitzt ganz
   über dem Kasten und ragt nur in dessen Polster hinein – der Inhalt bleibt
   auf derselben Höhe wie im Kasten daneben. Deshalb der grosse Abstand über
   .duel-scores. */
.duel-score { position: relative; }
/* Dasselbe Gelb wie die Sterne – die Krone ist eine Belohnung, und die
   sehen im ganzen Programm so aus. */
.duel-score-crown {
  position: absolute;
  top: -38px; left: 50%;
  transform: translateX(-50%);
  width: 44px; height: 44px;
  color: var(--star);
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, .2));
}
.duel-score-crown svg { width: 100%; height: 100%; display: block; }

/* Zeile für Zeile – der eigentliche Gewinn der gemeinsamen Folge: Man sieht
   nicht nur, WER schneller war, sondern WO. */
.duel-rows {
  width: 100%;
  border-collapse: collapse;
  background: var(--surface);
  color: var(--ink);
  border-radius: var(--radius);
  overflow: hidden;
  margin-bottom: 20px;
}
.duel-rows th, .duel-rows td { padding: 8px 12px; }
.duel-rows th {
  font-size: 13px; color: var(--ink-soft); text-align: center;
  border-bottom: 1px solid var(--line);
}
.duel-rows th:first-child { text-align: left; }
/* „Du" in derselben Farbe wie der Rand des eigenen Kastens darüber – so
   findet man die eigene Spalte, ohne zu lesen. */
.duel-rows th.is-me { color: var(--accent); }
/* Die Rechnung braucht nur ihre eigene Breite – der Rest gehört den beiden
   Zeitspalten, die verglichen werden sollen. Eine echte Breite, kein
   `width: 1%`: Damit fiel die Spalte auf 24 px zusammen und die Rechnung ragte
   in die Nachbarspalte. */
.duel-rows td:first-child, .duel-rows th:first-child { width: 6.4em; white-space: nowrap; }
.duel-rows tbody tr + tr td { border-top: 1px solid var(--line-soft); }

/* Die Rechnung in drei Feldern, damit das Rechenzeichen in jeder Zeile an
   derselben Stelle steht: linke Zahl rechtsbündig, Zeichen in der Mitte,
   rechte Zahl linksbündig. Bei „10 · 7" über „2 · 3" wandert es sonst mit der
   Stellenzahl, und die Spalte wird unruhig. */
.duel-row-term {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: baseline;
  width: 5.5em;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  font-size: 16px;
}
.duel-row-term .t-a { text-align: right; }
.duel-row-term .t-op { padding: 0 .35em; color: var(--ink-soft); }
.duel-row-term .t-b { text-align: left; }

.duel-row-cell {
  text-align: center;
  font-variant-numeric: tabular-nums;
  color: var(--ink-soft);
  white-space: nowrap;
}
/* Fester Platz für den Haken, damit die Zeiten in der Spalte untereinander
   stehen – und mit Abstand, sonst klebt er am „s". */
.duel-row-zeichen { display: inline-block; width: 1.1em; margin-left: .35em; text-align: left; }
.duel-row-cell.is-right { color: var(--good-ink); }
.duel-row-cell.is-wrong { color: var(--bad); }

/* Wer die Zeile für sich entschieden hat. Nicht über die Farbe – die sagt
   schon richtig oder falsch – sondern über Gewicht und eine ruhige Fläche
   dahinter. So bleibt auf einen Blick lesbar, WO das Duell gewonnen wurde. */
.duel-row-cell.is-schneller {
  font-weight: 900;
  background: color-mix(in srgb, var(--good) 12%, transparent);
}
.duel-row-cell.is-schneller.is-wrong {
  background: color-mix(in srgb, var(--bad) 10%, transparent);
}

/* Auf dem Handy zusammenrücken statt seitwärts scrollen: Ein Vergleich, den
   man schieben muss, ist kein Vergleich – man sieht nie beide Spalten. */
@media (max-width: 430px) {
  .duel-rows th, .duel-rows td { padding: 8px 5px; }
  .duel-rows th { font-size: 12px; }
  .duel-rows td:first-child, .duel-rows th:first-child { width: 5.2em; }
  .duel-row-term { width: 4.6em; font-size: 15px; }
  .duel-row-term .t-op { padding: 0 .25em; }
  .duel-row-cell { font-size: 14px; }
  .duel-row-zeichen { width: 1em; margin-left: .25em; }
}

/* --- Die Nachfrage vor dem Herausfordern ---------------------------------- */
/* Das Gesicht steht ganz oben und gross: Genau daran merkt ein Kind, dass es
   danebengegriffen hat – der Name allein wird in der Eile überlesen. */
.duel-confirm-avatar {
  display: block;
  width: 72px; height: 72px;
  margin: 0 auto 12px;
}
.duel-confirm .btn.c-ok { background: var(--accent); }

/* --- Belohnungen (Lehrerseite) -------------------------------------------- */
.duel-rewards {
  margin: 4px 0 18px;
  padding: 14px 16px;
  border-radius: 14px;
  background: var(--surface-soft);
}
.duel-rewards-lead { margin: 0 0 12px; font-weight: 700; }
.duel-rewards-lead .hint { display: block; font-weight: 400; margin-top: 2px; }
/* Drei Spalten, nicht fünf: Wer, welche Art – und genau EIN Wertfeld. Die
   drei möglichen Wertfelder (Sterne, Gutschein, Kiste) liegen übereinander,
   immer nur eines ist sichtbar, und ein verstecktes Feld belegt in einem
   Raster keine Spalte. */
.duel-reward-row {
  display: grid;
  grid-template-columns: 6.5em minmax(10em, 1fr) minmax(10em, 1.3fr);
  gap: 8px;
  align-items: center;
  margin-bottom: 8px;
}
.duel-reward-who { font-weight: 700; }
@media (max-width: 620px) {
  .duel-reward-row { grid-template-columns: 1fr 1fr; }
  .duel-reward-who { grid-column: 1 / -1; }
}
/* Die Stufen-Preise des Kraken: fünf Zeilen, eine je Stufe. Die erste
   Spalte trägt Nummer UND Namen der Stufe und braucht darum mehr Platz. */
.boss-prizes .duel-reward-row { grid-template-columns: 11em minmax(9em, 1fr) minmax(10em, 1.3fr); }
.boss-prizes .duel-reward-who small { display: block; font-weight: 400; color: var(--ink-soft); font-size: 12px; }
@media (max-width: 620px) {
  .boss-prizes .duel-reward-row { grid-template-columns: 1fr 1fr; }
}

/* --- Hinweis auf der Startseite ------------------------------------------- */
/* Violett, und das ist der ganze Zweck: „Weiter lernen" darüber ist die
   Akzentfarbe (Türkis), der Krakenkampf warm rot-magenta. Ein Duell-Banner in
   Türkis sah aus wie ein zweiter Lern-Knopf – gerade dort, wo beide
   untereinander stehen. Kühles Violett steht zwischen beiden und wird mit
   keinem verwechselt. Fest gesetzt wie beim Kraken: Diese Bänder sollen in
   jeder Welt dasselbe bedeuten. */
.home-duel {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 18px;
  margin-bottom: 16px;
  border-radius: var(--radius);
  background: linear-gradient(100deg, rgba(124, 92, 214, .95), rgba(75, 63, 168, .95));
  color: #fff;
  text-decoration: none;
  box-shadow: var(--shadow);
}
/* Ein entschiedenes Duell ist keine Einladung mehr, sondern eine Auflösung –
   Gold statt Violett, wie die Sterne und die Krone. Dass es dieselbe Kachel
   in einer anderen Farbe ist, macht den Unterschied ohne zweites Muster. */
.home-duel--fertig {
  background: linear-gradient(100deg, rgba(245, 179, 1, .95), rgba(214, 138, 0, .95));
  color: #2b1d00;
}
.home-duel--fertig .home-duel-txt small { opacity: .78; }

/* Dasselbe auf der Startseite: „wartet jetzt auf dich" ist Grün, weil es an
   den Anwesenheitspunkt in der Liste anschliesst – dort heisst Grün „ist
   gerade da", und hier heisst es dasselbe. */
.home-duel--jetzt {
  background: linear-gradient(100deg, rgba(47, 191, 113, .95), rgba(32, 140, 84, .95));
}

.home-duel-ic { font-size: 2rem; line-height: 1; flex: none; }
/* Dieselbe Krone wie im Menü – ein Kind soll die Kachel und den Menüeintrag
   als dieselbe Sache erkennen. */
.home-duel-ic svg { width: 30px; height: 30px; display: block; }
.home-duel-txt { display: flex; flex-direction: column; }
.home-duel-txt small { opacity: .85; }
.home-duel-go { margin-left: auto; font-size: 1.4rem; }

/* --- Einstellungen -------------------------------------------------------- */
.setting-ic.ic-duel {
  background: color-mix(in srgb, var(--accent) 14%, var(--surface));
  color: var(--accent-strong);
}

@media (max-width: 520px) {
  .duel-lane { grid-template-columns: 4.2em 1fr; }
  .duel-scores { flex-direction: column; align-items: center; gap: 30px; }
  .duel-score { max-width: none; width: 100%; }
}
