/* ============================================================
   Quizplattform — Farbwelt aus Logo und Banner (A.2.1.0)
   Violett/Neon-Pink mit Türkis als Zweitakzent. Die Grundtöne sind
   nicht geraten, sondern aus den Grafiken in public/uploads/
   ausgezählt: #2A0F62 war dort mit Abstand die häufigste Fläche,
   #0F062D der dunkelste Ton, #5BA1C3 das Türkis.

   Vorher stand hier das Gold-Schema der sg80-Vorlage. Die Variablen
   heißen deshalb nicht mehr --xxl-gold-*, sondern --xxl-accent-*:
   ein „gold" im Namen, das pink ausgibt, führt beim nächsten Umbau
   jeden in die Irre.
   ============================================================ */

:root {
  --xxl-bg:          #150b2b;   /* dunkler als die Karten, damit die sich abheben */
  /* Sein Bühnenbild (24.08.2026). Steht hier EINMAL, damit Bühnengrund und
     Übergabe-Schirm nicht auseinanderlaufen. `buehne-web.jpg` ist die auf
     1920 px verkleinerte Fassung von `buehne.jpg` — 51 KB statt 1,9 MB. */
  --xxl-buehne-bild: url('/static/uploads/buehne-web.jpg');
  --xxl-content:     #241247;
  --xxl-card:        #2A0F62;   /* die dominante Fläche aus dem Banner */
  --xxl-card-hover:  #351a75;
  --xxl-sidebar:     #0F062D;
  --xxl-header-base: #0F062D;   /* der dunkelste Ton aus dem Logo */
  --xxl-header-mid:  #241247;

  /* Neon-Pink: die Signalfarbe der Grafiken. Sie trägt hier alles, was
     Aufmerksamkeit verlangt — Hauptknöpfe, aktive Zeilen, Raumcode. */
  --xxl-accent:         #FF4FD8;
  --xxl-accent-hover:   #E62FBC;
  --xxl-accent-light:   #FF87E5;
  /* Getrennte RGB-Anteile für rgba()-Flächen. Diese Variable fehlte in der
     Vorlage, obwohl sie dort benutzt wurde — solche rgba() sind ungültig und
     fallen STILL aus, die Fläche fehlt dann einfach. */
  --xxl-accent-rgb:     255, 79, 216;

  /* Türkis als Zweitakzent (aus Banner und Logo), für alles, was NEBEN dem
     Pink stehen soll, ohne mit ihm um Aufmerksamkeit zu ringen. */
  --xxl-accent2:        #4FD9E8;
  --xxl-accent2-rgb:    79, 217, 232;

  --xxl-text:         #ffffff;
  --xxl-text-muted:   #CEB6DB;   /* das Lavendel der Banner-Schrift */
  --xxl-text-dim:     #9481b0;

  --xxl-border:       #4a2f7a;
  --xxl-border-light: #3a2460;

  /* „Richtig" bleibt bewusst grün und „falsch" rot: bei einer Wertung muss die
     Bedeutung sofort lesbar sein, da hilft kein Markenton. Beide leicht ins
     Kühle gezogen, damit sie neben dem Violett nicht schmutzig wirken. */
  --xxl-success:  #5ED84F;
  --xxl-warning:  #FFC94F;
  --xxl-danger:   #FF5C7A;
  --xxl-info:     #4FD9E8;

  --xxl-shadow-sm: 0 1px 3px rgba(0,0,0,0.4);
  --xxl-shadow:    0 2px 8px rgba(0,0,0,0.35);
  --xxl-shadow-lg: 0 8px 32px rgba(0,0,0,0.5), 0 2px 8px rgba(0,0,0,0.3);
}

/* Light-Mode. ACHTUNG: wird derzeit NIRGENDS aktiviert — kein Skript setzt
   data-theme. Die Werte stehen hier trotzdem passend zur neuen Farbwelt, damit
   niemand später einen Umschalter einbaut und plötzlich das alte Gold zurück
   auf den Schirm holt. */
html[data-theme="light"] {
  --xxl-bg:          #f4eefb;
  --xxl-content:     #ffffff;
  --xxl-card:        #ffffff;
  --xxl-card-hover:  #f8f1ff;
  --xxl-sidebar:     #241247;   /* Sidebar bleibt dunkel */
  --xxl-header-base: #0F062D;   /* Header bleibt dunkel */
  --xxl-header-mid:  #241247;

  --xxl-accent:         #C4189E;
  --xxl-accent-hover:   #A81487;
  --xxl-accent-light:   #FF4FD8;
  --xxl-accent-rgb:     196, 24, 158;

  --xxl-accent2:        #1B8C99;
  --xxl-accent2-rgb:    27, 140, 153;

  --xxl-text:         #1a1030;
  --xxl-text-muted:   #4a3a66;
  --xxl-text-dim:     #8878a0;

  --xxl-border:       #d8c8ea;
  --xxl-border-light: #e9dff5;

  --xxl-shadow-sm: 0 1px 3px rgba(0,0,0,0.08);
  --xxl-shadow:    0 2px 10px rgba(0,0,0,0.10);
  --xxl-shadow-lg: 0 8px 32px rgba(0,0,0,0.15), 0 2px 8px rgba(0,0,0,0.08);
}
/* Im Light-Mode sind Text auf Header/Sidebar trotzdem hell */
html[data-theme="light"] .xxl-header,
html[data-theme="light"] .xxl-header .text-white,
html[data-theme="light"] .xxl-sidebar,
html[data-theme="light"] .xxl-sidebar-item {
  color: #ffffff;
}
html[data-theme="light"] .xxl-sidebar-item { color: #cccccc; }

/* ▶▶ DIE ZWEI BÜHNENMASSE ALS RÜCKFALL (A.20.27.0) --------------------------
   `--bw` und `--bh` sind ein Prozent der Bühnenbreite beziehungsweise -höhe.
   Gesetzt werden sie an zwei Stellen: an `.stage-body` als feste 12,8/7,2 px
   (die Bühne IST 1280x720) und am `.quiz-vorschau-schirm` als `1cqw`/`0.5625cqw`.

   ⚠️⚠️ DIESER RÜCKFALL IST PFLICHT, KEINE VORSICHT. Die `.stage-*`-Regeln
   werden auch außerhalb dieser beiden Orte gelesen. Wäre `--bw` dort
   undefiniert, wäre `calc(2 * 1.25 * var(--bw))` zur Rechenzeit ungültig — die
   Deklaration fiele ERSATZLOS aus, und zwar still: kein Fehler, keine Meldung,
   nur ein Abstand, der plötzlich weg ist. */
:root { --bw: 12.8px; --bh: 7.2px; }

html, body {
  background: var(--xxl-bg);
  color: var(--xxl-text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
               "Helvetica Neue", Arial, sans-serif;
}

body {
  /* dezente horizontale Schliff-Textur wie abgenutzter Stahl */
  background-image:
    repeating-linear-gradient(0deg,
      transparent, transparent 2px,
      rgba(255,255,255,0.02) 2px, rgba(255,255,255,0.02) 3px);
  min-height: 100vh;
}

/* Alpine: x-cloak-Elemente verborgen halten, bis Alpine initialisiert hat —
   verhindert kurzes Aufblitzen un-initialisierter x-show-/x-text-Bindings beim
   Laden (v. a. im Admin, wo sonst Übersicht/Editor/Auswertung gleichzeitig
   aufflackern). Fehlte bisher komplett, auch in tailwind.css. */
[x-cloak] { display: none !important; }

/* Header mit Gunmetal-Gradient + Gold-Akzent-Unterkante */
/* Done-Tasks per Default ausgeblendet (siehe showDone-State im Frontend);
   beim Toggle erscheinen sie wieder oben in der Liste. Damit ist der
   sichtbare Bereich nur noch offene Tasks + Eingabefeld, der freie Platz
   unten ist „echt" (kein Scroll-Layout-Trick). */

/* Bilder in Tasks (Markdown ![alt](url|w=NNN)) */
.task-img-wrap {
  display: inline-block;
  position: relative;
  vertical-align: top;
  margin: 0.25rem 0;
  max-width: 100%;
}
.task-img {
  display: block;
  max-width: 100%;
  height: auto;
  border-radius: 6px;
  border: 1px solid var(--xxl-border-light);
  user-select: none;
}
.task-img-resize-handle {
  position: absolute;
  right: -2px;
  bottom: -2px;
  width: 14px;
  height: 14px;
  background: var(--xxl-accent);
  border: 2px solid var(--xxl-bg);
  border-radius: 3px;
  cursor: nwse-resize;
  opacity: 0;
  transition: opacity 0.15s;
  z-index: 2;
}
.task-img-wrap:hover .task-img-resize-handle { opacity: 0.95; }
.task-img-resize-handle:hover { transform: scale(1.15); }

/* Drag-Target highlight beim Drop von Bild-Files */
.xxl-drag-target {
  outline: 2px dashed var(--xxl-accent) !important;
  outline-offset: -2px;
  background: rgba(222, 167, 38, 0.08) !important;
}

.xxl-header {
  background:
    repeating-linear-gradient(120deg,
      transparent, transparent 30px,
      rgba(255,255,255,0.015) 30px, transparent 31px),
    linear-gradient(180deg,
      rgba(255,255,255,0.10) 0%,
      rgba(255,255,255,0.03) 40%,
      rgba(0,0,0,0.25) 100%),
    linear-gradient(180deg, var(--xxl-header-mid) 0%, var(--xxl-header-base) 100%);
  border-bottom: 2px solid var(--xxl-accent);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.12),
    0 2px 10px rgba(0,0,0,0.45);
}

/* Sidebar mit feiner Metall-Textur */
.xxl-sidebar {
  background:
    repeating-linear-gradient(0deg,
      transparent, transparent 2px,
      rgba(255,255,255,0.025) 2px, rgba(255,255,255,0.025) 3px),
    radial-gradient(ellipse at 50% 50%, transparent 35%, rgba(0,0,0,0.22) 100%),
    linear-gradient(180deg, #323848 0%, #222730 40%, #181b22 100%);
  border-right: 1px solid var(--xxl-border);
  box-shadow: 4px 0 18px rgba(0,0,0,0.6);
}

.xxl-sidebar-item {
  color: var(--xxl-text-muted);
  transition: background 0.15s, color 0.15s;
}
.xxl-sidebar-item:hover {
  background: rgba(255,255,255,0.05);
  color: var(--xxl-text);
}
.xxl-sidebar-item.is-current {
  background: rgba(222,167,38,0.15);
  color: var(--xxl-accent);
  border-left: 3px solid var(--xxl-accent);
  padding-left: calc(0.5rem - 3px);
  font-weight: 600;
}

/* Haupt-Container für die Task-Liste (Karten-Look) */
.xxl-card {
  background:
    linear-gradient(180deg,
      rgba(255,255,255,0.03) 0%,
      transparent 40%),
    var(--xxl-card);
  border: 1px solid var(--xxl-border);
  border-left: 4px solid var(--xxl-accent);
  border-radius: 0.75rem;
  box-shadow: var(--xxl-shadow);
}

/* Task-Zeile — jede Aufgabe ist eine eigene "Karte" */
.task-card-wrap {
  /* Kein perspective im Ruhezustand: zusammen mit preserve-3d/backface-visibility
     der Karte erzeugte es permanente GPU-Composited-Layer, die beim Tab-Wechsel
     (Fenster raus/rein, z.B. nach einem Link-Klick in neuem Tab) neu komponiert
     wurden → die Karten ruckelten um ein Sub-Pixel nach oben/unten. Der card-flip
     (rotateX 0–90–0) sieht ohne perspective minimal flacher aus — kaum sichtbar. */
  margin-bottom: 0.75rem;
  animation: card-enter 0.35s cubic-bezier(0.34, 1.56, 0.64, 1) both;
  position: relative;
}
/* Wenn das Action-Menü offen ist, die ganze Karte über alle anderen
   Karten heben. Sonst stapeln nachfolgende Karten in der DOM-Reihenfolge
   das Popover zu, weil jede Karte ihren eigenen Stacking-Kontext hat. */
.task-card-wrap.menu-open { z-index: 100; }

@keyframes card-enter {
  from { transform: translateY(-10px) scale(0.97); opacity: 0; }
  to   { transform: translateY(0) scale(1); opacity: 1; }
}
.task-card {
  position: relative;
  border: 1px solid;
  border-left-width: 5px;
  border-radius: 0.55rem;
  padding: 0.9rem 1rem;
  box-shadow: var(--xxl-shadow-sm);
  /* Kein preserve-3d/backface-visibility: erzeugten permanente GPU-Layer (s.o.).
     Für die card-flip (rotateX 0–90–0, man sieht nie die Rückseite) unnötig.
     transform aus der Transition genommen — der Hover-Lift ist weg, und die
     card-flip nutzt eine @keyframes-animation, keine Transition; so kann auch
     kein Repaint-bedingter transform-Sprung mit Overshoot nachfedern. */
  transition: box-shadow 0.2s;
}
.task-card:hover {
  /* Bewusst KEIN translateY beim Hover: ein vertikaler Lift (zumal mit
     Overshoot-Easing) verschiebt die Links in der Karte unter dem Cursor weg
     und man verklickt sich. Feedback kommt allein über den stärkeren Schatten. */
  box-shadow: var(--xxl-shadow-lg);
}

/* Exit-Animationen für verschobene/gelöschte Tasks */
@keyframes slide-out-right {
  to { transform: translateX(120%); opacity: 0; }
}
@keyframes slide-out-down {
  to { transform: translateY(60%) scale(0.85); opacity: 0; }
}
@keyframes slide-out-up {
  to { transform: translateY(-60%) scale(0.85); opacity: 0; }
}
.task-card-wrap.leaving-right   { animation: slide-out-right 0.35s cubic-bezier(0.4, 0, 1, 1) forwards; }
.task-card-wrap.leaving-later   { animation: slide-out-down  0.35s cubic-bezier(0.4, 0, 1, 1) forwards; }
.task-card-wrap.leaving-delete  { animation: slide-out-up    0.25s cubic-bezier(0.4, 0, 1, 1) forwards; }

/* Milchglas-Tint pro Status */
.task-card.is-open {
  border-color: var(--xxl-danger);
  background:
    linear-gradient(135deg,
      rgba(229,115,115,0.14) 0%,
      rgba(229,115,115,0.04) 45%,
      rgba(0,0,0,0.15) 100%),
    linear-gradient(180deg, rgba(255,255,255,0.03) 0%, transparent 40%),
    var(--xxl-card);
  backdrop-filter: blur(6px) saturate(1.1);
  -webkit-backdrop-filter: blur(6px) saturate(1.1);
}

/* Manuelle Farb-Marker (überschreibt den Status-Default-Rand) */
.task-card.color-red    { border-color: #E57373 !important; }
.task-card.color-orange { border-color: #FFB74D !important; }
.task-card.color-yellow { border-color: #FDD835 !important; }
.task-card.color-green  { border-color: #7CB342 !important; }
.task-card.color-teal   { border-color: #4DB6AC !important; }
.task-card.color-blue   { border-color: #64B5F6 !important; }
.task-card.color-purple { border-color: #BA68C8 !important; }
.task-card.color-gray   { border-color: #9E9E9E !important; }

.color-chooser {
  display: flex;
  gap: 0.25rem;
  padding: 0.25rem 0.4rem;
  background: var(--xxl-card);
  border: 1px solid var(--xxl-border);
  border-radius: 0.4rem;
  box-shadow: var(--xxl-shadow);
  position: absolute;
  right: 0;
  top: 100%;
  margin-top: 0.35rem;
  z-index: 10;
}
.color-dot {
  width: 1.1rem;
  height: 1.1rem;
  border-radius: 50%;
  border: 2px solid var(--xxl-border);
  cursor: pointer;
  transition: transform 0.12s;
}
.color-dot:hover { transform: scale(1.2); }
.color-dot.is-clear { background: transparent; border-style: dashed; }
.color-dot.color-red    { background: #E57373; border-color: #E57373; }
.color-dot.color-orange { background: #FFB74D; border-color: #FFB74D; }
.color-dot.color-yellow { background: #FDD835; border-color: #FDD835; }
.color-dot.color-green  { background: #7CB342; border-color: #7CB342; }
.color-dot.color-teal   { background: #4DB6AC; border-color: #4DB6AC; }
.color-dot.color-blue   { background: #64B5F6; border-color: #64B5F6; }
.color-dot.color-purple { background: #BA68C8; border-color: #BA68C8; }
.color-dot.color-gray   { background: #9E9E9E; border-color: #9E9E9E; }
.task-card.is-done {
  border-color: var(--xxl-success);
  background:
    linear-gradient(135deg,
      rgba(124,179,66,0.14) 0%,
      rgba(124,179,66,0.04) 45%,
      rgba(0,0,0,0.15) 100%),
    linear-gradient(180deg, rgba(255,255,255,0.03) 0%, transparent 40%),
    var(--xxl-card);
  backdrop-filter: blur(6px) saturate(1.1);
  -webkit-backdrop-filter: blur(6px) saturate(1.1);
}

/* Flip-Animation (horizontale Achse, schnell — wie eine umklappende Karte) */
@keyframes card-flip {
  0%   { transform: rotateX(0deg);    box-shadow: var(--xxl-shadow-sm); }
  50%  { transform: rotateX(90deg);   box-shadow: var(--xxl-shadow-lg); }
  100% { transform: rotateX(0deg);    box-shadow: var(--xxl-shadow-sm); }
}
.task-card.is-flipping {
  animation: card-flip 0.35s ease-in-out;
}

/* Quick-Add-Zeile ist flacher */
.xxl-quickadd {
  border: 1px dashed var(--xxl-border);
  border-radius: 0.55rem;
  padding: 0.75rem 1rem;
  background: rgba(255,255,255,0.02);
}

/* Task-Text pro Status */
.task-text-open   { color: var(--xxl-text); }
.task-text-active { color: var(--xxl-info); font-weight: 600; }
.task-text-done   { color: var(--xxl-text-dim); }

/* ---------- Rich-Content (Markdown-Light) ---------- */
.md-line {
  display: flex;
  gap: 0.5rem;
  align-items: baseline;
  padding: 0.12rem 0;
  line-height: 1.55;
  /* Lange URLs/Wörter ohne Leerzeichen müssen umbrechen, sonst sprengen
     sie die Karte. min-width: 0 erlaubt flex-Kinder zu schrumpfen. */
  min-width: 0;
  word-break: break-word;
  overflow-wrap: anywhere;
}
.md-line > * { min-width: 0; }
.md-empty { min-height: 0.6rem; padding: 0; }
.md-bullet-mark {
  color: var(--xxl-accent);
  font-weight: 700;
  min-width: 0.9rem;
  flex-shrink: 0;
  user-select: none;
}
.md-indent-1 { padding-left: 1.5rem; }
.md-indent-2 { padding-left: 3rem; }
.md-indent-3 { padding-left: 4.5rem; }
.md-indent-4 { padding-left: 6rem; }
.md-hr {
  border: none;
  border-top: 1px dashed var(--xxl-border);
  margin: 0.5rem 0;
}
.md-heading {
  font-weight: 700;
  color: var(--xxl-accent-light);
  font-size: 1.05em;
  padding-bottom: 0.2rem;
}

.md-line b, .md-line strong { color: var(--xxl-text); font-weight: 700; }

/* Inline-Status-Pills */
.status-pill {
  padding: 0.05rem 0.45rem;
  border-radius: 0.3rem;
  font-size: 0.82em;
  font-weight: 700;
  letter-spacing: 0.03em;
  white-space: nowrap;
}
.status-pill.open-pill   { background: rgba(229,115,115,0.18); color: var(--xxl-danger); }
.status-pill.active-pill { background: rgba(77,182,172,0.18); color: var(--xxl-info); }
.status-pill.done-pill   { background: rgba(124,179,66,0.18); color: var(--xxl-success); }

/* Editor-Textarea für Task-Content */
.task-edit-textarea {
  width: 100%;
  min-height: 5rem;
  background: var(--xxl-bg);
  color: var(--xxl-text);
  border: 1px solid var(--xxl-accent);
  border-radius: 0.4rem;
  padding: 0.6rem 0.75rem;
  font: inherit;
  line-height: 1.55;
  resize: vertical;
  outline: none;
  white-space: pre-wrap;
}
.task-edit-textarea:focus { box-shadow: 0 0 0 2px rgba(222,167,38,0.25); }

/* Rich-Editor (contenteditable) — visuell identisch zur textarea, plus Pill-Styling. */
.task-edit-rich {
  width: 100%;
  min-height: 5rem;
  background: var(--xxl-bg);
  color: var(--xxl-text);
  border: 1px solid var(--xxl-accent);
  border-radius: 0.4rem;
  padding: 0.6rem 0.75rem;
  font: inherit;
  line-height: 1.55;
  outline: none;
  white-space: pre-wrap;
  word-break: break-word;
  overflow-wrap: anywhere;
}
.task-edit-rich:focus { box-shadow: 0 0 0 2px rgba(222,167,38,0.25); }

/* Quill-Editor-Wrapper (Riegel ON, Notes-Style). Quill liefert eigene
   .ql-toolbar / .ql-container / .ql-editor — wir setzen nur die Hülle und
   überschreiben Farben für Dark-Theme-Konsistenz. */
.task-edit-quill-wrap { width: 100%; }
.task-edit-quill-wrap .ql-toolbar.ql-snow,
.task-edit-quill-wrap .ql-container.ql-snow {
  border-color: var(--xxl-accent);
  background: var(--xxl-bg);
}
.task-edit-quill-wrap .ql-toolbar.ql-snow {
  border-top-left-radius: 0.4rem;
  border-top-right-radius: 0.4rem;
}
.task-edit-quill-wrap .ql-container.ql-snow {
  border-bottom-left-radius: 0.4rem;
  border-bottom-right-radius: 0.4rem;
  min-height: 6rem;
  font-size: 1rem;
  color: var(--xxl-text);
}
.task-edit-quill-wrap .ql-editor { min-height: 5rem; }
.task-edit-quill-wrap .ql-editor.ql-blank::before { color: var(--xxl-text-muted); font-style: normal; }
.task-edit-quill-wrap .ql-snow .ql-stroke { stroke: var(--xxl-text-muted); }
.task-edit-quill-wrap .ql-snow .ql-fill { fill: var(--xxl-text-muted); }
.task-edit-quill-wrap .ql-snow .ql-picker { color: var(--xxl-text-muted); }
.task-edit-quill-wrap .ql-snow.ql-toolbar button:hover .ql-stroke,
.task-edit-quill-wrap .ql-snow.ql-toolbar button.ql-active .ql-stroke {
  stroke: var(--xxl-accent);
}
.task-edit-quill-wrap .ql-snow.ql-toolbar button:hover .ql-fill,
.task-edit-quill-wrap .ql-snow.ql-toolbar button.ql-active .ql-fill {
  fill: var(--xxl-accent);
}
.task-edit-quill-wrap .ql-editor a {
  color: var(--xxl-accent-light);
  text-decoration: underline;
}

/* Schieberegler "Bearbeitung verriegeln" — oben rechts im Edit-Modus.
   Sieht aus wie ein klassischer iOS-style Toggle-Switch. */
.edit-lock-switch {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  cursor: pointer;
  user-select: none;
  background: transparent;
  border: 1px solid transparent;
  padding: 0.15rem 0.4rem;
  border-radius: 0.4rem;
  color: var(--xxl-text-muted);
  transition: color 0.15s, border-color 0.15s;
}
.edit-lock-switch:hover {
  border-color: var(--xxl-border);
  color: var(--xxl-text);
}
.edit-lock-switch.is-on {
  color: var(--xxl-accent);
  border-color: rgba(222,167,38,0.4);
}
.edit-lock-switch .track {
  width: 28px;
  height: 16px;
  border-radius: 999px;
  background: rgba(255,255,255,0.12);
  position: relative;
  flex-shrink: 0;
  transition: background 0.18s;
}
.edit-lock-switch .track::after {
  content: '';
  position: absolute;
  top: 2px; left: 2px;
  width: 12px; height: 12px;
  background: rgba(255,255,255,0.6);
  border-radius: 50%;
  transition: transform 0.18s, background 0.18s;
}
.edit-lock-switch.is-on .track {
  background: rgba(222,167,38,0.55);
}
.edit-lock-switch.is-on .track::after {
  transform: translateX(12px);
  background: var(--xxl-accent);
}

/* Markdown-Link-Pille im Rich-Editor — atomar (contenteditable=false), klickbar
   für das Edit-Popover. user-select:text erlaubt das Mit-Markieren beim
   Drag über mehrere Elemente (user-select:all würde die Pille beim Range-
   Drag ganz überspringen, user-select:none auch — beides hatte den Copy-Bug).
   Pill-Text ist visuell selektierbar, aber per contenteditable=false nicht
   editierbar. */
.md-link-pill {
  display: inline-block;
  padding: 1px 6px;
  margin: 0 1px;
  border-radius: 0.3rem;
  background: rgba(222,167,38,0.12);
  color: var(--xxl-accent-light);
  border: 1px solid rgba(222,167,38,0.3);
  cursor: pointer;
  user-select: text;
  font-size: 0.95em;
  text-decoration: none;
}
.md-link-pill:hover {
  background: rgba(222,167,38,0.22);
  border-color: var(--xxl-accent);
}
.md-link-pill::before { content: '🔗 '; opacity: 0.7; font-size: 0.85em; }


/* Status-Button (Checkbox-artig) */
.status-btn {
  width: 24px;
  height: 24px;
  border-radius: 5px;
  border: 2px solid;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 0.8rem;
  line-height: 1;
  color: white;
  cursor: pointer;
  transition: transform 0.12s;
  flex-shrink: 0;
}
.status-btn:hover:not(:disabled) { transform: scale(1.08); }
.status-btn:disabled { cursor: not-allowed; opacity: 0.5; }

.status-btn.is-open    { border-color: var(--xxl-danger);  background: transparent; }
.status-btn.is-done    { border-color: var(--xxl-success); background: var(--xxl-success); }
.status-btn.is-selected {
  border-color: var(--xxl-accent) !important;
  background: rgba(222,167,38,0.25) !important;
  box-shadow: 0 0 0 3px rgba(222,167,38,0.35);
}

/* Legende-Pill */
.legend-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--xxl-text-muted);
  font-size: 0.8rem;
}
.legend-pill .status-btn { cursor: default; pointer-events: none; }

/* Input-Zeile (neue Aufgabe) */
.xxl-new-input {
  background: transparent;
  border: 0;
  color: var(--xxl-text);
  font-size: 1rem;
  width: 100%;
  padding: 0.35rem 0;
  outline: none;
}
.xxl-new-input::placeholder { color: var(--xxl-text-dim); }

/* contenteditable aktive Zeile */
[contenteditable]:focus {
  outline: none;
  background: rgba(222,167,38,0.08);
  border-radius: 4px;
  padding: 0 4px;
}

/* Links im Task-Text */
.task-content a {
  color: var(--xxl-accent-light);
  text-decoration: underline;
  /* Lange URLs müssen umbrechen, sonst sprengen sie das Karten-Layout */
  word-break: break-word;
  overflow-wrap: anywhere;
}
.task-content a:hover { color: var(--xxl-accent); }
/* Auch der Karten-Inhalt selbst muss umbrechen (z.B. lange Wörter ohne Leerzeichen) */
.task-content {
  word-break: break-word;
  overflow-wrap: anywhere;
}
/* Desktop-Edit-Affordance: Klasse wird im Template nur gesetzt wenn !isMobile && !readOnly */
.task-content-clickable { cursor: pointer; }

/* Gold-Button */
.xxl-btn-accent {
  background: linear-gradient(135deg, var(--xxl-accent) 0%, var(--xxl-accent-hover) 100%);
  color: #1a1a1a;
  font-weight: 600;
  border: none;
  padding: 0.5rem 0.9rem;
  border-radius: 0.5rem;
  cursor: pointer;
  transition: transform 0.12s, box-shadow 0.12s;
  box-shadow: var(--xxl-shadow-sm);
}
.xxl-btn-accent:hover {
  transform: translateY(-1px);
  box-shadow: var(--xxl-shadow);
}

/* Ghost-Button (unauffällig) */
.xxl-btn-ghost {
  background: transparent;
  color: var(--xxl-text-muted);
  border: 1px solid var(--xxl-border);
  padding: 0.35rem 0.75rem;
  border-radius: 0.4rem;
  cursor: pointer;
  transition: all 0.15s;
}
.xxl-btn-ghost:hover {
  color: var(--xxl-text);
  border-color: var(--xxl-accent);
  background: rgba(255,255,255,0.03);
}

/* Icon-Button (Header-Pfeile, Menü, etc.) */
.xxl-icon-btn {
  background: transparent;
  color: var(--xxl-text-muted);
  border: none;
  cursor: pointer;
  font-size: 1.3rem;
  line-height: 1;
  padding: 0.25rem 0.4rem;
  border-radius: 0.3rem;
  transition: color 0.12s, background 0.12s;
}
.xxl-icon-btn:hover {
  color: var(--xxl-accent);
  background: rgba(255,255,255,0.04);
}

/* Delete-× dezent, wird bei Hover rot */
.xxl-delete-btn {
  background: transparent;
  border: none;
  color: var(--xxl-text-dim);
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  padding: 0 0.4rem;
}
.xxl-delete-btn:hover { color: var(--xxl-danger); }

/* Defer drop-zone */
.defer-zone {
  background: rgba(255,255,255,0.03);
  border: 2px dashed var(--xxl-border);
  color: var(--xxl-text-muted);
  transition: all 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
  user-select: none;
}
.defer-zone.drag-over,
.defer-zone:hover {
  background: rgba(222,167,38,0.12);
  border-color: var(--xxl-accent);
  color: var(--xxl-accent);
  transform: scale(1.08);
}

/* Während eine Task gezogen wird: Drop-Zonen sind dezent als Targets sichtbar
   (farbig abgesetzter Rand + Text), aber ohne Glow oder Pulse — sonst leuchten
   alle Zonen gleichzeitig und der Nutzer weiß nicht, wo er gerade hovert. */
body.is-dragging .defer-zone {
  border-color: var(--xxl-accent);
  color: var(--xxl-accent);
}

/* Nur die Zone, in der SortableJS das Ghost-Element gelandet hat (= aktive
   Drop-Zone), leuchtet mit Glow + Pulse-Animation. */
body.is-dragging .defer-zone:has(.sortable-ghost) {
  background: rgba(222,167,38,0.18);
  box-shadow: 0 0 28px rgba(222,167,38,0.45);
  animation: pulse-zone 1s ease-in-out infinite;
}
@keyframes pulse-zone {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.08); box-shadow: 0 0 36px rgba(222,167,38,0.6); }
}

/* Drag-Over: das von SortableJS in die Drop-Zone verschobene <li> nicht
   visuell rendern. Die Zone behält ihre Originalgröße + "→ Morgen"-Label.
   Sortable's onAdd-Logik feuert weiterhin (reagiert auf Pointer-Events,
   nicht auf Layout). Selektor eng auf direkte Kinder begrenzt — das
   normale Reorder-Verhalten in der Task-<ul> bleibt unberührt. */
.defer-zone > .sortable-ghost,
.defer-zone > .sortable-chosen,
.defer-zone > .sortable-fallback {
  display: none !important;
}

/* Quick-Action-Buttons rechts an jeder Task */
.xxl-action-btn {
  background: transparent;
  border: 1px solid transparent;
  color: var(--xxl-text-dim);
  cursor: pointer;
  padding: 0.25rem 0.55rem;
  border-radius: 0.35rem;
  font-size: 0.95rem;
  line-height: 1;
  font-weight: 600;
  transition: all 0.15s cubic-bezier(0.34, 1.56, 0.64, 1);
  min-width: 1.8rem;
  text-align: center;
}
.xxl-action-btn:hover {
  color: var(--xxl-accent);
  border-color: var(--xxl-accent);
  background: rgba(222,167,38,0.12);
  transform: scale(1.12);
}
.xxl-action-btn:active { transform: scale(0.95); }
.xxl-action-btn.is-danger:hover {
  color: var(--xxl-danger);
  border-color: var(--xxl-danger);
  background: rgba(229,115,115,0.12);
}
/* Labels werden nur im Mobile-Popover gezeigt (siehe is-mobile-open).
   Desktop zeigt kompakte Icon-Buttons. */
.xxl-action-btn .action-label { display: none; }
.xxl-action-group {
  display: flex;
  gap: 0.3rem;
  opacity: 0;
  transform: translateX(4px);
  transition: opacity 0.15s, transform 0.15s;
  pointer-events: none;
}
.task-card:hover .xxl-action-group {
  opacity: 1;
  transform: translateX(0);
  pointer-events: auto;
}
@media (hover: none) {
  /* Touch-Geräte: Icons immer sichtbar */
  .xxl-action-group { opacity: 1; transform: none; pointer-events: auto; }
}

/* Status-Button: kräftigerer Hover-Effekt */
.status-btn {
  transition: transform 0.15s cubic-bezier(0.34, 1.56, 0.64, 1),
              box-shadow 0.15s;
}
.status-btn:hover:not(:disabled) {
  transform: scale(1.15);
  box-shadow: 0 0 0 4px rgba(222,167,38,0.15);
}
.status-btn.is-done:hover:not(:disabled) {
  box-shadow: 0 0 0 4px rgba(124,179,66,0.25);
}

/* Toast */
.xxl-toast {
  background: linear-gradient(135deg, var(--xxl-card) 0%, var(--xxl-header-base) 100%);
  color: var(--xxl-text);
  border-left: 3px solid var(--xxl-accent);
  box-shadow: var(--xxl-shadow);
}

/* Version-Badge */
.xxl-notes-fab {
  background: linear-gradient(135deg, var(--xxl-accent) 0%, var(--xxl-accent-hover) 100%);
  color: #1a1a1a;
  font-weight: 600;
  border: none;
  padding: 0.7rem 1.1rem;
  border-radius: 999px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  box-shadow: var(--xxl-shadow), 0 6px 18px rgba(0,0,0,0.25);
  transition: transform 0.12s, box-shadow 0.12s;
}
.xxl-notes-fab:hover {
  transform: translateY(-2px);
  box-shadow: var(--xxl-shadow), 0 10px 24px rgba(0,0,0,0.35);
}

/* Modal */
.xxl-modal-backdrop {
  background: rgba(0,0,0,0.7);
  backdrop-filter: blur(4px);
}
.xxl-modal {
  background: var(--xxl-content);
  border: 1px solid var(--xxl-border);
  border-radius: 0.75rem;
  box-shadow: var(--xxl-shadow-lg);
  color: var(--xxl-text);
}
.xxl-modal-header {
  border-bottom: 1px solid var(--xxl-border);
  background: linear-gradient(180deg, rgba(255,255,255,0.04) 0%, transparent 100%);
}

/* Form-Eingaben (Login, Invite) */
.xxl-input {
  background: var(--xxl-card);
  border: 1px solid var(--xxl-border);
  color: var(--xxl-text);
  padding: 0.55rem 0.75rem;
  border-radius: 0.4rem;
  width: 100%;
  outline: none;
  transition: border-color 0.15s;
}
.xxl-input:focus { border-color: var(--xxl-accent); }
.xxl-input::placeholder { color: var(--xxl-text-dim); }

/* Keyboard-Kbd-Tag */
.xxl-kbd {
  display: inline-block;
  padding: 0.15rem 0.55rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.85rem;
  color: var(--xxl-accent-light);
  background: var(--xxl-card);
  border: 1px solid var(--xxl-border);
  border-bottom: 2px solid var(--xxl-border);
  border-radius: 0.3rem;
  min-width: 2rem;
  text-align: center;
  box-shadow: 0 1px 0 rgba(0,0,0,0.3);
}

/* Auth-Card (Login/Invite-Formulare) */
.xxl-auth-card {
  background:
    linear-gradient(180deg, rgba(255,255,255,0.04) 0%, transparent 50%),
    var(--xxl-content);
  border: 1px solid var(--xxl-border);
  border-top: 1px solid rgba(255,255,255,0.15);
  border-radius: 0.75rem;
  box-shadow: var(--xxl-shadow-lg);
}

/* SortableJS */
.sortable-ghost { opacity: 0.4; }
.sortable-drag  { cursor: grabbing; box-shadow: var(--xxl-shadow-lg); }

/* --- Mobile-Layout (< 768px) -------------------------------------------- */
/* PWA-Standalone auf Mobile: Safe-Area-Insets (Notch oben, Home-Indicator
   unten) müssen respektiert werden, damit Inhalte nicht hinter Hardware-
   Elementen verschwinden. */
@supports (padding: env(safe-area-inset-top)) {
  .xxl-header {
    padding-top: calc(0.75rem + env(safe-area-inset-top)) !important;
    padding-left: calc(0.75rem + env(safe-area-inset-left)) !important;
    padding-right: calc(0.75rem + env(safe-area-inset-right)) !important;
  }
}

/* Pull-to-Refresh deaktivieren (System-Geste). Auf einzelnen Scroll-Containern
   reicht overscroll-behavior: contain — der äußere html/body-Container braucht
   none, sonst greift's auf System-Ebene. */
/* overflow-x:hidden gehört NUR auf body (s. body-Regel unten). Auf `html` zwingt es
   overflow-y rechnerisch auf `auto` und macht html zum Scroll-Container — damit entfällt
   die body->Viewport-Overflow-Propagation und body wird ein verschachtelter Scroller,
   der nur seinen eigenen Inhalt umfasst (also selbst nichts zu scrollen hat). Zusammen mit
   overscroll-behavior-y:none wird Mausrad-Scrollen dann geschluckt: der Scrollbalken (am
   Viewport) zieht noch, das Rad ist aber tot. overscroll-behavior-y darf auf beiden bleiben
   (kein Scroll-Container-Effekt) und propagiert von body zum Viewport. */
html, body { overscroll-behavior-y: none; }

/* Umfrage-Tool: normales Dokument-Scrolling (kein Fixed-Layout wie im board).
   overflow-x gehört NUR auf body — auf html würde es html zum Scroll-Container
   machen und das Mausrad totlegen (A.0.2.4). touch-action:pan-y hält vertikales
   Wischen erhalten. board's body>div.flex>main-Layout + aside.xxl-sidebar-Regeln
   entfernt — matchten das Umfrage-DOM nicht (A.0.4.7). */
html { height: auto; }
body {
  min-height: 100dvh;
  overflow-x: hidden;
  touch-action: pan-y;
}

/* (board-Reste entfernt — totes DOM, A.0.4.7: .task-img-resize-handle,
   .mobile-search-overlay, .xxl-camera-btn) */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0);
  white-space: nowrap; border: 0;
}

@media (max-width: 767px) {
  /* Header: weniger Padding, kleineres Datum */
  .xxl-header { padding: 0.75rem 0.75rem !important; gap: 0.25rem; }
  .xxl-header h1 { font-size: 1rem !important; }
  .xxl-header .xxl-icon-btn { font-size: 1rem; padding: 0.2rem 0.3rem; min-width: 44px; min-height: 44px; }

  /* Touch-Targets — etwas reduziert (38 px statt 44 px), damit alle 5 Action-
     Buttons + Status-Box + Content in eine schmale Phone-Breite passen ohne
     horizontalen Overflow. 38 px sind erfahrungsgemäß noch komfortabel. */
  .status-btn { min-width: 38px; min-height: 38px; }
  .xxl-action-btn { min-width: 38px; min-height: 38px; padding: 0.4rem 0.45rem; font-size: 1rem; }
  .task-img-resize-handle { width: 22px; height: 22px; right: -4px; bottom: -4px; }

  /* Mobile: alle Action-Buttons werden hinter einem ⋮-Trigger versteckt.
     Tap auf den Trigger öffnet ein Popover (absolut positioniert) mit den
     Aktionen als kompakte Liste. */
  .xxl-action-trigger {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 38px;
    min-height: 38px;
    background: transparent;
    border: 1px solid transparent;
    color: var(--xxl-text-muted);
    border-radius: 0.35rem;
    font-size: 1.25rem;
    line-height: 1;
    cursor: pointer;
  }
  .xxl-action-trigger:hover { color: var(--xxl-accent); border-color: var(--xxl-accent); }

  /* Default: Action-Group versteckt; nur sichtbar wenn is-mobile-open */
  .xxl-action-group { display: none !important; }
  .xxl-action-group.is-mobile-open {
    display: flex !important;
    flex-direction: column;
    align-items: stretch;
    position: absolute;
    right: 0;
    top: 2.4rem;
    z-index: 200;
    background: var(--xxl-card);
    border: 1px solid var(--xxl-accent);
    border-radius: 0.5rem;
    padding: 0.4rem;
    min-width: 9rem;
    box-shadow: 0 12px 32px rgba(0,0,0,0.75);
  }
  .xxl-action-group.is-mobile-open .xxl-action-btn {
    text-align: left;
    min-width: 100%;
    padding: 0.5rem 0.75rem;
    font-size: 1rem;
    border-radius: 0.35rem;
    display: flex;
    align-items: center;
    gap: 0.6rem;
  }
  .xxl-action-group.is-mobile-open .action-icon {
    display: inline-block;
    width: 1.25rem;
    text-align: center;
    flex-shrink: 0;
  }
  .xxl-action-group.is-mobile-open .action-label { display: inline; }
  .xxl-action-group.is-mobile-open .xxl-action-btn:hover {
    background: var(--xxl-card-hover);
    border-color: var(--xxl-accent);
  }
  /* Task-Card kompakter, Content darf voll ausnutzen */
  .task-card > .flex-1 { min-width: 0; }

  /* Inputs ≥ 16px font-size verhindert iOS-Auto-Zoom beim Fokus */
  .xxl-new-input,
  .task-edit-textarea,
  input[type="text"],
  input[type="email"],
  input[type="password"],
  input[type="search"],
  textarea { font-size: 16px !important; }

  /* dvh nutzen für Modals (mit vh-Fallback bereits durch vorherige Deklaration) */
  .xxl-modal { max-height: calc(100dvh - 1rem); }

  /* Sidebar fällt in ein Overlay mit dunklem Backdrop */
  .xxl-sidebar {
    position: fixed !important;
    top: 0; left: 0; bottom: 0;
    width: 80vw !important;
    max-width: 18rem;
    z-index: 40;
    box-shadow: 4px 0 25px rgba(0,0,0,0.7);
  }

  /* Main nutzt die volle Breite, kein Platz mehr für rechte Drop-Zone */
  main { padding: 0.75rem !important; padding-right: 0.75rem !important; }

  /* Such-Widget im Header wird durch Lupen-Icon ersetzt (siehe JS isMobile).
     WICHTIG: nicht nur das Input verstecken, sondern den ganzen .header-search-
     Container kollabieren (er hat flex-1) — sonst schluckt er den Platz vom
     User-Menü-Button rechts. */
  .header-search { display: none !important; }
  .mobile-search-trigger { display: inline-flex !important; min-width: 44px; min-height: 44px; }

  /* Action-Icons immer sichtbar, dichter */
  .xxl-action-group { opacity: 1 !important; transform: none !important; pointer-events: auto !important; gap: 0.15rem; }
  .xxl-action-btn { padding: 0.3rem 0.45rem; min-width: 1.6rem; }

  /* Rechte Drop-Zone weg, stattdessen Bottom-Bar */
  .defer-zone.fixed,
  body > div.fixed > .defer-zone { display: none; }

  /* Neue Mobile-Bottom-Bar für Morgen / Später / Heute */
  .mobile-action-bar {
    position: fixed;
    bottom: 0; left: 0; right: 0;
    display: flex;
    justify-content: space-around;
    padding: 0.5rem 0.5rem 0.7rem;
    background: var(--xxl-header-base);
    border-top: 2px solid var(--xxl-accent);
    box-shadow: 0 -4px 14px rgba(0,0,0,0.4);
    z-index: 20;
  }
  .mobile-action-bar button {
    flex: 1;
    padding: 0.6rem 0.4rem;
    color: var(--xxl-text-muted);
    background: transparent;
    border: none;
    cursor: pointer;
    font-size: 0.8rem;
    border-radius: 0.4rem;
  }
  .mobile-action-bar button:hover { background: rgba(255,255,255,0.05); color: var(--xxl-accent); }

  /* FAB höher damit bottom-bar nicht drauf liegt */
  .xxl-notes-fab { bottom: 4.5rem !important; }
  [x-show="lastDeleted"]  { bottom: 7rem !important; }

  /* Task-Karten mit weniger Padding */
  .task-card { padding: 0.7rem 0.8rem; }
  .task-content { font-size: 0.95rem; }
}
@media (min-width: 768px) {
  .mobile-action-bar { display: none; }
  .mobile-search-trigger { display: none !important; }
  .xxl-camera-btn { display: none; }
  /* Desktop: Action-Trigger versteckt; Action-Group inline sichtbar (Default) */
  .xxl-action-trigger { display: none; }

  /* Action-Group raus aus dem Flex-Flow → Content nutzt volle Karten-Breite.
     Buttons floaten on-hover rechts oben über der Karte. Backdrop sorgt
     dafür, dass Icons über Text noch lesbar bleiben. */
  .xxl-action-group {
    position: absolute;
    right: 0.6rem;
    top: 0.6rem;
    margin-top: 0;
    padding: 0.15rem;
    border-radius: 0.4rem;
    background: rgba(0, 0, 0, 0.4);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
  }
}

/* Notes-Modal Mobile-Tweaks: Toolbar horizontal scrollbar (eine Zeile),
   Editor 16px font (kein iOS-Auto-Zoom) */
@media (max-width: 767px) {
  #notesEditorContainer .ql-toolbar.ql-snow {
    flex-wrap: nowrap !important;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    white-space: nowrap;
  }
  #notesEditorContainer .ql-toolbar.ql-snow .ql-formats { display: inline-flex; flex-shrink: 0; }
  #notesEditorContainer .ql-editor { font-size: 16px; }
}

/* Scrollbar dezent im Dark-Theme */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: var(--xxl-bg); }
::-webkit-scrollbar-thumb {
  background: var(--xxl-border);
  border-radius: 5px;
}

/* PWA Window-Controls-Overlay: Titelleiste transparent,
   nur Min/Max/Close oben rechts. Header bekommt rechts genug
   Reserve, damit das User-Menü nicht unter den OS-Buttons sitzt. */
@media (display-mode: window-controls-overlay) {
  .xxl-header {
    padding-right: 200px;
    -webkit-app-region: drag;
  }
  .xxl-header button,
  .xxl-header input,
  .xxl-header a,
  .xxl-header select {
    -webkit-app-region: no-drag;
  }
}

/* Quill-Editor (Notizen) ans Theme anpassen */
#notesEditorContainer .ql-toolbar.ql-snow {
  background: var(--xxl-card);
  border: 1px solid var(--xxl-border);
  border-bottom: none;
  border-radius: 0;
}
#notesEditorContainer .ql-container.ql-snow {
  background: var(--xxl-bg);
  color: var(--xxl-text);
  border: 1px solid var(--xxl-border);
  font-size: 1rem;
  font-family: inherit;
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
#notesEditorContainer .ql-editor {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  color: var(--xxl-text);
  padding: 1.25rem 1.5rem;
}
#notesEditorContainer .ql-editor.ql-blank::before {
  color: var(--xxl-text-dim);
  font-style: normal;
}
/* Toolbar-Icons im Dark-Theme sichtbar */
[data-theme="dark"] #notesEditorContainer .ql-snow .ql-stroke { stroke: var(--xxl-text-muted); }
[data-theme="dark"] #notesEditorContainer .ql-snow .ql-fill,
[data-theme="dark"] #notesEditorContainer .ql-snow .ql-picker-label,
[data-theme="dark"] #notesEditorContainer .ql-snow .ql-picker-options { color: var(--xxl-text-muted); fill: var(--xxl-text-muted); }
[data-theme="dark"] #notesEditorContainer .ql-snow .ql-picker-options {
  background: var(--xxl-card);
  border-color: var(--xxl-border);
}
[data-theme="dark"] #notesEditorContainer .ql-snow .ql-picker-item:hover,
[data-theme="dark"] #notesEditorContainer .ql-snow .ql-picker-item.ql-selected {
  color: var(--xxl-accent);
}
[data-theme="dark"] #notesEditorContainer .ql-snow.ql-toolbar button:hover .ql-stroke,
[data-theme="dark"] #notesEditorContainer .ql-snow.ql-toolbar button.ql-active .ql-stroke {
  stroke: var(--xxl-accent);
}
[data-theme="dark"] #notesEditorContainer .ql-snow.ql-toolbar button:hover .ql-fill,
[data-theme="dark"] #notesEditorContainer .ql-snow.ql-toolbar button.ql-active .ql-fill {
  fill: var(--xxl-accent);
}
[data-theme="dark"] #notesEditorContainer .ql-snow .ql-tooltip {
  background: var(--xxl-card);
  color: var(--xxl-text);
  border-color: var(--xxl-border);
  box-shadow: none;
}
[data-theme="dark"] #notesEditorContainer .ql-snow .ql-tooltip input[type="text"] {
  background: var(--xxl-bg);
  color: var(--xxl-text);
  border-color: var(--xxl-border);
}
/* Eingebettete Bilder schön begrenzen */
#notesEditorContainer .ql-editor img { max-width: 100%; height: auto; }
::-webkit-scrollbar-thumb:hover { background: var(--xxl-accent); }

/* ==========================================================
   Kontakte / Inbox — B.1.8.0
   ========================================================== */

/* Modal-Backdrop + Card */
.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 50;
  padding: 1rem;
}
/* Tailwind's .hidden ist display:none — aber unsere .modal-backdrop hat
   display:flex, und styles.css lädt nach tailwind.css. Wir geben .hidden
   in dieser Kombination wieder Vorrang. (Wurde unter cdn.tailwindcss.com
   nie sichtbar, weil dieser CDN-Loader die CSS dynamisch nachgeladen hat.) */
.modal-backdrop.hidden { display: none; }
.modal-card {
  background: white;
  color: #1f2937; /* gray-800 — Body-Default ist weiß (dark theme), Card braucht expliziten dunklen Text */
  border-radius: 0.5rem;
  width: 100%;
  max-height: 90vh;
  overflow: hidden;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
}
/* Tailwind text-gray-* Klassen werden vom Body-Color überschrieben — innerhalb der
   Modal-Card wieder sichtbar machen */
.modal-card .text-gray-800 { color: #1f2937; }
.modal-card .text-gray-700 { color: #374151; }
.modal-card .text-gray-600 { color: #4b5563; }
.modal-card .text-gray-500 { color: #6b7280; }
.modal-card .text-gray-400 { color: #9ca3af; }
.modal-card label { color: #1f2937; }
.modal-card h1, .modal-card h2, .modal-card h3 { color: #1f2937; }
.modal-card input, .modal-card select, .modal-card textarea {
  color: #1f2937;
  background: white;
}
/* Buttons mit "border" (z.B. Abbrechen) brauchen expliziten Text-Color */
.modal-card button.border:not(.bg-blue-600):not(.bg-green-600):not(.bg-red-600):not(.bg-blue-700):not(.bg-green-700) {
  color: #1f2937;
}

/* z-60 für Sub-Modals (über Standard-Modal mit z-50) */
.z-60 { z-index: 60 !important; }

/* Body während Modal sperren (kein Page-Scroll) */
body.modal-open { overflow: hidden; }

/* Inbox-Tabs */
.tab-btn {
  padding: 0.5rem 1rem;
  border-bottom: 2px solid transparent;
  cursor: pointer;
  background: transparent;
  border-radius: 0;
}
.tab-btn:hover {
  background-color: #f9fafb;
}
.tab-btn.tab-active {
  border-bottom-color: #2563eb;
  font-weight: 600;
  color: #2563eb;
}

/* Task-Badge "📨 von X" */
.task-from-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  background-color: #eff6ff;
  color: #1d4ed8;
  font-size: 0.75rem;
  padding: 0.125rem 0.5rem;
  border-radius: 9999px;
  margin-right: 0.5rem;
  white-space: nowrap;
}

/* Permission-Toggle Cursor */
input[type="checkbox"][data-perm] {
  cursor: pointer;
  width: 1rem;
  height: 1rem;
}
input[type="checkbox"][data-perm]:disabled {
  cursor: not-allowed;
}

/* Bell-Counter pill kontrast + min-width (Tailwind-Klassen sind bereits gesetzt;
   das hier nur als Sicherheitsnetz, falls Tailwind-CDN spät lädt) */
#bell-counter, #bell-counter-mobile {
  min-width: 1.25rem;
  text-align: center;
  line-height: 1;
}

/* Mobile: Desktop-Bell aus, Mobile-Bell aktiv */
@media (max-width: 767px) {
  #bell-btn { display: none !important; }
}
@media (min-width: 768px) {
  #bell-btn-mobile { display: none !important; }
}

/* === weclapp-Auftragsprüfung (3-Spalten-Maske) === */
.weclapp-box {
  width: 100%;
  height: 100%;
  max-width: 1500px;
  max-height: 94vh;
  display: flex;
  flex-direction: column;
}
.weclapp-grid {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: 210px minmax(0, 1fr) 360px;
}
.weclapp-col { overflow-y: auto; min-height: 0; }
.weclapp-col-pdf {
  background: var(--xxl-bg);
  display: flex;
  flex-direction: column;
  border-left: 1px solid var(--xxl-border);
  border-right: 1px solid var(--xxl-border);
  overflow: hidden;
}
.weclapp-col-pos { padding: 0.75rem; display: flex; flex-direction: column; gap: 0.6rem; }
.weclapp-pdf-frame { width: 100%; height: 100%; border: 0; background: #fff; }
.weclapp-pdf-empty {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--xxl-text-dim);
  font-size: 0.85rem;
}
.weclapp-order-btn {
  display: block;
  width: 100%;
  text-align: left;
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--xxl-border);
  cursor: pointer;
}
.weclapp-order-btn.is-current { background: var(--xxl-card-hover); }
.weclapp-order-num { font-family: monospace; font-size: 0.85rem; color: var(--xxl-text); }
.weclapp-order-remark {
  font-size: 0.7rem;
  color: var(--xxl-text-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.weclapp-pos-card {
  background: var(--xxl-card);
  border: 1px solid var(--xxl-border);
  border-radius: 0.375rem;
  padding: 0.5rem;
}
.weclapp-pos-badge {
  font-family: monospace;
  font-size: 0.7rem;
  background: var(--xxl-accent);
  color: #000;
  padding: 0.05rem 0.4rem;
  border-radius: 0.25rem;
  white-space: nowrap;
}
.weclapp-pos-meta {
  font-size: 0.68rem;
  color: var(--xxl-text-dim);
  margin-top: 0.25rem;
}
.weclapp-pos-desc {
  font-size: 0.7rem;
  color: var(--xxl-text-muted);
  margin-top: 0.25rem;
  line-height: 1.3;
  max-height: 4.5em;
  overflow-y: auto;
}
.weclapp-preview {
  font-family: monospace;
  font-size: 0.8rem;
  background: var(--xxl-bg);
  border: 1px dashed var(--xxl-border);
  border-radius: 0.375rem;
  padding: 0.5rem;
  color: var(--xxl-accent);
  word-break: break-word;
}
/* Schmal: Spalten stapeln (Hauptnutzung ist Desktop). */
@media (max-width: 900px) {
  .weclapp-grid { grid-template-columns: 1fr; grid-auto-rows: minmax(0, auto); }
  .weclapp-col-pdf { min-height: 50vh; }
}

/* Eigenständige Seite /board/auftragspruefung (Vollbild statt Overlay) */
.weclapp-page {
  height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--xxl-bg);
  color: var(--xxl-text);
}
.weclapp-page-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--xxl-border);
  background: var(--xxl-header-base);
  flex-shrink: 0;
}
.weclapp-page-main {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}


/* ============================================================
   Quizplattform — eigene Komponenten (A.1.0.0)
   Ersetzt den Umfrage-Block (.survey-tile/.scale-btn/.preview-*), der mit
   dem Umfrage-Tool zusammen entfernt wurde.
   ============================================================ */

/* Eine Frage im Editor. Etwas abgesetzt vom Runden-Kasten, damit man auf einen
   Blick sieht, was zu welcher Runde gehört. */
.quiz-question {
  background: var(--xxl-content);
  border: 1px solid var(--xxl-border);
  border-radius: 10px;
}

/* Der abwertende Knopf für Löschvorgänge. Bewusst nicht knallrot gefüllt,
   sondern als Kontur — gefüllt zieht er im Editor mehr Blick auf sich als die
   Knöpfe, die man tatsächlich ständig braucht. */
.xxl-btn-danger {
  background: transparent;
  color: var(--xxl-danger);
  border: 1px solid var(--xxl-danger);
  padding: 0.35rem 0.75rem;
  border-radius: 0.4rem;
  cursor: pointer;
  font-weight: 600;
  transition: all 0.15s;
}
.xxl-btn-danger:hover {
  background: var(--xxl-danger);
  color: #1a1a1a;
}

/* Rückfrage vor dem Löschen */
.quiz-modal-backdrop {
  position: fixed; inset: 0; z-index: 1000;
  display: flex; align-items: center; justify-content: center;
  padding: 16px;
  background: rgba(0, 0, 0, 0.66);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}
.quiz-modal {
  max-width: 460px;
  width: 100%;
}

/* Ausgegraute Sortierpfeile: ohne das sehen deaktivierte Knöpfe genauso aus
   wie aktive, und man klickt ins Leere. */
.xxl-btn-ghost:disabled {
  opacity: 0.35;
  cursor: default;
}
.xxl-btn-ghost:disabled:hover {
  color: var(--xxl-text-muted);
  border-color: var(--xxl-border);
  background: transparent;
}

/* ============================================================
   Spielansichten (A.2.0.0) — Buzzer, Bühne, Pult
   ============================================================ */

/* ---- Spieler-Handy: der Buzzer ------------------------------------------- */

.buzzer-body {
  background: var(--xxl-bg);
  min-height: 100vh;
  min-height: 100dvh;              /* dvh: sonst schiebt die Adressleiste den Knopf aus dem Bild */
  margin: 0;
  overscroll-behavior: none;        /* kein Gummiband-Scrollen beim hektischen Drücken */
  touch-action: manipulation;       /* kein Doppeltipp-Zoom */
  -webkit-user-select: none;
  user-select: none;
}

.buzzer-join {
  min-height: 100dvh;
  display: flex; align-items: center; justify-content: center;
  padding: 1rem;
  /* Seit A.4.2.0 steht die Rechtliches-Zeile als ZWEITES Kind in diesem
     Flex-Rahmen — ohne column landete sie neben der Karte statt darunter.
     Auf dem Handy fiel es nicht auf, weil dort ohnehin kein Platz daneben ist;
     auf dem Rechner stand sie frei im Nichts. */
  flex-direction: column;
}
/* Die Karte behält ihre Breite, auch wenn die Fußzeile schmaler ist. */
.buzzer-join > .xxl-card { flex: 0 0 auto; }

/* Der Raumcode wird laut zugerufen und abgetippt — großzügig und
   zeichenweise getrennt, damit man sich nicht vertippt. */
.buzzer-code {
  text-transform: uppercase;
  letter-spacing: 0.35em;
  font-size: 1.5rem;
  text-align: center;
  font-weight: 700;
}

.buzzer-game {
  min-height: 100dvh;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: 1.25rem;
  padding: 1rem;
}

.buzzer-head {
  position: fixed; top: 0; left: 0; right: 0;
  display: flex; justify-content: space-between;
  padding: 0.75rem 1rem;
  font-size: 0.95rem;
  background: var(--xxl-header-base);
}

/* Das Auswahlfeld „→ Runde …" in der Fragenliste (A.16.1.0). Schmal halten:
   es steht in einer Reihe mit Ändern, Vorschau, ▲▼ und Löschen, und ein
   Feld in voller Eingabebreite schöbe die Löschen-Schaltfläche in die
   nächste Zeile. */
.quiz-rundenwahl {
  width: auto;
  max-width: 11rem;
  padding: 0.3rem 0.5rem;
  font-size: 0.9rem;
}

/* Hinweise auf dem Handy (A.16.0.0). ⚠️ FESTE HÖHE, INNEN SCROLLEND — und das
   ist der ganze Punkt: das Handy ist der Buzzer. Ein Kasten, der mit jedem
   Hinweis um eine Zeile wächst, schiebt den Knopf nach unten, und zwar genau in
   dem Moment, in dem der Hinweis jemanden zum Drücken bringt. Der Finger liegt
   dann woanders als das Ziel. Lieber innen scrollen als außen schieben.

   22vh, nicht mehr: darunter steht der Knopf mit min(72vw, 52vh), und beides
   zusammen muss auf ein 640 px hohes Handy passen. */
.raum-hinweise {
  width: min(92vw, 34rem);
  height: 22vh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  border: 1px solid var(--xxl-border);
  border-radius: 0.6rem;
  background: rgba(0, 0, 0, 0.22);
  padding: 0.5rem 0.65rem;
  font-size: 0.98rem;
  line-height: 1.3;
}

.raum-hinweis {
  display: flex;
  gap: 0.45rem;
  padding: 0.2rem 0;
  color: var(--xxl-text);
}

.raum-hinweis-marke {
  color: var(--xxl-accent);
  font-weight: 700;
  flex: 0 0 auto;
}

/* Die noch ausstehenden gedämpft — sie sind eine Ankündigung, kein Inhalt. */
.raum-hinweis.ist-offen {
  color: var(--xxl-text-dim);
  font-style: italic;
}

/* Der Punktwert steht als eigene Zeile darunter, nicht im Kasten: er gehört
   nicht zu den Hinweisen, er ist ihre FOLGE — und er darf nicht mit
   weggescrollt werden. */
.raum-hinweis-wert {
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--xxl-text-muted);
}
.raum-hinweis-wert.ist-gefallen { color: var(--xxl-warning); }

/* Im Querformat ist die Höhe knapp und 22vh wären fast nichts — dort nimmt der
   Kasten die Breite statt der Höhe. */
@media (orientation: landscape) and (max-height: 520px) {
  .raum-hinweise { height: 30vh; font-size: 0.9rem; }
}

/* Der Knopf selbst: so groß wie der Bildschirm hergibt. Quadratisch über die
   KLEINERE Kante gerechnet, sonst ragt er im Querformat aus dem Bild. */
.buzzer-knob {
  width: min(72vw, 52vh);
  height: min(72vw, 52vh);
  border-radius: 50%;
  border: none;
  font-size: clamp(1.2rem, 5vw, 2.2rem);
  font-weight: 800;
  letter-spacing: 0.03em;
  cursor: pointer;
  transition: transform 0.08s, box-shadow 0.15s, background 0.2s;
  -webkit-tap-highlight-color: transparent;
}

.buzzer-knob.is-idle {
  background: #2a1a4e;
  color: var(--xxl-text-dim);
  box-shadow: inset 0 -6px 0 rgba(0,0,0,0.35);
  cursor: default;
}

/* Freigegeben: der auffälligste Zustand der ganzen Anwendung. Das Pulsieren
   sagt „jetzt" — ohne es sieht ein freier Buzzer aus wie ein gesperrter. */
.buzzer-knob.is-armed {
  background: linear-gradient(160deg, var(--xxl-accent) 0%, var(--xxl-accent-hover) 100%);
  color: #2a0038;
  box-shadow: 0 0 0 8px rgba(var(--xxl-accent-rgb), 0.22), inset 0 -8px 0 rgba(0,0,0,0.3);
  animation: buzzer-pulse 1.1s ease-in-out infinite;
}
@keyframes buzzer-pulse {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.04); }
}
.buzzer-knob.is-armed:active { transform: scale(0.94); }

.buzzer-knob.is-sent {
  background: #3a2568;
  color: var(--xxl-text);
  box-shadow: inset 0 -6px 0 rgba(0,0,0,0.35);
  cursor: default;
}

/* Du bist dran — muss man auf einen Blick von „gedrückt" unterscheiden können. */
.buzzer-knob.is-mine {
  background: linear-gradient(160deg, var(--xxl-success) 0%, #3d9c33 100%);
  color: #0c2408;
  box-shadow: 0 0 0 10px rgba(94,216,79,0.25), inset 0 -8px 0 rgba(0,0,0,0.25);
  cursor: default;
}

.buzzer-knob.is-locked {
  background: #2a1a4e;
  color: var(--xxl-text-muted);
  border: 3px solid var(--xxl-accent);
  box-shadow: none;
  cursor: default;
}

.buzzer-status {
  text-align: center;
  color: var(--xxl-text-muted);
  font-size: 1.05rem;
  min-height: 1.6em;
  padding: 0 1rem;
}
.buzzer-link {
  position: fixed; bottom: 0.75rem; left: 0; right: 0;
  text-align: center;
  font-size: 0.85rem;
  color: var(--xxl-danger);
}

/* Querformat auf dem Handy: nebeneinander statt untereinander, sonst bleibt
   für den Knopf keine Höhe mehr übrig. */
@media (orientation: landscape) and (max-height: 500px) {
  .buzzer-game { flex-direction: row; gap: 1.5rem; }
  .buzzer-knob { width: min(45vh, 45vw); height: min(45vh, 45vw); }
  .buzzer-head { position: static; }
}

/* ---- Bühne (Beamer/TV) ---------------------------------------------------- */

/* Alle Größen mit clamp() gegen die Viewport-Breite: dieselbe Seite läuft auf
   einem 1024er-Beamer und auf einem 4K-Fernseher. */
.stage-body {
  /* A.20.0.0, seine Ansage: „Die Bühne kann übrigens pauschal als Hintergrund
     genutzt werden. Dann hat auch z.B. das Intro Video nicht diesen
     unansehlichen Rand." Der Bühnenton bleibt als Unterlage stehen, falls das
     Bild einmal fehlt — ein weißer Beamer wäre das Schlimmste von allem. */
  /* ⚠️ SCHLEIER ÜBER DEM BILD, und zwar NUR hier: über der Frage stehen
     Beschriftungen in gedämpftem Grau („BUZZER", „PUNKTE"), und die gingen im
     hellen Lichtkreis des Bühnenbilds unter — im Screenshot gesehen, kein
     Prüfpunkt meldet Unlesbarkeit. Unten dichter als oben: dort sitzt die
     Spielerleiste, oben bleibt der Lichtkegel frei.
     Der ÜBERGABE-SCHIRM bekommt ihn ausdrücklich NICHT — dort ist das Bild der
     Auftritt und nicht die Unterlage. */
  background:
    linear-gradient(to bottom,
      rgba(21, 11, 43, 0.38) 0%,
      rgba(21, 11, 43, 0.30) 45%,
      rgba(21, 11, 43, 0.72) 100%),
    var(--xxl-bg) var(--xxl-buehne-bild) center/cover no-repeat;
  /* ▶▶ DIE BÜHNE IST EIN FESTES FELD VON 1280x720 (A.20.27.0) ---------------
     Sein Befund vom 26.08.: „Es gibt einen erheblichen Unterschied zwischen dem
     was die Vorschau zeigt und dem was das Spiel nachher wirklich zeigt."

     ⚠️⚠️ DIE URSACHE WAR, DASS DIE BÜHNE KEIN EIGENES MASS HATTE. Sie füllte das
     Fenster, und ihre Kopf- und Fußzeile bemaßen sich in `rem` und `vw` — also
     an der WELT. Die Vorschau ist dagegen ein starres Kästchen und rechnet in
     Prozent ihrer eigenen Breite — also am SCHIRM. Beide konnten deshalb nur bei
     GENAU EINER Fenstergröße übereinstimmen, und das war 1280x720.

     Gemessen (`node scripts/befund-vorschau-buehne.js`, 26.08.):
       Fläche Vorschau  665x258  = 2,575 — immer, egal wie groß das Fenster ist
       Fläche Bühne     1216x558 = 2,181 auf 1280x720   (18,1 % daneben)
       Fläche Bühne     1840x741 = 2,484 auf 1920x930   ( 3,7 % daneben)
     Und daraus folgend: die Frage war im Saal fast DOPPELT so groß wie in der
     Vorschau (2,41 % → 4,74 % der Flächenbreite), weil sie in der flacheren
     Vorschaufläche nicht passte und automatisch verkleinert wurde, auf der
     Bühne aber passte.

     ▶ DIE LÖSUNG: ein festes Feld, als Ganzes auf den Schirm skaliert.

     Alles darin bleibt exakt so bemessen, wie es bei 1280x720 immer war — jedes
     `rem`, jedes `px`, jeder Abstand. Was der Beamer zeigt, ist dasselbe Bild,
     nur größer oder kleiner. Damit kann die Vorschau es NICHT MEHR verfehlen:
     sie muss nur dieselben Proportionen halten, und das tut ein Kästchen von
     Natur aus.

     ⚠️ SEIN GO VOM 26.08.: „Ränder sind OK, bau das so." Ist das Fenster nicht
     16:9, bleiben links/rechts oder oben/unten Ränder stehen. Auf einem echten
     16:9-Beamer sieht man davon nie etwas.

     ⚠️⚠️ `vw` UND `vh` MUSSTEN TROTZDEM WEICHEN. Sie rechnen auch in einem
     skalierten Feld weiter gegen das FENSTER — `4.5vw` wären auf 1920 dann
     86,4 px statt der gemeinten 57,6. Sie stehen jetzt als `--bw` und `--bh`
     da: ein Prozent der BÜHNENBREITE beziehungsweise -HÖHE. Auf der Bühne sind
     das feste 12,8 px und 7,2 px, in der Vorschau `1cqw` und `0.5625cqw` — und
     damit rechnet dieselbe Regel in beiden dasselbe aus.

     ⚠️ Die Skalierung teilt eine Länge durch eine Länge und ergibt eine Zahl.
     Das kann CSS seit 2023; gemessen wird es im Befund-Lauf mit. */
  --bw: 12.8px;    /* 1 % der Bühnenbreite  — der frühere `1vw` bei 1280 */
  --bh: 7.2px;     /* 1 % der Bühnenhöhe    — der frühere `1vh` bei  720 */
  position: absolute;
  top: 50%;
  left: 50%;
  width: 1280px;
  height: 720px;
  /* ⚠️⚠️ `min-height` MUSS MIT. Die allgemeine Regel `body { min-height: 100vh }`
     weiter oben hat weniger Gewicht als diese hier, gilt aber trotzdem für eine
     Eigenschaft, die HIER nicht steht — und `min-height` schlägt `height`. Ohne
     diese Zeile war das Feld auf 1920x930 in Wahrheit 1280x930 hoch, wurde mit
     1,29 skaliert und ragte oben und unten aus dem Fenster: gemessen 1201 px auf
     einem 930 px hohen Schirm, y = −136. Die INNEREN Maße stimmten dabei
     tadellos (1216x558 auf jedem Schirm) — man hätte den Fehler also nicht an
     den Zahlen der Fläche gesehen, sondern nur an den abgeschnittenen Kanten. */
  min-height: 720px;
  transform: translate(-50%, -50%)
             scale(min(calc(100vw / 1280px), calc(100vh / 720px)));
  margin: 0;
  overflow: hidden;
}
/* Die RÄNDER neben dem Feld. Sie sind bewusst schlicht schwarz: was neben der
   Bühne liegt, soll auf einem Beamer nicht mitspielen. */
html:has(> .stage-body) {
  background: #000;
  overflow: hidden;
  height: 100%;
}

.stage-error {
  min-height: calc(100 * var(--bh));
  display: flex; align-items: center; justify-content: center;
  padding: calc(2 * 1.25 * var(--bw)); text-align: center;
  color: var(--xxl-danger);
  font-size: clamp(calc(1.1 * 1.25 * var(--bw)), calc(2.2 * var(--bw)), calc(1.6 * 1.25 * var(--bw)));
}

.stage-lobby {
  min-height: calc(100 * var(--bh));
  max-height: calc(100 * var(--bh));
  display: flex; align-items: center; justify-content: center;
  padding: calc(2 * 1.25 * var(--bw));
}
/* Seit die Teilnehmer mit Gesicht dastehen, kann der Wartesaal höher werden
   als der Schirm — und die Bühne scrollt nicht (body: overflow hidden), sie
   schneidet einfach ab. `min-height: 0` erlaubt dem Block zu schrumpfen, und
   das Banner gibt als Erstes nach: es ist Schmuck, die Namen sind es nicht. */
.stage-lobby-inner {
  text-align: center;
  max-width: calc(90 * var(--bw));
  min-height: 0;
  display: flex; flex-direction: column; align-items: center;
  overflow: hidden;
}
.stage-lobby-inner > .stage-banner { flex: 0 1 auto; min-height: 0; object-fit: contain; }
.stage-lobby-inner > * { flex: 0 0 auto; }
.stage-quiz-title {
  font-size: clamp(calc(1.8 * 1.25 * var(--bw)), calc(5 * var(--bw)), calc(4 * 1.25 * var(--bw)));
  font-weight: 800;
  color: var(--xxl-accent);
  margin-bottom: calc(2 * 1.25 * var(--bw));
}
.stage-lobby-hint {
  font-size: clamp(calc(1 * 1.25 * var(--bw)), calc(2 * var(--bw)), calc(1.6 * 1.25 * var(--bw)));
  color: var(--xxl-text-muted);
  margin-bottom: calc(0.5 * 1.25 * var(--bw));
}
/* Die Adresse braucht man ZUERST — erst danach den Code. Sie war deutlich
   kleiner als der Code und ging daneben unter; jetzt im Zweitakzent und nur
   noch halb so klein. */
.stage-url {
  font-size: clamp(calc(1.8 * 1.25 * var(--bw)), calc(4.5 * var(--bw)), calc(3.6 * 1.25 * var(--bw)));
  font-weight: 700;
  color: var(--xxl-accent2);
  margin-bottom: calc(1.25 * 1.25 * var(--bw));
}
/* Der Raumcode ist das, was aus zehn Metern Entfernung lesbar sein muss. */
.stage-code {
  font-size: clamp(calc(3 * 1.25 * var(--bw)), calc(12 * var(--bw)), calc(9 * 1.25 * var(--bw)));
  font-weight: 900;
  letter-spacing: 0.12em;
  color: var(--xxl-accent);
  line-height: 1;
  margin-bottom: calc(2 * 1.25 * var(--bw));
}
/* Ohne Überschrift stehen die Namen unvermittelt im Raum und niemand weiß,
   was sie bedeuten. */
.stage-players-head {
  font-size: clamp(calc(0.95 * 1.25 * var(--bw)), calc(1.9 * var(--bw)), calc(1.5 * 1.25 * var(--bw)));
  color: var(--xxl-text-muted);
  margin-bottom: 0.35em;
}
/* Die Namen sind die Bestätigung „ich bin drin" — die schwächste Schrift des
   Bildes ist dafür der falsche Platz. */
.stage-players {
  font-size: clamp(calc(1.1 * 1.25 * var(--bw)), calc(2.4 * var(--bw)), calc(1.9 * 1.25 * var(--bw)));
  font-weight: 600;
  color: var(--xxl-text);
}

.stage-main {
  min-height: calc(100 * var(--bh));
  /* ⚠️⚠️ DIE BÜHNE HATTE NIE EINE OBERGRENZE (bis A.20.9.0). `min-height` ohne
     `max-height` heißt: der Block wächst mit dem Inhalt, und alles darunter
     liegt außerhalb des Bildes. Gemessen bei einer achtzeiligen Frage auf
     1280×720: `.stage-main` war **1306 px** hoch, die Fußzeile endete bei 894.
     Niemand scrollt einen Beamer — was unter der Kante liegt, ist weg.

     Zwei Folgen, beide gewollt:
       1. Der Wrap darunter kann jetzt WIRKLICH schrumpfen (`flex: 1` mit
          `min-height: 0` braucht einen begrenzten Container, sonst gibt es
          keinen „verfügbaren Platz", gegen den geschrumpft wird) — und meldet
          damit seinen Überlauf ehrlich an `buehnenbild.js`.
       2. Was auch bei 45 % Schrift nicht passt, wird abgeschnitten statt
          unsichtbar unter die Kante geschoben. Das ist dasselbe Ergebnis, nur
          sieht man es beim Vorbereiten in der Vorschau. */
  max-height: calc(100 * var(--bh));
  overflow: hidden;
  display: flex; flex-direction: column;
  padding: clamp(calc(1 * 1.25 * var(--bw)), calc(2.5 * var(--bw)), calc(2.5 * 1.25 * var(--bw)));
  gap: clamp(calc(0.75 * 1.25 * var(--bw)), calc(2 * var(--bh)), calc(1.5 * 1.25 * var(--bw)));
}
/* ⚠️ HIER STAND `.stage-top` — gelöscht am 06.09.2026 (A.20.48.0, Befund 14).
   Das war die Kopfzeile über der Frage; seit A.20.46.0 stehen Runde und Punkte
   in der Statuszone unten, und das Element gibt es in `buehne.html` nicht mehr.
   ⚠️⚠️ `.stage-round` und `.stage-points` DARUNTER BLEIBEN — sie sind die
   beiden Spannen, die jetzt in der Statuszone stehen. Nur ihr alter Kasten ist
   weg. Ihre Schriftgrösse steht dort, wo sie hingehört: an
   `.stage-zone-status > .stage-round/-points`.
   ⚠️ `scripts/befund-zonen.js` fragt weiterhin `.stage-top` ab (Spalte „Kopf")
   und bekommt seither immer 0 — das ist richtig so und sagt genau das: da
   steht nichts mehr. */
.stage-round  { color: var(--xxl-accent); font-weight: 700; }
.stage-points { color: var(--xxl-text-muted); }

.stage-question-wrap {
  flex: 1;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  text-align: center;
  gap: clamp(calc(0.75 * 1.25 * var(--bw)), calc(2 * var(--bh)), calc(1.5 * 1.25 * var(--bw)));
  /* Beides ist Pflicht, seit hier auch Bilder stehen: ohne min-height weigert
     sich ein Flex-Kind, unter seine Inhaltsgröße zu schrumpfen, und ohne
     overflow schiebt ein hohes Bild die Lösung unten aus dem Schirm. */
  min-height: 0;
  overflow: hidden;
  /* ▶ DER BEZUGSRAHMEN FÜR FREI GESETZTE BILDER (A.20.9.0). Ihre Prozentmaße
     rechnen gegen DIESE Fläche — die Bühne zwischen Kopf- und Fußzeile —, und
     genau dieselbe Fläche zeigt die Vorschau im Editor. Stünde `relative`
     woanders, säße dasselbe Bild auf dem Beamer an einem anderen Fleck als in
     der Vorschau. */
  position: relative;
}
/* ⚠️⚠️ WENN ES AUCH BEI 45 % NICHT PASST, wird abgeschnitten — aber HINTEN,
   nicht vorne. Der Block steht sonst mittig, es verschwindet oben und unten
   gleich viel, und als Erstes geht die THEMA-Zeile weg. Wer eine Frage
   vorliest, fängt vorne an. `buehnenbild.js` setzt die Klasse, wenn sein
   Einpassen an die Untergrenze gestoßen ist und der Inhalt immer noch
   überläuft. Am eigenen Bild gesehen, von keinem Prüfpunkt gemeldet. */
/* ⚠️ HIER STAND `justify-content: flex-start` für `.laeuft-ueber` — gelöscht
   am 06.09.2026 (A.20.48.0, Befund 14). Die Klasse sitzt seit A.20.47.0 an der
   ZONE und nicht mehr hier: `buehnenbild.passeEin` setzt sie an ihrem Träger,
   und der ist `.stage-zone-frage`. Die wirksame Regel steht dort
   (`.stage-zone-frage.laeuft-ueber`); diese hier hat seither nichts mehr
   getroffen — und wer sie las, suchte den Grund an der falschen Stelle. */
/* ⚠️⚠️ IST DER TEXT FREI GESETZT, RÜCKT DER REST NACH UNTEN (A.20.11.0).
   Am eigenen Bühnenbild gefunden, von keinem Prüfpunkt: sobald der Textblock
   aus dem Fluss geht, rücken die Antwortmöglichkeiten in die MITTE nach — also
   genau dorthin, wo bei einer Wahlfrage meistens der Text liegt. „The Fate of
   Ophelia" stand quer über der Frage.
   Unten ist die richtige Antwort darauf: der Text ist von Hand gesetzt, alles
   Übrige macht ihm Platz und sammelt sich am unteren Rand. Die Klasse setzt
   `buehne.js`; per `:has()` wäre es kürzer, aber dann hinge die ganze Anordnung
   an der Browserfassung im Saal. */
/* ⚠️ UND HIER `justify-content: flex-end` für `.text-frei` — ebenfalls
   gelöscht (Befund 14). Die KLASSE bleibt und wird weiter gesetzt (sie steht
   in anderen Regeln), aber diese eine Angabe ist seit A.20.47.0 wirkungslos:
   der Träger ist ein RASTER mit einer Spalte über die volle Breite, und
   `justify-content` verschiebt darin nichts. Was sie erreichen sollte —
   „alles Übrige sammelt sich unten" — machen jetzt die Zonen selbst. */

/* Frage und Lösung behalten ihre Höhe — schrumpfen soll das Bild. */
.stage-question, .stage-answer, .stage-buzz-state { flex: 0 0 auto; }
/* ⚠️⚠️ DIE STAPELORDNUNG (A.20.9.0). Ein frei gesetztes Bild darf beliebig
   groß werden — bis über die ganze Bühne. Damit dabei nie der TEXT verschwindet,
   liegt alles Geschriebene grundsätzlich darüber. Ein Bild wird so zum
   Hintergrund, wenn man es groß zieht, und verdeckt nie die Frage. Das ist
   eine Entscheidung gegen die Freiheit und für die Lesbarkeit: eine Frage, die
   niemand lesen kann, ist auf einem Quizabend keine gestalterische Variante. */
/* ⚠️⚠️⚠️ SEIT A.20.47.0 DARF DIESE REGEL DIE ZONEN NICHT TREFFEN. Die direkten
   Kinder des Wraps sind jetzt die vier Zonen — bekämen SIE `position: relative`,
   wäre jede von ihnen der neue Bezugsrahmen für die absolut gesetzten Elemente
   darin, und die **81** von Hand gesetzten Platzierungen aus der Datenbank
   sprängen auf einen Schlag woanders hin. Die Zonen sind Rasterfelder und
   bleiben ohne `position`; ein Raster-Kind darf `z-index` auch so tragen.
   ▶ Getroffen wird deshalb nur noch, was WIRKLICH gestapelt werden soll: die
     Inhalte innerhalb der Zonen. `.stage-media` bleibt ausgenommen — es ist der
     Hintergrund, über dem alles Geschriebene liegt. */
/* ⚠️⚠️ `:not(.ist-frei)` STEHT HIER AUS DEMSELBEN GRUND WIE IN DER VORSCHAU:
   diese Regel wiegt mit der Zone im Selektor 0,3,0 — genauso viel wie die
   Regeln für frei gesetzte Blöcke. Heute gewinnen die, weil sie weiter unten
   stehen; das ist ein Zufall der Reihenfolge und keine Eigenschaft des Baus.
   In der Vorschau ist genau dieser Zufall am 27.08. andersherum ausgegangen
   und hat die frei gesetzten Antworten in den Fluss zurückgenagelt. */
.stage-question-wrap .stage-zone-frage > *:not(.ist-frei),
.stage-question-wrap .stage-zone-antworten > *:not(.stage-media):not(.ist-frei),
.stage-question-wrap .stage-zone-status > *:not(.ist-frei) { position: relative; z-index: 1; }
.stage-question {
  /* ⚠️⚠️ ZWEI GRÖSSEN, NICHT EINE (A.20.9.0):
       `--frage-grund`     was der Zustand vorgibt (Wette, Hinweise, eng, …).
                           Alle Überschreibungen weiter unten setzen NUR diese
                           Variable — nie `font-size` direkt, sonst fällt die
                           Skala für den betroffenen Zustand still weg.
       `--fragetext-skala` was `buehnenbild.js` beim Einpassen ausrechnet:
                           1, solange der Text passt; kleiner, wenn nicht.
     Der Grund steht in `buehnenbild.js` — kurz: mit den neuen Zeilenarten wird
     eine Frage acht Zeilen lang, und die Bühne schnitt die letzten drei ab. */
  --frage-grund: clamp(calc(1.6 * 1.25 * var(--bw)), calc(4.5 * var(--bw)), calc(4 * 1.25 * var(--bw)));
  /* ⚠️ DRITTER FAKTOR SEIT A.20.11.0: `--fragetext-frei` ist die Größe, die er
     am Block selbst gezogen hat. Er steht als eigener Faktor da und nicht in
     `--frage-grund`, weil der Zustand (Wette, Hinweise, eng) weiter seine
     Grundgröße vorgeben darf — beides multipliziert sich, statt sich
     gegenseitig zu überschreiben. Ohne freien Block ist er 1, und jede
     bestehende Frage sieht aus wie vorher. */
  font-size: calc(var(--frage-grund) * var(--fragetext-skala, 1) * var(--fragetext-frei, 1));
  font-weight: 700;
  line-height: 1.22;
  color: var(--xxl-text);
  /* Das Maß in ch, NICHT in vw: 90vw sind auf einem 1920er Schirm 1728 px —
     jede Frage lief dort in eine einzige endlose Zeile, die man vom Sofa aus
     abscannen muss statt sie zu lesen. ch wächst mit der Schriftgröße mit,
     das Maß bleibt also auf jedem Schirm gleich lang. */
  max-width: min(calc(90 * var(--bw)), 30ch);
  /* Verteilt die Wörter gleichmäßig auf die Zeilen, statt unten ein
     einzelnes Wort hängen zu lassen. */
  text-wrap: balance;
}
.stage-answer {
  font-size: clamp(calc(1.3 * 1.25 * var(--bw)), calc(3.2 * var(--bw)), calc(2.6 * 1.25 * var(--bw)));
  font-weight: 800;
  color: var(--xxl-success);
  /* Dasselbe Maß wie bei der Frage — eine lange Lösung lief sonst genauso
     in eine endlose Zeile. */
  max-width: min(calc(90 * var(--bw)), 32ch);
  text-wrap: balance;
  padding: 0.4em 0.9em;
  border: 3px solid var(--xxl-success);
  border-radius: 0.6em;
}

.stage-buzz-state {
  font-size: clamp(calc(1.1 * 1.25 * var(--bw)), calc(2.6 * var(--bw)), calc(2 * 1.25 * var(--bw)));
  font-weight: 700;
  padding: 0.25em 0.8em;
  border-radius: 999px;
}
/* ⚠️ NICHT MEHR NUR GEDÄMPFT (Audit 24.08.): diese Zeile sitzt genau im
   hellsten Bereich des Bühnenbilds, wo der Schleier am dünnsten ist — gemessen
   2,4:1. Ausgerechnet der Zustand, der während der ganzen Intro- und
   Übergabephase dasteht, war der blasseste. Die beiden Geschwister
   (`is-open`, `is-locked`) tragen eine gefüllte Pille; hier genügt ein
   dunkler Träger, damit „Buzzer noch zu" nicht zurückhaltender wirkt als es
   soll. */
.stage-buzz-state.is-closed {
  color: var(--xxl-text-muted);
  background: rgba(15, 6, 45, 0.55);
  border-radius: 999px;
  padding: 0.15em 0.9em;
}
.stage-buzz-state.is-open   { color: #2a0038; background: var(--xxl-accent); }
.stage-buzz-state.is-locked { color: #06202a; background: var(--xxl-accent2); }

/* ▶ FREI GESETZT (A.20.15.0). Sein Befund vom 25.08.: „«Buzzer noch zu» kann
   ich leider noch nicht verschieben."

   ⚠️⚠️ DIE SELEKTOREN SIND ABSICHTLICH LÄNGER ALS NÖTIG. Sowohl die Bühne als
   auch die Vorschau tragen eine Regel `… > *:not(.stage-media) { position:
   relative }` mit derselben Spezifität wie `.stage-buzz-state.ist-frei` (0,2,0),
   und bei Gleichstand gewinnt die SPÄTERE. Die Vorschau-Regel steht weiter
   unten in dieser Datei — sie hätte die frei gesetzte Zeile dort wieder in den
   Fluss genagelt, und zwar NUR im Editor, nicht auf der Bühne. Genau dieselbe
   Falle wie beim Textblock in A.20.11.0, nur andersherum. Mit dem Vorfahren im
   Selektor (0,3,0) ist die Reihenfolge egal.

   ⚠️ `width: max-content` wie beim Textblock: ein absolut gesetzter Kasten
   nimmt sonst den Platz bis zum Rand und bricht früher um als im Saal. */
/* ⚠️ `translate` VOR `scale` (A.20.23.0), wie bei den Möglichkeiten: die
   Prozente im `translate` rechnen gegen die UNGESKALIERTE Größe, so bleibt der
   Mittelpunkt auf x/y, egal wie groß gezogen wird. Andersherum wanderte die
   Zeile beim Größerziehen unter dem Finger weg.

   ⚠️⚠️ UND ES IST `scale`, KEINE SCHRIFTGRÖSSE — daran hängt, dass diese Zeile
   überhaupt skalierbar sein darf. Die Vorschau ist auf ihren PLATZBEDARF im
   Fluss geeicht (64 px auf 1280×720, siehe `.quiz-vorschau-schirm`); `scale`
   verändert den nicht, eine Schriftgröße hätte ihn verändert. */
.stage-question-wrap .stage-buzz-state.ist-frei,
.quiz-vorschau-flaeche .stage-buzz-state.ist-frei {
  position: absolute;
  transform: translate(-50%, -50%) scale(var(--stand-skala, 1));
  width: max-content;
  max-width: none;
  z-index: 1;
}
/* Im EDITOR ist die Zeile anfassbar — auf der Bühne nicht. Deshalb steht der
   Zeigefinger nur an der Vorschau. */
.quiz-vorschau-flaeche .stage-buzz-state { cursor: grab; touch-action: none; }
.quiz-vorschau-flaeche .stage-buzz-state:active { cursor: grabbing; }

/* ▶ DIE ANTWORTMÖGLICHKEITEN FREI GESETZT (A.20.17.0). Sein Befund vom 25.08.:
   „außerdem kann ich bei Multiple Choice die Antwortmöglichkeiten nicht bewegen
   oder in ihrer Größe verändern."

   ⚠️⚠️ DIE SELEKTOREN TRAGEN IHREN VORFAHREN, aus genau demselben Grund wie
   eine Regel weiter oben bei der Zustandszeile: Bühne und Vorschau haben je ein
   `… > *:not(…) { position: relative }` mit derselben Spezifität (0,2,0), und
   bei Gleichstand gewinnt die SPÄTERE. Die Vorschau-Regel steht weiter unten in
   dieser Datei — sie hätte den frei gesetzten Block dort wieder in den Fluss
   genagelt, und zwar NUR im Editor, nicht im Saal. Mit dem Vorfahren (0,3,0)
   ist die Reihenfolge egal.

   ⚠️⚠️ DIE GRÖSSE STEHT IM `transform`, NICHT IN DER SCHRIFT. `.stage-options`
   bemisst seine Kästen in `clamp()`-Pixeln, `.stage-tipp` seine Zeilen in `rem`:
   eine Schriftgröße am Behälter hätte bei der Wahlfrage die Schrift wachsen
   lassen und die Polsterung stehen gelassen — und bei Ordnen und Paaren gar
   nichts getan. `scale()` vergrößert alles gleich, auf beiden Seiten mit
   derselben Zahl.

   ⚠️ DIE REIHENFOLGE IM `transform` IST NICHT BELIEBIG: `translate` VOR
   `scale`. Die Prozente im `translate` rechnen gegen die UNGESKALIERTE Größe —
   so bleibt der Mittelpunkt auf x/y, egal wie groß gezogen wird. Andersherum
   wanderte der Block beim Größerziehen unter dem Finger weg.

   ⚠️ Die BREITE bleibt, wie sie ist (`min(92vw, 46ch)` bei der Wahl, `100 %`
   bei den Tippformen). Sie ist das ZEILENMASS — wer sie hier auf `max-content`
   setzte, ließe eine lange Option in EINER Zeile über die halbe Bühne laufen,
   und die Vorschau bräche an einer anderen Stelle um als der Beamer. */
.stage-question-wrap .stage-options.ist-frei,
.stage-question-wrap .stage-tipp.ist-frei,
.quiz-vorschau-flaeche .stage-options.ist-frei,
.quiz-vorschau-flaeche .stage-tipp.ist-frei {
  position: absolute;
  transform: translate(-50%, -50%) scale(var(--antworten-skala, 1));
  z-index: 1;
}

/* ▶ LÖSUNG, „WER IST DRAN" UND DIE HINWEISE FREI GESETZT (A.20.28.0).
   Sein Befund vom 26.08.: „Es gibt noch immer Sachen die nicht verschoben
   werden können." Es sind genau diese drei — dieselben, die A.20.24.0 in der
   Vorschau zuschaltbar gemacht hat.

   ⚠️⚠️ DIE SELEKTOREN TRAGEN IHREN VORFAHREN, aus genau demselben Grund wie
   die beiden Regeln darüber: Bühne und Vorschau haben je ein
   `… > *:not(…) { position: relative }` mit derselben Spezifität (0,2,0), und
   bei Gleichstand gewinnt die SPÄTERE. Die Vorschau-Regel steht weiter unten in
   dieser Datei — sie hätte den frei gesetzten Block dort wieder in den Fluss
   genagelt, und zwar NUR im Editor, nicht im Saal. Mit dem Vorfahren (0,3,0)
   ist die Reihenfolge egal.

   ⚠️⚠️ DIE GRÖSSE IST `transform: scale()`, KEINE SCHRIFTGRÖSSE — dieselbe
   Regel und derselbe Grund wie bei Zustandszeile und Möglichkeiten: die
   Vorschau ist auf den PLATZBEDARF im Fluss geeicht (`.quiz-vorschau-schirm`),
   und `scale()` verändert den nicht.

   ⚠️ `translate` VOR `scale`: die Prozente im `translate` rechnen gegen die
   UNGESKALIERTE Größe, so bleibt der Mittelpunkt auf x/y. Andersherum wanderte
   der Kasten beim Größerziehen unter dem Finger weg. */

/* Die LÖSUNG. ⚠️ `max-width` BLEIBT stehen (`min(90bw, 32ch)`) und wird nicht
   auf `none` gesetzt wie bei der Zustandszeile: eine lange Lösung liefe sonst
   in eine endlose Zeile, und die Vorschau bräche an einer anderen Stelle um als
   der Beamer. `width: max-content` mit stehendem `max-width` ergibt genau die
   Breite, die der Kasten auch im Fluss hat. */
.stage-question-wrap .stage-answer.ist-frei,
.quiz-vorschau-flaeche .stage-answer.ist-frei {
  position: absolute;
  transform: translate(-50%, -50%) scale(var(--loesung-skala, 1));
  width: max-content;
  z-index: 1;
}

/* „WER IST DRAN". ⚠️ Das GRÖSSTE, was in der Fläche auftauchen kann — deshalb
   bleibt `max-width: 100 %` stehen, damit der Kasten auch frei gesetzt nicht
   breiter wird als die Bühne selbst. */
.stage-question-wrap .stage-dran.ist-frei,
.quiz-vorschau-flaeche .stage-dran.ist-frei {
  position: absolute;
  transform: translate(-50%, -50%) scale(var(--dran-skala, 1));
  width: max-content;
  z-index: 1;
}

/* ▶ DIE HÜLLE UM HINWEISE UND PUNKTWERT (A.20.28.0).

   ⚠️⚠️ SIE IST DER GRUND, WARUM DIE HINWEISE ÜBERHAUPT EINE HÜLLE BEKOMMEN
   HABEN. Der Punktwert („12 Punkte") gehört zu den Hinweisen; wer die Liste in
   eine Ecke zieht und die Zahl mitten im Bild stehen lässt, hat ein Bruchstück
   auf der Bühne. Gesetzt wird deshalb die HÜLLE, nicht die Liste.

   ⚠️ `gap` IST DIESELBE ZAHL WIE IN `.stage-question-wrap`. Vorher lagen
   Hinweise und Punktwert als zwei Kinder im Fluss und hatten genau diese Lücke
   zwischen sich; die Hülle nimmt sie mit, damit der PLATZBEDARF auf den Pixel
   derselbe bleibt. Ohne das wäre die Eichung der Vorschau verstimmt — und zwar
   nur bei Fragen mit Hinweisen, also genau dort, wo es ohnehin eng ist. */
.stage-hinweise-block {
  display: flex; flex-direction: column;
  align-items: center;
  width: 100%;
  /* ⚠️⚠️ KEIN `flex: 0 0 auto` HIER. Die beiden Kinder lagen vorher als eigene
     Flex-Kinder im Wrap und trugen dort die VORGABE `0 1 auto` — sie durften
     also gedrückt werden. Ein `0 0 auto` an der Hülle hätte das umgedreht: die
     Hinweise wären starr, und bei einer engen Bühne gäbe stattdessen alles
     ANDERE nach. Die Hülle soll den Fluss nicht verändern, sie soll nur
     zusammenhalten, was zusammengehört. */
  gap: clamp(calc(0.75 * 1.25 * var(--bw)), calc(2 * var(--bh)), calc(1.5 * 1.25 * var(--bw)));
}
/* ⚠️⚠️ FREI GESETZT BRAUCHT SIE EINE ZAHL ALS BREITE, und es ist genau das
   `max-width` der Hinweisliste. Ein absolut gesetzter Kasten schrumpft sonst
   auf seinen Inhalt — und weil `.stage-hinweise` darin `width: 100 %` steht,
   liefe die Rechnung im Kreis: der Browser nähme die max-content-Breite, und
   die Hinweise stünden frei gesetzt in EINER endlosen Zeile statt umgebrochen
   wie im Fluss. Dieselbe Zahl heißt: derselbe Umbruch. */
.stage-question-wrap .stage-hinweise-block.ist-frei,
.quiz-vorschau-flaeche .stage-hinweise-block.ist-frei {
  position: absolute;
  transform: translate(-50%, -50%) scale(var(--hinweise-skala, 1));
  width: min(calc(58 * 1.25 * var(--bw)), calc(72 * var(--bw)));
  z-index: 1;
}

/* --- „Anne, wie lautet die Antwort?" (A.14.0.0) ---------------------------
   Der Moment, in dem der ganze Raum auf eine Person schaut. Deshalb die
   größte Schrift der Bühne nach der Frage selbst, das Bild daneben statt
   darunter (quer liest sich ein Name schneller), und die Uhr rechts außen,
   wo das Auge sie sucht, ohne den Namen zu verlieren.

   Alles in `em` gegen eine `clamp()`-Grundgröße: die Bühne läuft auf einem
   Beamer wie auf einem 4K-Fernseher, und feste Pixel wären auf dem einen
   winzig und auf dem anderen abgeschnitten. */
.stage-dran {
  display: flex; align-items: center; gap: 0.5em;
  flex: 0 0 auto;
  font-size: clamp(calc(1.6 * 1.25 * var(--bw)), calc(4.5 * var(--bw)), calc(4 * 1.25 * var(--bw)));
  padding: 0.3em 0.7em;
  border-radius: 0.5em;
  background: var(--xxl-accent2);
  color: #06202a;
  max-width: 100%;
}
.stage-dran .dran-bild { flex: 0 0 auto; }
.dran-text { min-width: 0; line-height: 1.1; }
.dran-name { font-weight: 900; }
/* Die Aufforderung kleiner als der Name: gerufen wird der Name, der Rest ist
   Satzbau. */
.dran-frage { font-weight: 600; font-size: 0.72em; opacity: 0.85; }

.dran-uhr {
  margin-left: auto;
  flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center;
  width: 1.5em; height: 1.5em;
  border-radius: 50%;
  border: 0.08em solid currentColor;
  font-weight: 900;
  font-variant-numeric: tabular-nums;
}
/* Die letzten zwei Sekunden. Farbe statt Zappeln: eine springende Zahl auf
   einem Beamer zieht den Blick von der Person weg, um die es geht. */
.dran-uhr.ist-knapp { background: var(--xxl-danger); color: #fff; border-color: var(--xxl-danger); }

/* ⚠️⚠️⚠️ DER UNTERBAU HATTE KEINE OBERGRENZE (bis A.20.44.0) — und das ist der
   teuerste Fehler dieser Datei gewesen. Sein Befund nach dem ERSTEN echten
   Spielabend, 26.08.2026: „Die Anordnung der Teilnehmer hat 2/3 der Bildfläche
   verdeckt. So waren die meisten Fragen nicht lesbar."

   `.stage-main` ist eine Flex-Spalte mit fester Höhe. `.stage-question-wrap`
   steht darin auf `flex: 1` und bekommt den REST — also das, was der Unterbau
   übrig lässt. Der Unterbau aber war so hoch wie seine Namensliste lang, und
   die wächst mit jedem, der beitritt. Gemessen an EINER Frage, nur mit immer
   mehr Handys im Raum (`scripts/befund-unterbau-frisst-buehne.js`):

     Spieler    0      2      4      6      8     10
     Unterbau  4,9 % 17,6 % 30,3 % 42,9 % 55,6 % 68,3 %
     Frage    77,4 % 64,7 % 52,1 % 39,4 % 26,7 % 14,0 %

   Bei zehn Leuten stand von der Frage ein Siebtel der Bühne — der Fragetext
   war mitten im Satz abgeschnitten und das Bild ganz aus dem Bild geschoben.
   Seine „2/3" sind 68,3 %.

   ⚠️⚠️ WARUM ES NIE JEMAND SAH: jeder Prüfstand stellt seine Frage in einen
   Raum mit null bis drei Handys — so viele, wie er zum Drücken braucht. Der
   volle Saal kam in keinem einzigen Lauf vor.

   ▶ DIE GRENZE IST DIE REPARATUR, NICHT DIE SCHRIFTGRÖSSE. Der Unterbau darf
   höchstens ein Viertel der Bühne nehmen; die Frage behält damit immer rund
   zwei Drittel — egal, wie viele im Raum sitzen. Was in die Grenze nicht
   hineinpasst, wächst in die BREITE (siehe `.ist-voll` unten) und nicht mehr
   in die Höhe. */
.stage-bottom {
  display: flex; gap: clamp(calc(1 * 1.25 * var(--bw)), calc(3 * var(--bw)), calc(3 * 1.25 * var(--bw)));
  font-size: clamp(calc(0.9 * 1.25 * var(--bw)), calc(1.7 * var(--bw)), calc(1.4 * 1.25 * var(--bw)));
  /* ⚠️ `0 0 auto`, NICHT `0 1 auto`: mit Schrumpfen erlaubt wurde der Unterbau
     um 27 px kleiner als sein Inhalt, und die unterste Namenszeile lag unter
     der Kante — `test:vollersaal` hat genau das gemeldet. Er darf nicht
     schrumpfen; seine Höhe ist ohnehin gedeckelt (drei Zeilen, dann in die
     Breite), und die Frage bekommt den Rest. */
  flex: 0 0 auto;
  min-height: 0;
  max-height: calc(26 * var(--bh));
  overflow: hidden;
  /* Platz für die QR-Ecke, die fixiert darüber liegt. Ohne das schiebt sich
     die Punktzahl des letzten Spielers unter den Code und ist nicht mehr zu
     lesen — auf dem Screenshot lugte eine pinke „0" unter dem QR hervor.
     ⚠️⚠️ EINE ZAHL, EIN ORT (A.20.48.0, Befund 13). Hier stand die Rechnung
     ein ZWEITES Mal ausgeschrieben, samt des px-Bodens von 70 — und die
     Version in `--qr-platz` hatte sich schon davon gelöst. Zwei Fassungen
     derselben Zahl sind zwei Gelegenheiten, nur eine nachzuziehen; genau
     dieselbe Falle wie bei den vier Bauern der Kachelreihe. Die Regel
     `.stage-main > .stage-bottom` weiter unten setzte diesen Wert auf der
     Bühne ohnehin schon aussen vor. */
  padding-right: var(--qr-platz);
}

/* ▶ AB FÜNF EINTRÄGEN WÄCHST DIE LISTE IN DIE BREITE (A.20.44.0). DREI Zeilen
   sind die Höhe, die der Unterbau hat; alles Weitere geht in eine zweite,
   dritte, vierte Spalte. Zehn Namen stehen damit in vier Spalten zu drei
   Zeilen statt in zehn Zeilen untereinander — und die Frage darüber behält
   ihren Platz.

   ⚠️ DREI, NICHT VIER. Mit vier Zeilen brauchte die Liste bei zehn Namen 231 px
   und die Grenze gibt 187 her: `test:vollersaal` meldete „44 px unter der
   Kante". Mehrspaltig heisst nicht abgeschnitten — wer die Höhe deckelt, muss
   nachrechnen, ob der Inhalt in den Deckel passt, sonst hat er den Fehler nur
   unsichtbar gemacht.

   ⚠️ `grid-auto-flow: column` mit festen Zeilen ist hier genau richtig: die
   Zahl der Spalten ergibt sich von selbst aus der Zahl der Einträge, es gibt
   keine Schwelle, die jemand pflegen müsste, und bei zwanzig Leuten bricht
   nichts — es wird nur schmaler.
   ⚠️ Die Klasse setzt `buehne-listen.js` je Liste EINZELN: die Buzz-Spalte ist
   fast immer kurz, während die Punktespalte alle im Raum führt. Eine
   gemeinsame Klasse hätte die kurze Liste unnötig zerlegt.
   ⚠️ `minmax(0, 1fr)` ist Pflicht — ohne die 0 nimmt jede Spalte ihre
   Inhaltsbreite, und die Liste ragt seitlich aus dem Bild. */
/* Ein langer Name darf schmal werden, aber nicht die Spalte sprengen. */
.stage-listenname {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
/* Bei einer Wahlfrage steht in dieser Spalte der Name einer Person, kein
   verbrauchter Buzz-Versuch — durchgestrichen liest er sich wie
   „rausgeworfen". Das Zeichen dahinter (✓ ✗ —) sagt genug. */

/* ---- Selfie-Avatare (A.7.0.0) --------------------------------------------- */

/* Eine Größe je Klasse, keine Pixelzahlen an den Stellen: die Bühne rechnet
   mit clamp() gegen die Schirmbreite, ein fester Wert wäre auf einem
   4K-Fernseher ein Punkt und auf einem 1024er-Beamer ein Klotz. */
.avatar {
  display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 auto;
  border-radius: 50%;
  overflow: hidden;
  vertical-align: middle;
  width: 1.8em; height: 1.8em;      /* Grundmaß: mitwachsend mit der Schrift */
  line-height: 1;
}
.avatar.ist-klein  { width: 1.7em; height: 1.7em; }
.avatar.ist-mittel { width: 2.4em; height: 2.4em; }
.avatar.ist-gross  { width: 3.6em; height: 3.6em; }
/* Nur für „Anne, wie lautet die Antwort?" auf der Bühne: dieses eine Bild wird
   quer durchs Wohnzimmer angesehen, nicht in einer Liste überflogen. */
.avatar.ist-riesig { width: 1.6em; height: 1.6em; }

.avatar-bild {
  width: 100%; height: 100%;
  object-fit: cover;                /* zuschneiden, NICHT stauchen */
  display: block;
}

/* Der Platzhalter ist der NORMALFALL, nicht der Notbehelf — das Selfie ist
   freiwillig. Deshalb Initialen und eine eigene Farbe statt eines grauen
   Umrisses, der wie ein halb geladener Eintrag aussieht. Farbe und Rand setzt
   avatar.js, hier steht nur die Form. */
.avatar-platzhalter {
  width: 100%; height: 100%;
  display: flex; align-items: center; justify-content: center;
  border: 2px solid;
  border-radius: 50%;
  font-weight: 800;
  letter-spacing: 0.02em;
  box-sizing: border-box;
}
/* ⚠️ Die Schriftgröße MUSS je Kreisgröße gesetzt werden. `em` rechnet gegen die
   GEERBTE Schrift, nicht gegen die Breite des Kreises — ein fester Wert ergab
   im großen Kreis ein 7-px-Kürzel, das man auf dem Handy nicht mehr lesen
   konnte und auf dem Beamer gar nicht. Der Kreis ist in em breit, die Schrift
   muss also im selben Maß mitwachsen. */
.avatar .avatar-platzhalter        { font-size: 0.72em; }
.avatar.ist-klein  .avatar-platzhalter { font-size: 0.68em; }
.avatar.ist-mittel .avatar-platzhalter { font-size: 1em; }
.avatar.ist-gross  .avatar-platzhalter { font-size: 1.5em; }

/* Wartesaal: die Gesichter statt einer Namensaufzählung. Hier schaut der ganze
   Raum hin, während getrudelt wird. */
.stage-players {
  display: flex; flex-wrap: wrap;
  gap: clamp(calc(0.6 * 1.25 * var(--bw)), calc(1.8 * var(--bw)), calc(1.6 * 1.25 * var(--bw)));
  justify-content: center;
}
.stage-lobbyspieler {
  display: flex; flex-direction: column; align-items: center;
  gap: 0.3em;
  max-width: 8em;
  /* Ein langer Name darf die Kachel nicht sprengen. */
  overflow-wrap: anywhere;
  font-size: clamp(calc(0.9 * 1.25 * var(--bw)), calc(1.9 * var(--bw)), calc(1.5 * 1.25 * var(--bw)));
}

/* In den Listen steht das Gesicht VOR dem Namen. */
.stage-buzzname, .stage-scorename {
  display: flex; align-items: center; gap: 0.5em;
  min-width: 0;
}

/* Auf dem Pult ebenso — dort aber in Grundschrift, nicht in Bühnengröße. */
.pult-mitgesicht { display: inline-flex; align-items: center; gap: 0.45rem; }
.pult-avatarweg {
  font-size: 0.72rem;
  padding: 0.1rem 0.4rem;
  border: 1px solid var(--xxl-border);
  border-radius: 0.3rem;
  background: transparent;
  color: var(--xxl-text-dim);
  cursor: pointer;
}
.pult-avatarweg:hover { color: var(--xxl-danger); border-color: var(--xxl-danger); }

/* ---- Punkte von Hand (A.20.32.0) -------------------------------------------
   Sein Auftrag vom 26.08.: „jederzeit jedem Spieler Punkte geben und abziehen".

   ⚠️ MINDESTENS 32 px HOCH UND BREIT. Diese beiden Knöpfe stehen zwölfmal
   untereinander, direkt neben den beiden ✕-Knöpfen, und werden im Halbdunkel
   mit dem Daumen getroffen. Die 44 px der grossen Pult-Knöpfe passen hier nicht
   in die Zeile; 32 px sind der Kompromiss, den die Zeilenhöhe hergibt — sie
   sind trotzdem fast dreimal so gross wie „✕ Bild" daneben, und das ist
   Absicht: einer von beiden verändert den Spielstand.
   ⚠️ `font-variant-numeric: tabular-nums`: „+9" und „+10" dürfen den Knopf
   nicht in der Breite springen lassen, während getippt wird. */
.pult-punktknopf {
  min-width: 2rem;
  min-height: 2rem;
  padding: 0.15rem 0.45rem;
  font-size: 0.8rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  border: 1px solid var(--xxl-border);
  border-radius: 0.35rem;
  background: transparent;
  color: var(--xxl-text-dim);
  cursor: pointer;
  line-height: 1;
}
.pult-punktknopf:hover {
  color: var(--xxl-accent);
  border-color: var(--xxl-accent);
}

/* ⚠️⚠️ DIE SPIELERZEILE MUSS SEIT A.20.32.0 UMBRECHEN KÖNNEN — beim Ansehen
   gefunden, nicht beim Messen. Rechts standen bis dahin „✕ Bild", „✕ Spieler"
   und die Punktzahl; mit „−10" und „+10" sind es fünf Dinge. Auf 390 px schob
   ein langer Name („Charlotte-Sophie") das „+" aus der Karte, auf 320 px
   verlor selbst „Anna" ihres. Ein Knopf, den man nicht mehr erreicht, ist
   dasselbe wie kein Knopf — und gemessen war alles grün, weil der Prüfstand
   auf 1280 px klickt.
   ⚠️ `min-width: 0` am linken Teil ist der eigentliche Schalter: ein
   Flex-Kind schrumpft von sich aus nicht unter seinen Inhalt, und ohne das
   drückt ein langer Name die rechte Gruppe hinaus, statt selbst umzubrechen. */
.pult-spielerzeile { flex-wrap: wrap; row-gap: 0.35rem; column-gap: 0.5rem; }
.pult-spielerzeile > .pult-mitgesicht { min-width: 0; }
.pult-spielerzeile-rechts { flex-wrap: wrap; justify-content: flex-end; margin-left: auto; }

/* ⚠️ „✕ Spieler" darf nicht in „✕" und „Spieler" zerfallen. Es tat es schon
   vorher, wenn der Platz eng wurde — auf 320 px stand der Knopf zweizeilig und
   war doppelt so hoch wie die Zeile. Aufgefallen erst jetzt, weil die Zeile
   seit A.20.32.0 enger ist. Eine Beschriftung, die umbricht, ist kein Knopf
   mehr, sondern ein Absatz mit Rahmen. */
.pult-avatarweg { white-space: nowrap; }

/* Die Leiste über der Liste. `flex-wrap`, weil sie auf 320 px sonst die Karte
   aufspannt — hier darf sie umbrechen, anders als die klebende Kopfleiste. */
.pult-punkteleiste {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-bottom: 0.6rem;
  font-size: 0.8rem;
  color: var(--xxl-text-muted);
}
.pult-punkteleiste input {
  width: 4.5rem;
  padding: 0.2rem 0.4rem;
  font-size: 0.85rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  text-align: center;
  border: 1px solid var(--xxl-border);
  border-radius: 0.35rem;
  background: var(--xxl-bg);
  color: var(--xxl-text);
}

/* Der Satz mit dem Rückgängig. Steht am ENDE der Karte: er kommt und geht, und
   was darüber liegt, darf sich dadurch nicht verschieben. */
.pult-punkte-zuletzt {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.7rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--xxl-border);
  font-size: 0.78rem;
  color: var(--xxl-text-muted);
}

/* ---- Selfie aufnehmen (Spieler-Handy) ------------------------------------- */

.selfie-zeile { display: flex; align-items: center; gap: 0.9rem; }
.selfie-hinweis {
  font-size: 0.8rem;
  color: var(--xxl-text-muted);
  margin-top: 0.3rem;
  line-height: 1.35;
}

/* Über allem, weil die Kamera den ganzen Bildschirm braucht — und weil ein
   halb sichtbarer Buzzer darunter zum Danebendrücken einlädt. */
.selfie-schicht {
  position: fixed; inset: 0;
  z-index: 50;
  display: flex; align-items: center; justify-content: center;
  padding: 1rem;
  background: rgba(10, 4, 22, 0.94);
}
.selfie-kasten {
  width: 100%; max-width: 22rem;
  text-align: center;
}
.selfie-titel {
  font-size: 1.15rem; font-weight: 700;
  color: var(--xxl-accent);
  margin-bottom: 0.75rem;
}

/* Rund und quadratisch — man soll sehen, was hinterher als Avatar dasteht,
   und nicht ein Rechteck, aus dem später die Mitte geschnitten wird. */
.selfie-video, .selfie-bild {
  width: min(70vw, 16rem);
  height: min(70vw, 16rem);
  object-fit: cover;
  border-radius: 50%;
  border: 3px solid var(--xxl-accent);
  background: #2a1a4e;
  display: block;
  margin: 0 auto 1rem;
}
/* Die Live-Vorschau spiegeln: man ist es aus jedem Spiegel so gewohnt, und ein
   Bild, das beim Auslösen umklappt, wirkt wie ein Fehler. Das AUFGENOMMENE
   Bild ist bereits gespiegelt gezeichnet — es darf hier nicht noch einmal. */
.selfie-video { transform: scaleX(-1); }

.selfie-knoepfe {
  display: flex; flex-direction: column; gap: 0.5rem;
  align-items: stretch;
}

/* Der antippbare Avatar im Kopfbereich des Buzzers. */
.buzzer-avatar {
  background: none; border: none; padding: 0;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  font-size: 1rem;
}

/* ---- Antwortmöglichkeiten auf der Bühne (A.6.0.0) ------------------------- */

/* Wie die Frage darüber: das Zeilenmaß in ch, NICHT in vw. Auf einem 1920er
   Schirm wären 80vw = 1536 px, und jede Option stünde in einer Zeile, die man
   vom Sofa aus abscannen muss.
   ⚠️ Die Schriftgröße MUSS hier stehen, nicht erst auf .stage-option: `ch`
   rechnet gegen die Schrift DIESES Elements. Ohne sie waren 60ch die 60
   Zeichen der 17-px-Grundschrift, während in den Kästchen 35-px-Text stand —
   das Feld war damit halb so breit wie gedacht, und „Mondrapunzel" brach
   mitten im Wort um. Gefunden hat das kein Check, sondern der Screenshot. */
.stage-options {
  flex: 0 1 auto;
  min-height: 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(calc(0.4 * 1.25 * var(--bw)), calc(1.2 * var(--bh)), calc(0.9 * 1.25 * var(--bw)));
  font-size: clamp(calc(1 * 1.25 * var(--bw)), calc(2.2 * var(--bw)), calc(1.9 * 1.25 * var(--bw)));
  width: min(calc(92 * var(--bw)), 46ch);
  text-align: left;
}
/* Zwei Spalten erst ab vier Möglichkeiten — zwei oder drei stehen untereinander
   besser lesbar da als in einem halb leeren Raster. */
.stage-options.ist-zweispaltig { grid-template-columns: repeat(2, minmax(0, 1fr)); }

.stage-option {
  display: flex; align-items: center;
  gap: clamp(calc(0.5 * 1.25 * var(--bw)), calc(1.2 * var(--bw)), calc(1 * 1.25 * var(--bw)));
  padding: clamp(calc(0.35 * 1.25 * var(--bw)), calc(1 * var(--bh)), calc(0.7 * 1.25 * var(--bw))) clamp(calc(0.6 * 1.25 * var(--bw)), calc(1.4 * var(--bw)), calc(1.1 * 1.25 * var(--bw)));
  border: 2px solid var(--xxl-border);
  border-radius: 0.5em;
  font-size: 1em;              /* erbt das Maß von .stage-options — siehe dort */
  color: var(--xxl-text);
  /* Damit eine lange Option nicht die Spalte sprengt. */
  min-width: 0;
}
/* Der Buchstabe ist die gemeinsame Sprache von Bühne, Handy und Pult: der
   Quizmaster kann „B ist richtig" sagen, ohne den Text vorzulesen. */
.stage-option-marke {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.7em; height: 1.7em;
  border-radius: 50%;
  background: rgba(var(--xxl-accent-rgb), 0.18);
  color: var(--xxl-accent);
  font-weight: 800;
}
/* break-word, NICHT anywhere: `anywhere` erlaubt den Umbruch mitten im Wort
   auch dann, wenn das Wort in die nächste Zeile passen würde. „Mondrapunzel"
   stand dadurch als „Mondrapunz / el" da. */
.stage-option-text { min-width: 0; overflow-wrap: break-word; }

/* Erst nach dem Aufdecken. Vorher trägt keine Option eine dieser Klassen —
   und im Datenstrom steht auch nichts, woraus man sie ableiten könnte. */
.stage-option.ist-richtig {
  border-color: var(--xxl-success);
  background: rgba(94, 216, 79, 0.16);
  color: var(--xxl-success);
  font-weight: 700;
}
.stage-option.ist-richtig .stage-option-marke {
  background: var(--xxl-success); color: #0c2408;
}
/* Falsche werden gedimmt, nicht durchgestrichen: durchgestrichen liest sich
   auf zehn Meter wie ein Fehler des Beamers. */
.stage-option.ist-falsch { opacity: 0.4; }

/* ---- Antwortmöglichkeiten auf dem Handy ----------------------------------- */

.wahl-feld {
  display: flex; flex-direction: column;
  gap: 0.6rem;
  width: 100%; max-width: 26rem;
}

/* Groß genug für einen Daumen im Halbdunkel: 3rem Mindesthöhe, kein
   Doppeltipp-Zoom, keine Textauswahl beim schnellen Tippen. */
.wahl-knopf {
  display: flex; align-items: center; gap: 0.75rem;
  min-height: 3.2rem;
  padding: 0.6rem 0.9rem;
  border: 2px solid var(--xxl-border);
  border-radius: 0.75rem;
  background: #2a1a4e;
  color: var(--xxl-text);
  font-size: 1.05rem;
  text-align: left;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: transform 0.08s, border-color 0.15s, background 0.15s;
}
.wahl-knopf:active { transform: scale(0.98); }

.wahl-marke {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  width: 2rem; height: 2rem;
  border-radius: 50%;
  background: rgba(var(--xxl-accent-rgb), 0.18);
  color: var(--xxl-accent);
  font-weight: 800;
}
.wahl-text { min-width: 0; overflow-wrap: anywhere; }

/* Meine Wahl. Muss auf einen Blick zu erkennen sein, auch schräg von der
   Seite — deshalb Rahmen UND Fläche, nicht nur ein Häkchen. */
.wahl-knopf.ist-meine {
  border-color: var(--xxl-accent);
  background: rgba(var(--xxl-accent-rgb), 0.22);
}
.wahl-knopf.ist-meine .wahl-marke { background: var(--xxl-accent); color: #2a0038; }

/* Gesperrt: die eigene Wahl bleibt STEHEN, nur eben unantastbar. Sie hier
   abzuräumen hieße, dass zwei Minuten später beim Auflösen niemand mehr weiß,
   was er getippt hatte. */
.wahl-knopf.ist-gesperrt { cursor: default; opacity: 0.85; }
.wahl-knopf.ist-gesperrt:active { transform: none; }

.wahl-knopf.ist-richtig {
  border-color: var(--xxl-success);
  background: rgba(94, 216, 79, 0.18);
}
.wahl-knopf.ist-richtig .wahl-marke { background: var(--xxl-success); color: #0c2408; }
/* Falsch heißt hier „nicht richtig", nicht „du hast danebengetippt" — die
   Klasse liegt auf allen nicht-richtigen Optionen. Wer sie angetippt hatte,
   erkennt es an ist-meine, das darüber stehen bleibt. */
.wahl-knopf.ist-falsch { opacity: 0.45; }

/* Querformat: die Optionen brauchen die Breite, die im Hochformat der Buzzer
   hätte. Ohne das stünden sie in einer 8rem schmalen Spalte neben dem
   Kopfbereich. */
@media (orientation: landscape) and (max-height: 500px) {
  .wahl-feld { max-width: 34rem; }
  .wahl-knopf { min-height: 2.6rem; font-size: 0.95rem; }
}

/* ---- Quizmaster-Pult ------------------------------------------------------ */

/* Die Optionen am Pult — hier MIT Markierung, was richtig ist. Der Kanal geht
   ausschließlich an angemeldete Pulte. */
.pult-optionen { display: flex; flex-direction: column; gap: 0.35rem; }
.pult-option {
  display: flex; align-items: center; gap: 0.6rem;
  padding: 0.35rem 0.6rem;
  border: 1px solid var(--xxl-border);
  border-radius: 0.4rem;
  color: var(--xxl-text-muted);
}
.pult-option.ist-richtig {
  border-color: var(--xxl-success);
  background: rgba(94, 216, 79, 0.12);
  color: var(--xxl-success);
  font-weight: 700;
}
.pult-option-marke {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.6rem; height: 1.6rem;
  border-radius: 50%;
  background: rgba(var(--xxl-accent-rgb), 0.15);
  color: var(--xxl-accent);
  font-weight: 800; font-size: 0.85rem;
}

.pult-auswertung {
  background: rgba(var(--xxl-accent-rgb), 0.07);
  border-left: 3px solid var(--xxl-accent);
  border-radius: 0.3rem;
  padding: 0.5rem 0.75rem;
}

.pult-code {
  font-size: 2.2rem; font-weight: 900;
  letter-spacing: 0.15em;
  color: var(--xxl-accent);
  line-height: 1.1;
}
/* Bühnen- und Pultcode. Kleiner als der Raumcode — der ist der eine, den ein
   ganzer Raum vom Beamer abliest; diese beiden tippt genau eine Person ein.
   `user-select: all` macht aus einem Klick die ganze Auswahl: am Rechner will
   man ihn kopieren, nicht abschreiben. */
.pult-nebencode {
  font-size: 1.5rem; font-weight: 800;
  letter-spacing: 0.12em;
  color: var(--xxl-accent);
  line-height: 1.2;
  user-select: all;
}
.pult-stagelink {
  display: block;
  color: var(--xxl-text);
  text-decoration: underline;
  word-break: break-all;
  font-size: calc(0.9 * 1.25 * var(--bw));
}
.pult-stagelink:hover { color: var(--xxl-accent); }

/* Die Lösung abgesetzt hervorheben — sie ist beim Werten das, worauf der
   Blick sofort fallen muss. */
.pult-answer {
  background: rgba(94,216,79,0.12);
  border-left: 3px solid var(--xxl-success);
  padding: 0.5rem 0.75rem;
  border-radius: 0.3rem;
}

.pult-judge {
  background: rgba(var(--xxl-accent-rgb), 0.1);
  border: 1px solid var(--xxl-accent);
  border-radius: 0.5rem;
  padding: 0.75rem;
}

.pult-buzzrow { padding: 0.25rem 0.4rem; border-radius: 0.3rem; color: var(--xxl-text); }
.pult-buzzrow.is-active  { background: rgba(var(--xxl-accent-rgb), 0.18); color: var(--xxl-accent); font-weight: 700; }
.pult-buzzrow.is-wrong   { color: var(--xxl-danger); text-decoration: line-through; }
.pult-buzzrow.is-correct { color: var(--xxl-success); font-weight: 700; }
/* Der Abstandssatz steht unter dem Namen, nicht daneben: „Peter hat 0,021 s
   später gedrückt als Anna." passt neben einem Namen auf keinem Handy in eine
   Zeile. */
.pult-buzzabstand {
  font-size: 0.85rem;
  font-weight: 400;
  color: var(--xxl-text-dim);
  /* Den Durchstrich von .is-wrong zurücknehmen — er meint den verbrauchten
     Versuch, nicht die Zeitangabe dazu. */
  text-decoration: none;
}

.pult-qpick {
  display: flex; justify-content: space-between; align-items: center; gap: 0.75rem;
  width: 100%; text-align: left;
  padding: 0.45rem 0.7rem;
  background: var(--xxl-content);
  border: 1px solid var(--xxl-border);
  border-radius: 0.4rem;
  color: var(--xxl-text);
  cursor: pointer;
  transition: border-color 0.12s, background 0.12s;
}
.pult-qpick:hover { border-color: var(--xxl-accent); }
.pult-qpick.is-current {
  border-color: var(--xxl-accent);
  background: rgba(var(--xxl-accent-rgb), 0.14);
  color: var(--xxl-accent);
  font-weight: 600;
}

/* ---- Grafiken (A.2.1.0) ---------------------------------------------------- */

/* Logo auf Einwählseite und Login. Auf niedrigen Handy-Displays im Querformat
   darf es nicht die halbe Höhe fressen — deshalb zusätzlich in vh gedeckelt. */
.buzzer-logo {
  display: block;
  width: 100%;
  max-width: 280px;
  max-height: 30vh;
  height: auto;
  object-fit: contain;
  margin: 0 auto 1.25rem;
}

/* Banner im Wartesaal der Bühne. Der Raumcode darunter muss der Held der Seite
   bleiben, also bekommt der Banner eine Höhengrenze statt voller Breite. */
.stage-banner {
  display: block;
  width: 100%;
  max-width: min(calc(70 * var(--bw)), 900px);
  max-height: calc(26 * var(--bh));
  height: auto;
  object-fit: contain;
  margin: 0 auto calc(1.5 * 1.25 * var(--bw));
  border-radius: calc(0.75 * 1.25 * var(--bw));
}

/* ---- QR-Codes und mobiles Pult (A.3.0.0) ---------------------------------- */

/* Beitritts-Block im Wartesaal: Text und QR nebeneinander, damit beides gleich
   wichtig aussieht. Auf schmalen Schirmen untereinander. */
.stage-join {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(calc(1.5 * 1.25 * var(--bw)), calc(4 * var(--bw)), calc(4 * 1.25 * var(--bw)));
  flex-wrap: wrap;
  margin-bottom: calc(1.5 * 1.25 * var(--bw));
}
.stage-join-text { text-align: center; }
.stage-join .stage-url,
.stage-join .stage-code { margin-bottom: calc(0.5 * 1.25 * var(--bw)); }

/* Weißer Rahmen ist Pflicht: ein QR auf dunklem Grund ohne Ruhezone wird von
   vielen Kameras nicht erkannt. */
.stage-qr {
  width: clamp(140px, calc(18 * var(--bw)), 300px);
  height: auto;
  background: #fff;
  padding: 10px;
  border-radius: 12px;
}

/* Im laufenden Spiel: klein in der Ecke, damit Nachzügler beitreten können,
   ohne die Frage zu überlagern. Halbtransparent, bis jemand hinschaut. */
.stage-qr-small {
  /* Sitzt seit A.5.1.0 im Rahmen .stage-qr-ecke, der die Ecke bestimmt —
     eigenes position: fixed hier würde den Knopf daneben abhängen. */
  width: clamp(70px, calc(8 * var(--bw)), 130px);
  height: auto;
  background: #fff;
  padding: 6px;
  border-radius: 8px;
  opacity: 0.55;
  transition: opacity 0.2s;
  z-index: 5;
}
.stage-qr-small:hover { opacity: 1; }

/* ---- Pult auf dem Handy ---------------------------------------------------- */

.pult-body {
  background: var(--xxl-bg);
  min-height: 100vh;
  min-height: 100dvh;
  margin: 0;
}
.pult-bar {
  position: sticky; top: 0; z-index: 10;
  display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap;
  padding: 0.6rem 1rem;
  background: var(--xxl-header-base);
  border-bottom: 1px solid var(--xxl-border);
}
.pult-bar-title { color: var(--xxl-accent); font-weight: 700; }
.pult-bar-room {
  color: var(--xxl-accent2);
  font-weight: 800;
  letter-spacing: 0.12em;
}
.pult-bar-link { color: var(--xxl-danger); font-size: 0.85rem; margin-left: auto; }

/* Wo stehe ich im Quiz (A.17.0.0). Gedaempft: es ist eine Randnotiz, kein
   Signal — der Blick soll bei der Frage bleiben, nicht bei der Zaehlung. */
/* Die Platzziffer der Rangfolge (A.17.0.0). Feste Breite und Ziffern gleicher
   Laufweite, damit die Spalte auch bei zweistelligen Plaetzen eine Kante
   bleibt. */
.pult-platz {
  min-width: 1.7rem;
  color: var(--xxl-text-dim);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.pult-bar-fortschritt {
  color: var(--xxl-text-dim);
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
}

/* ⚠️ DER ENGSTE FALL: „Frage 27 von 30 · Runde 3 von 3" neben Titel und
   Raumcode brach auf 390 px auf ZWEI Zeilen um — gemessen, nicht vermutet.
   Die Leiste ist `sticky`, sie hätte den ganzen Abend 20 px vom Bild
   gefressen. Das Wort „Pult" ist auf einem schmalen Schirm reine Zierde: dass
   man am Pult ist, steht im Browser-Tab und ergibt sich aus allem darunter.
   Weg damit, und die Zeile bleibt eine. */
@media (max-width: 480px) {
  .pult-bar-title { display: none; }
  /* ⚠️ 0,8125 rem = 13 px, NICHT 0,8 rem = 12,8 px: unter 13 px liest man auf
     einem Handy nicht mehr mit, und `mobil` meldet es zu Recht.
     Die Sorge, dass die Leiste dadurch auf zwei Zeilen bricht, ist GEMESSEN
     ausgeräumt — mit dem engsten Text („Frage 27 von 30 · Runde 3 von 3"),
     nicht mit dem kurzen, den der Prüfstand zufällig vor sich hat:
       390 px: 96 px hoch bei 12,8 px UND bei 13 px — kein Unterschied.
       Gegenprobe 1,1 rem: 130 px, die Leiste bricht also sehr wohl, wenn die
       Schrift zu groß wird. Die Messung kann rot werden.
     (Zu „auf 360 px sei die Leiste zweizeilig", wie hier bis A.20.19.0 stand:
     GEMESSEN stimmt das nicht — `befund-pultleiste.js` zeigt 44 px, also eine
     Zeile, ab 360 px aufwärts. Die alte Notiz kam aus einer Zählung über die
     OBERKANTEN der Kinder, und bei `align-items: center` haben ein 13-px- und
     ein 16-px-Text in derselben Zeile verschiedene Oberkanten. Zweizeilig
     wird die Leiste erst auf 320 px.) */
  .pult-bar-fortschritt { font-size: 0.8125rem; }

  /* ⚠️⚠️ AB HIER KANN DIE LEISTE NICHT MEHR WACHSEN (A.20.20.0).
     Sie ist `sticky`: jede zweite Zeile kostet den ganzen Abend rund 33 px
     Bild, auf jeder Frage. Mit `nowrap` ist das ausgeschlossen — und weil
     etwas weichen muss, weicht der Fortschritt, und zwar er allein.

     Das ist die richtige Reihenfolge: der Raumcode ist das, was die Gäste
     eintippen, und „Verbindung weg…" ist der Satz, wegen dem man überhaupt
     hinsieht. Der Fortschritt ist laut der Zeile darüber „eine Randnotiz,
     kein Signal" — er ist damit das Einzige, dessen Kürzung nichts kaputt
     macht.

     ⚠️ `min-width: 0` ist der Kern davon: ein Flex-Kind schrumpft ohne diese
     Zeile NIE unter die Breite seines Inhalts, und `text-overflow` bekäme nie
     etwas zu tun. Ohne sie wäre `nowrap` kein Schutz, sondern ein
     seitlicher Überlauf — und genau den meldet `mobil` als Wandprobe. */
  .pult-bar { flex-wrap: nowrap; }
  .pult-bar-room, .pult-bar-link { flex: 0 0 auto; }
  .pult-bar-fortschritt {
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
}

/* Die kurze Fassung der Fortschrittszeile („Frage 27/30 · Runde 3/3").
   Beide Fassungen stehen im DOM — `pult-ueberblick.js` schreibt sie zusammen —,
   damit das Drehen des Handys keine Neuberechnung braucht. */
.pult-bar-fortschritt-kurz { display: none; }

/* ⚠️ 360 px ist die Grenze, weil die Messung sie dort zieht, nicht weil die
   Zahl rund ist: auf 320 px fehlten dem langen Satz 17 px, auf 360 px passte
   er mit 23 px Luft. Die kurze Form spart 50 px — genug, dass auch ein
   sechsstelliger Raumcode oder „Runde 10 von 12" nicht wieder anschlägt. */
@media (max-width: 360px) {
  .pult-bar-fortschritt-lang { display: none; }
  .pult-bar-fortschritt-kurz { display: inline; }
}

.pult-main { padding: 0.75rem; max-width: 40rem; margin: 0 auto 3rem; }

/* Knöpfe für Daumen: mindestens 44 px hoch, sonst trifft man am Handy daneben —
   und ein Fehlgriff bedeutet hier eine falsche Wertung vor der ganzen Runde. */
.pult-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.pult-big {
  flex: 1 1 auto;
  min-width: 8rem;
  min-height: 46px;
  font-size: 1rem;
}

/* Das Moderationsfeld (A.17.0.0). Es soll wie ein Notizzettel aussehen und
   nicht wie ein Formular: keine harte Umrandung, aber ein sichtbarer Rand,
   damit man erkennt, dass man hineinschreiben kann. */
.pult-notiz {
  width: 100%;
  min-height: 3.2rem;
  resize: vertical;
  padding: 0.5rem 0.6rem;
  border-radius: 0.4rem;
  border: 1px dashed var(--xxl-border);
  background: rgba(0, 0, 0, 0.18);
  color: var(--xxl-text);
  font-size: 0.95rem;
  line-height: 1.4;
  font-family: inherit;
}
.pult-notiz::placeholder { color: var(--xxl-text-dim); }
.pult-notiz:focus {
  outline: none;
  border-style: solid;
  border-color: var(--xxl-accent);
}

/* Was als Naechstes kommt (A.17.0.0).
   ⚠️ Bewusst LEISER als die laufende Frage: sie ist das, was gerade gilt. Eine
   Vorschau, die genauso laut ist, zieht den Blick von der Frage weg, die
   gerade gestellt wird. Der Rundenwechsel darunter ist die Ausnahme — er
   aendert die Ansage und darf auffallen. */
.pult-vorschau {
  margin-top: 0.75rem;
  padding: 0.5rem 0.7rem;
  border-radius: 0.4rem;
  border-left: 3px solid var(--xxl-border);
  background: rgba(255, 255, 255, 0.03);
}
.pult-vorschau-kopf {
  color: var(--xxl-text-dim);
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.pult-vorschau-text {
  color: var(--xxl-text-muted);
  font-size: 0.92rem;
  line-height: 1.35;
  margin-top: 0.15rem;
  /* Bei einer langen Frage genuegen zwei Zeilen — die Vorschau soll erinnern,
     nicht vorlesen. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.pult-vorschau-wechsel {
  color: var(--xxl-accent2);
  font-weight: 700;
  font-size: 0.85rem;
  margin-bottom: 0.2rem;
}
.pult-vorschau.ist-ende {
  color: var(--xxl-text-muted);
  font-size: 0.9rem;
  border-left-color: var(--xxl-accent);
}

/* Schon gespielte Fragen (A.17.0.0). Gedaempft statt durchgestrichen: sie
   bleiben anspringbar (fuer „Frage nochmal"), und Durchstreichen liest sich
   wie „geht nicht mehr". */
.pult-qpick.ist-gespielt {
  opacity: 0.5;
}
.pult-qpick.ist-gespielt:hover { opacity: 0.85; }

.pult-qr {
  display: block;
  width: 100%;
  max-width: 200px;
  height: auto;
  margin: 0 auto;
  background: #fff;
  padding: 8px;
  border-radius: 10px;
}

/* Raumzeile auf der Beitrittsseite: bestätigt, in welchem Raum man landet,
   ohne dem Namensfeld die Aufmerksamkeit zu nehmen. */
.buzzer-raumzeile {
  text-align: center;
  color: var(--xxl-text-muted);
  margin-bottom: 1.25rem;
}
.buzzer-code-klein {
  color: var(--xxl-accent2);
  font-weight: 800;
  letter-spacing: 0.15em;
  font-size: 1.25rem;
}

/* ---- Rechtliche Seiten (A.4.2.0) ------------------------------------------ */

.legal-body {
  background: var(--xxl-bg);
  min-height: 100vh;
  margin: 0;
  color: var(--xxl-text-muted);
}
.legal-container { max-width: 780px; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
.legal-header {
  display: flex; justify-content: space-between; align-items: center; gap: 1rem;
  margin-bottom: 1.5rem; padding-bottom: 1rem;
  border-bottom: 1px solid var(--xxl-border);
}
.legal-header h1 {
  font-size: 1.5rem; font-weight: 800;
  color: var(--xxl-accent);
}
.legal-content {
  background: var(--xxl-card);
  border: 1px solid var(--xxl-border);
  border-radius: 12px;
  padding: 1.5rem;
  line-height: 1.75;
}
.legal-content h2 {
  font-size: 0.95rem; font-weight: 700;
  color: var(--xxl-accent2);
  letter-spacing: 0.06em; text-transform: uppercase;
  margin: 1.75rem 0 0.6rem;
}
.legal-content h2:first-child { margin-top: 0; }
.legal-content h3 {
  font-size: 0.95rem; font-weight: 700;
  color: var(--xxl-text);
  margin: 1.25rem 0 0.4rem;
}
.legal-content p { color: var(--xxl-text-muted); font-size: 0.95rem; margin-bottom: 0.75rem; }
.legal-content ul {
  color: var(--xxl-text-muted); font-size: 0.95rem;
  margin: 0 0 0.75rem 1.25rem; list-style: disc;
}
.legal-content li { margin-bottom: 0.25rem; }
.legal-content a { color: var(--xxl-accent); text-decoration: none; }
.legal-content a:hover { text-decoration: underline; }
.legal-content strong { color: var(--xxl-text); }
.legal-footer {
  margin-top: 1.5rem; padding-top: 1rem;
  border-top: 1px solid var(--xxl-border);
  font-size: 0.8rem; color: var(--xxl-text-dim);
}

/* Fußzeile mit den Pflichtlinks — auch auf der Startseite. */
.legal-nav {
  margin-top: 1.5rem;
  text-align: center;
  font-size: 0.85rem;
  color: var(--xxl-text-dim);
}
.legal-nav a { color: var(--xxl-text-dim); text-decoration: none; }
.legal-nav a:hover { color: var(--xxl-accent); text-decoration: underline; }

/* ===========================================================================
   A.5.0.0 — Medien in Fragen
   Farben ausschließlich über die Variablen. Rohe Hex-Werte überleben jeden
   Palettenwechsel und wirken danach schmutzig — beim Umzug auf die Farbwelt
   von Logo und Banner ist genau das passiert.
   =========================================================================== */

/* --- Tonfreigabe auf der Bühne ------------------------------------------- */
.stage-unlock {
  /* ⚠️ ÜBER ALLEM (Audit 24.08.): vorher lag die Tonfreigabe mit z-index 50
     UNTER dem Intro (80) und dem Übergabe-Schirm (70) — der Kommentar am Intro
     behauptete sogar das Gegenteil. Wer den Beamer-Rechner neu lädt, während
     ein Intro läuft oder der Schirm steht, kam an „Bühne starten" nicht heran:
     das Video stand stumm auf dem ersten Bild, und frei wurde der Ton erst,
     wenn der Schirm von selbst wegging. Diese Schicht ist die Voraussetzung
     für alle anderen und gehört deshalb nach ganz oben. */
  position: fixed; inset: 0; z-index: 90;
  display: flex; align-items: center; justify-content: center;
  background: var(--xxl-bg);
}
.stage-unlock-box { text-align: center; padding: calc(2 * 1.25 * var(--bw)); max-width: calc(34 * 1.25 * var(--bw)); }
.stage-unlock-title {
  font-size: clamp(calc(1.8 * 1.25 * var(--bw)), calc(4 * var(--bw)), calc(3 * 1.25 * var(--bw))); font-weight: 800;
  color: var(--xxl-accent); margin-bottom: calc(0.6 * 1.25 * var(--bw));
}
.stage-unlock-text {
  font-size: clamp(calc(1 * 1.25 * var(--bw)), calc(1.8 * var(--bw)), calc(1.4 * 1.25 * var(--bw)));
  color: var(--xxl-text-muted); margin-bottom: calc(1.8 * 1.25 * var(--bw));
}
.stage-unlock-btn {
  font-size: clamp(calc(1.1 * 1.25 * var(--bw)), calc(2.2 * var(--bw)), calc(1.7 * 1.25 * var(--bw))); font-weight: 700;
  padding: 0.7em 2em; border-radius: 999px; cursor: pointer;
  color: var(--xxl-bg); background: var(--xxl-accent); border: none;
  box-shadow: var(--xxl-shadow-lg);
}
.stage-unlock-btn:hover { background: var(--xxl-accent-hover); }

/* --- Medium zur Frage ----------------------------------------------------- */
/* min-height: 0 ist Pflicht, nicht Kosmetik: ein Flex-Kind schrumpft sonst
   nicht unter seine Inhaltsgröße, das Bild schöbe die Buzzer-Liste aus dem
   Bild und niemand sähe mehr, wer dran ist. */
.stage-media {
  flex: 1 1 auto; min-height: 0;
  display: flex; align-items: center; justify-content: center;
  /* Seit A.5.8.0 stehen hier MEHRERE Medien nebeneinander. Der Abstand hält
     sie auseinander, das Umbrechen fängt den Fall ab, dass drei Bilder nicht
     mehr in eine Reihe passen. */
  flex-wrap: wrap;
  gap: clamp(calc(0.5 * 1.25 * var(--bw)), calc(1.5 * var(--bw)), calc(1.25 * 1.25 * var(--bw)));
  width: 100%;
}
/* Bei mehreren Medien teilen sie sich die Breite, statt dass das erste alles
   nimmt und die anderen aus dem Bild schiebt. */
.stage-media > * { min-width: 0; max-width: 100%; }

/* --- FREI GESETZTE MEDIEN (A.20.9.0) ---------------------------------------
   Sein Befund: „die Bilder … bitte auch positionieren und in der Größe
   edieren können". Die Maße setzt `buehnenbild.js` als Prozentwerte ins
   `style`-Attribut; was hier steht, ist alles, was NICHT vom einzelnen Bild
   abhängt.

   ⚠️ Sie bleiben Kinder von `.stage-media` — `buehne-effekt.js` sucht seine
   Bilder als `#stage-media [data-medium="…"]`. Ein eigener Behälter hätte
   jede Enthüllung an einem freien Bild stillschweigend tot gestellt. */
.stage-media > .ist-frei {
  position: absolute;
  /* x/y sind der MITTELPUNKT: das ist der Punkt unter dem Finger beim Ziehen,
     und beim Größerziehen wächst das Bild nach allen Seiten gleichmäßig. */
  transform: translate(-50%, -50%);
  max-width: none;   /* die Breite steht im style-Attribut und ist gewollt */
  margin: 0;
  z-index: 0;        /* siehe Stapelordnung oben: Text liegt IMMER darüber */
}
/* Die Höhe folgt dem Seitenverhältnis. ⚠️ Das `max-height: 46vh` der Reihe
   muss hier weg — es würde jede Breitenangabe über etwa 60 % wieder
   einkassieren, und der Regler sähe aus, als hörte er ab der Hälfte auf. */
.stage-media > .ist-frei.stage-media-image,
.stage-media > .ist-frei .stage-media-image {
  max-height: none;
  max-width: none;
  width: 100%;
  height: auto;
}
.stage-main.zeigt-loesung .stage-media > .ist-frei .stage-media-image,
.stage-main.zeigt-loesung .stage-media > .ist-frei.stage-media-image {
  max-height: none;   /* frei heißt frei — auch neben der Lösung */
}
/* --- DER FREI GESETZTE TEXTBLOCK (A.20.11.0) --------------------------------
   Sein Befund vom 25.08.: „Den Textblock muss ich auch verschieben und in der
   Größe bearbeiten können." Dieselbe Fläche, dieselben Prozente und dieselbe
   Rechnung wie bei den Bildern — `buehnenbild.js` setzt die Maße ins
   style-Attribut, hier steht nur, was NICHT von der einzelnen Frage abhängt.

   ⚠️⚠️ DIESE REGEL MUSS NACH `.stage-question-wrap > *:not(.stage-media)`
   STEHEN. Jene setzt `position: relative` auf alles Geschriebene, und sie hat
   dieselbe Spezifität wie diese hier (0,2,0 — `:not()` zählt sein Argument
   mit). Bei Gleichstand gewinnt die SPÄTERE. Wandert dieser Block nach oben,
   bleibt der Text im Fluss stehen, ohne dass irgendetwas rot wird: die Maße
   stehen im style-Attribut, sie tun nur nichts.

   ⚠️ `z-index: 1` bleibt — der Text liegt IMMER über den Bildern. Frei setzen
   ändert daran nichts: ein groß gezogenes Bild darf auch dann nicht die Frage
   verdecken, wenn beide von Hand platziert sind. */
.stage-question.ist-frei {
  position: absolute;
  /* x/y sind der MITTELPUNKT — derselbe Bezugspunkt wie beim Bild: der Punkt
     unter dem Finger beim Ziehen, und beim Größerziehen wächst der Block nach
     allen Seiten gleichmäßig statt an einer Ecke wegzulaufen. */
  transform: translate(-50%, -50%);
  z-index: 1;
  margin: 0;
  /* Das `90vw` der festen Fassung fällt weg: wer den Block selbst gesetzt hat,
     hat auch entschieden, wie breit er sein darf. Die `30ch` bleiben — sie
     wachsen mit der Schrift mit, das Zeilenmaß bleibt also dasselbe, und genau
     daran hängt die Größe (siehe `buehnenbild.js`: die Skala ist das Maß, die
     Breite folgt ihr).

     ⚠️⚠️ `width: max-content` IST PFLICHT, und zwar aus einem Grund, den man
     nicht sieht, sondern nur misst: ein absolut gesetzter Block ohne Breite
     bekommt die „shrink-to-fit"-Breite, und die ist nach oben durch den PLATZ
     BIS ZUM RAND begrenzt — bei `left: 42%` also durch 58 % der Fläche. Der
     Block wurde damit umso schmaler, je weiter rechts er stand, und dieselbe
     Zeile brach an einer anderen Stelle um als vorher. Mit `max-content` gilt
     wieder allein das Zeilenmaß. */
  width: max-content;
  max-width: 30ch;
  /* Siehe die lange Begründung bei `--fragetext-mindest` weiter unten: an
     einem von Hand gesetzten Block gelten die VERHÄLTNISSE, nicht die 13 px. */
  --fragetext-mindest: 0px;
}

/* Stehen NUR freie Medien drin, darf der Behälter keinen Platz mehr nehmen:
   ein leerer, wachsender Kasten schöbe die Frage sonst aus der Mitte. */
.stage-media.nur-frei {
  flex: 0 0 auto;
  min-height: 0;
  height: 0;
}
.stage-media-image {
  max-width: min(calc(92 * var(--bw)), 100%);
  /* Die Höhe MUSS in vh stehen, nicht in Prozent. `max-height: 100%` bezieht
     sich auf .stage-media-buehne, und deren Höhe ergibt sich erst aus dem Bild
     — eine Prozentangabe ohne feste Bezugshöhe löst sich zu „keine Grenze" auf,
     und das Bild rendert in Naturgröße. Auf einem 1080er Beamer fiel das nie
     auf, weil es zufällig passte; auf 1600×950 schob das Bild die Zeile
     „X ist dran" 432 px unter den Rand — im Buzz-Moment ausgerechnet die
     wichtigste Zeile. `overflow: hidden` weiter oben verbarg es nur, statt es
     zu verhindern. 46vh lässt bei 1080p alles wie bisher aussehen und hält
     auch auf flachen Schirmen Frage, Lösung und Buzz-Zeile im Bild.

     ⚠️⚠️⚠️ UND NIE HÖHER ALS DER EIGENE BEHÄLTER (A.20.38.0). Die feste Zahl
     allein reicht nicht: `.stage-media` ist ein Flex-Kasten mit
     `align-items: center`, und ein zu hohes Kind wird dort NICHT begrenzt — es
     ragt oben und unten heraus. Nachgemessen an einer Wettfrage mit Bild: der
     Behälter war 190,7 px hoch, das Bild 216 px, es ragte 32 px heraus und lag
     damit über der ersten Antwortzeile. Genau sein Befund vom 26.08.: „trotz
     korrekter Einstellung im Editor sieht es im Spiel so aus".

     ▶ DAS IST EIN FORMFEHLER, KEINE ZAHL — und er ist HIER NICHT BEHOBEN. Was
     gemeint wäre, ist „so hoch wie erlaubt, aber nie höher als der Platz". Der
     naheliegende Ausdruck dafür, `min(calc(46 * var(--bh)), 100%)`, ist am
     26.08.2026 PROBIERT UND GEMESSEN WORDEN und macht es SCHLIMMER: das Bild
     ging auf 480 px und ragte 274 px heraus statt 32. Der Einwand oben gilt
     also unverändert — das Prozent löst sich nicht auf, auch nicht innerhalb
     von `min()`, weil `.stage-media` mit `flex: 1 1 auto` keine definite Höhe
     hat. Wer es noch einmal versucht, misst dasselbe.

     Ein echter Griff müsste am BEHÄLTER ansetzen (`flex: 1 1 0` macht seine
     Höhe berechenbar, ändert aber die Verteilung für JEDE Frage mit Medium)
     oder an `align-items` — beides ist ein Layout-Umbau und keine Zeile hier.
     Was A.20.38.0 tatsächlich behebt, steht unten bei `.zeigt-wette`: das Bild
     gibt Höhe ab, sobald unter den Optionen noch ein Kasten steht. Damit sank
     die Überlappung von zwölf Prozentpunkten auf anderthalb — die Rahmenlinie
     des Antwortkastens. Der Rest wartet auf seine Entscheidung. */
  /* ▶ A.20.45.0 — DAS BILD HAT JETZT EIN EIGENES MASS. Seine Ansage vom
     26.08.: „ja, gib dem Bild ein eigenes Maß."

     `--medienplatz` setzt `buehne-medienplatz.js` an den Medienkasten: es ist
     der Platz, der im Fragenfeld WIRKLICH frei ist, gemessen mit einem für
     einen Augenblick zusammengefalteten Kasten (die lange Begründung steht
     dort). Die feste Zahl bleibt als OBERGRENZE stehen — ein Bild soll auch
     dann nicht die ganze Bühne fluten, wenn sonst nichts darauf steht.

     ⚠️ DIE DREI TEILE DES `clamp` sind drei verschiedene Aussagen:
       Boden  8vh   ein Bild, das gar nicht mehr zu sehen ist, ist ein
                    stiller Fehler. Greift der Boden, hat die Frage mehr
                    Inhalt als Bühne — `buehne-medienplatz.js` meldet das
                    als `bodenGreift`, damit ein Prüfstand es ablesen kann.
       Maß          der gemessene Platz.
       Kappe        die alte feste Zahl, jetzt in `--medienkappe`, damit die
                    Regeln für Lösung und Wette nur EINEN Wert umstellen und
                    nicht die ganze Zeile abschreiben.

     ⚠️ OHNE `--medienplatz` (etwa bevor das Bauteil das erste Mal gemessen
     hat) fällt der Ausdruck auf `100vh` zurück, und `clamp` liefert die
     Kappe — also genau das Verhalten von vorher. Ein Bau, der beim ersten
     Bild eine Sekunde lang falsch aussieht, wäre schlimmer als der alte. */
  --medienkappe: calc(46 * var(--bh));
  --medienboden: calc(8 * var(--bh));
  max-height: clamp(var(--medienboden), var(--medienplatz, 100vh), var(--medienkappe));
  object-fit: contain;
  border-radius: calc(0.5 * 1.25 * var(--bw));
  box-shadow: var(--xxl-shadow-lg);
}
/* Sobald die Lösung steht, braucht sie Platz — und der kann nur vom Bild
   kommen. Ohne das blieb das Bild gleich groß und schob den Lösungskasten
   unten aus dem Schirm; auf 1366×768 fehlten die unteren 28 px, und
   „Lösung aufdecken" sah aus, als täte es nichts. buehne.js setzt die Klasse. */
.stage-main.zeigt-loesung .stage-media-image { --medienkappe: calc(30 * var(--bh)); }
/* ⚠️⚠️ UND BEI DER WETTE GENAUSO (A.20.38.0, sein Befund vom 26.08.: „trotz
   korrekter Einstellung im Editor sieht es im Spiel so aus" — das Bild lag über
   den Antworten).

   `zeigt-wette` nahm bisher nur FRAGE und OPTIONEN kleiner (Zeile ~5880), das
   BILD aber nicht — dabei kostet der Erklärkasten der Wette genauso Höhe wie
   der Lösungskasten, und die Bühne scrollt nicht. Nachgemessen an einer
   Wettfrage mit Bild und vier Möglichkeiten, in Prozent des Bühnenfeldes:

     Bild        21,9 % → 67,9 %
     Antworten   55,8 % → 70,3 %     ← zwölf Punkte Überlappung
     Wettkasten  71,1 % → 79,0 %

   Dieselbe Zahl wie bei der Lösung und aus demselben Grund. Wer hier einen
   eigenen Wert einführt, hat zwei Zahlen zu pflegen, wo eine Regel gilt: das
   Bild gibt Höhe ab, sobald unter den Optionen noch ein Kasten steht. */
.stage-main.zeigt-wette .stage-media-image { --medienkappe: calc(30 * var(--bh)); }
.stage-main.zeigt-wette .stage-media > .ist-frei .stage-media-image,
.stage-main.zeigt-wette .stage-media > .ist-frei.stage-media-image {
  max-height: none;   /* frei heißt frei — auch neben der Wette */
}
/* Ton hat kein Bild. Ohne sichtbares Zeichen wirkt eine stumme Bühne wie ein
   Fehler — die Note pulsiert deshalb, solange etwas läuft. */
.stage-media-audio {
  display: flex; align-items: center; justify-content: center;
  width: clamp(calc(6 * 1.25 * var(--bw)), calc(18 * var(--bh)), calc(14 * 1.25 * var(--bw))); height: clamp(calc(6 * 1.25 * var(--bw)), calc(18 * var(--bh)), calc(14 * 1.25 * var(--bw)));
  border-radius: 50%;
  background: var(--xxl-card);
  border: 4px solid var(--xxl-accent2);
  box-shadow: var(--xxl-shadow-lg);
}
.stage-media-audio-mark {
  font-size: clamp(calc(2.5 * 1.25 * var(--bw)), calc(9 * var(--bh)), calc(6 * 1.25 * var(--bw)));
  color: var(--xxl-accent2);
  animation: stage-ton-puls 1.6s ease-in-out infinite;
}
@keyframes stage-ton-puls {
  0%, 100% { transform: scale(1);    opacity: 0.85; }
  50%      { transform: scale(1.12); opacity: 1; }
}
/* Wer Bewegung nicht verträgt, bekommt sie nicht aufgezwungen. */
@media (prefers-reduced-motion: reduce) {
  .stage-media-audio-mark { animation: none; }
}

/* Eine stumme Spur (Stem-Modus): läuft synchron mit, ist aber noch nicht zu
   hören. Sie muss ANDERS aussehen — zwei gleiche Notenzeichen, von denen eines
   nichts tut, wirken auf dem Beamer wie ein Anzeigefehler. Gedämpft und ohne
   Puls ist sie eine Ankündigung: da kommt noch etwas. Beim Auflösen fällt die
   Klasse weg und die Spur leuchtet auf. */
.stage-media-audio.ist-stumm {
  opacity: 0.4;
  border-color: var(--xxl-border);
  box-shadow: none;
}
.stage-media-audio.ist-stumm .stage-media-audio-mark {
  animation: none;
  color: var(--xxl-text-dim);
}
.stage-media-audio { transition: opacity 0.5s ease, border-color 0.5s ease; }
/* Die Auflösung steht allein — das Fragen-Bild tritt beim Aufdecken ab und
   überlässt ihr den ganzen Platz. */
.stage-media-answer { flex: 1 1 auto; min-height: 0; }
.stage-media-answer .stage-media-image { border: 3px solid var(--xxl-success); }

/* --- Medien im Editor ----------------------------------------------------- */
.quiz-medien { display: flex; flex-direction: column; gap: 0.5rem; }
/* ⚠️ KEIN eigener Rahmen mehr (A.20.7.0): die Zeitachse der Station trägt die
   Gliederung. Drei gleich aussehende gestrichelte Kästen untereinander waren
   genau der „Einheitsbrei" aus Stefans Befund — sie sagten „hier sind drei
   gleichwertige Dinge", während in Wahrheit eine zeitliche Folge dasteht.
   Der Einzug kommt aus `.quiz-station`. */
.quiz-medienplatz { border: 0; }
.quiz-medienplatz-titel {
  font-size: 0.9rem; color: var(--xxl-text-dim);
  display: inline-flex; align-items: baseline; gap: 0.4rem; flex-wrap: wrap;
}
.quiz-medienplatz-hinweis { color: var(--xxl-text-muted); font-size: 0.8rem; }
.quiz-medienvorschau { display: flex; flex-direction: column; gap: 0.35rem; }
.quiz-medienbild {
  /* 9rem statt 12: die Karte einer einzigen Frage war mit zwei Bildern und dem
     Intro-Video fast 1400 px hoch — bei zwanzig Fragen scrollt man an der
     Übersicht vorbei, die der Editor sein soll. Erkennen kann man ein Bild
     auch auf 9rem; ansehen tut man es auf der Bühne. */
  max-width: 100%; max-height: 9rem;
  object-fit: contain;
  border-radius: 0.3rem;
  align-self: flex-start;
  background: var(--xxl-sidebar);
}
/* Dateiangabe und „Entfernen" in EINER Zeile unter dem Bild.
   ⚠️ NICHT `space-between`: der Knopf stand dann am rechten Kartenrand, einen
   halben Meter neben dem Bild, zu dem er gehört. Er steht direkt hinter der
   Dateiangabe. */
.quiz-mediendatei {
  font-size: 0.8rem; color: var(--xxl-text-muted);
  display: flex; align-items: center;
  gap: 0.75rem; flex-wrap: wrap;
}
.quiz-mediendatei .quiz-medien-weg { padding: 0.15rem 0.5rem; font-size: 0.8rem; }
/* Die Enthüllung gehört zu DEM Bild darüber, nicht zum Platz. Deshalb hängt
   sie an einer eigenen Kante und liegt eingerückt darunter — vorher lag sie
   auf derselben Ebene wie alles andere und war mit sieben großen Knöpfen die
   lauteste Stelle der ganzen Karte. */
.quiz-effekt {
  border-top: 0;
  border-left: 2px solid var(--xxl-border-light);
  border-radius: 0 0.3rem 0.3rem 0;
  background: rgba(255, 255, 255, 0.02);
  padding: 0.45rem 0.5rem 0.5rem 0.6rem;
}
.quiz-effekt-label { font-size: 0.9rem; color: var(--xxl-text-dim); }
/* ⚠️ Über `.quiz-effekt` mitgeschrieben, damit die Regel schwerer wiegt als
   `.xxl-btn-ghost`/`.xxl-btn-accent`, die derselbe Knopf mitträgt. */
.quiz-effekt .quiz-effekt-wahl { font-size: 0.8rem; padding: 0.22rem 0.55rem; }

/* --- Die Fragenkarte: Frage oben, Ablauf darunter (A.20.7.0) ---------------
   Stefans Befund vom 25.08.: „Der Fragenbereich muss aufgeräumter, logischer,
   übersichtlicher und anders angeordnet sein … es ist optisch alles ein
   ,Einheitsbrei'."

   Zwei Zonen statt fünf gleich lauter Kästen:
     1. DIE FRAGE — das, was am Abend gefragt wird. Am lautesten.
     2. DER ABLAUF — was davor läuft, was zur Frage gehört, was beim Auflösen
        kommt. Drei Stationen an EINER Zeitachse, in der Reihenfolge des
        Abends. Vorher stand das Intro ganz unten, obwohl es zuerst läuft.  */

.quiz-fragekopf {
  display: flex; align-items: flex-start; justify-content: space-between;
  column-gap: 0.75rem; row-gap: 0.9rem; flex-wrap: wrap;
}
/* ⚠️ 24rem ist kein Gefühl, sondern die Bruchkante: passt das Werkzeug daneben
   nicht mehr ohne die Frage auf drei Zeilen zu quetschen, rutscht es unter den
   Fragetext statt ihn schmal zu machen. Am 1280er Laptop tut es genau das —
   dort hatte die Frage sonst 330 px und die Knöpfe 400. */
.quiz-fragekopf-text { flex: 1 1 24rem; min-width: 0; }
/* Die Frage ist der Grund, warum die Karte da ist — und war bis hierher
   genauso laut wie die Beschriftung eines Medienplatzes. */
.quiz-fragetext {
  color: var(--xxl-text);
  font-size: 1.05rem;
  font-weight: 600;
  line-height: 1.35;
}
.quiz-fragewerkzeug {
  display: flex; align-items: center; justify-content: flex-end;
  gap: 0.3rem; flex-wrap: wrap;
  /* Rutscht die Leiste unter die Frage (schmaler Schirm, lange Frage), bleibt
     sie rechts stehen — sonst wechselt sie beim Ziehen des Fensters die Seite
     und man sucht „Ändern" zweimal an verschiedenen Orten. */
  margin-left: auto;
}
.quiz-fragepunkte {
  font-size: 0.9rem; font-weight: 700; white-space: nowrap;
  color: var(--xxl-accent);
  margin-right: 0.2rem;
}
/* Sechs Bedienelemente in einer Zeile: in Normalgröße schoben sie die Frage
   auf halbe Breite und zogen mehr Blick als der Fragetext. */
.quiz-fragewerkzeug .xxl-btn-ghost,
.quiz-fragewerkzeug .xxl-input {
  padding: 0.25rem 0.55rem;
  font-size: 0.85rem;
}
/* „Ändern" ist die Handlung, die man ständig braucht — sie darf man finden,
   ohne zu lesen. */
.quiz-fragewerkzeug .quiz-werkzeug-haupt {
  border-color: var(--xxl-accent);
  color: var(--xxl-accent);
}

/* Der Ablauf-Streifen. */
.quiz-ablauf {
  display: flex; flex-direction: column; gap: 0.5rem;
  border-top: 1px solid var(--xxl-border-light);
  padding-top: 0.7rem;
}
/* Eine Station an der Achse. `--station` ist ihre Farbe; jede Station setzt
   sie selbst, und alles in ihr greift darauf zu. */
.quiz-station {
  --station: var(--xxl-text-dim);
  position: relative;
  padding-left: 1.15rem;
}
/* Die Linie zwischen den Stationen. Sie läuft bis in den Abstand hinein
   (`bottom: -0.5rem` = der `gap`), damit die drei Punkte wirklich an EINEM
   Faden hängen und nicht als drei Striche danebenstehen. */
.quiz-station::before {
  content: '';
  position: absolute;
  left: 0.27rem; top: 0.9rem; bottom: -0.5rem;
  border-left: 1px solid var(--xxl-border);
}
.quiz-medienplatz.quiz-station:last-child::before { display: none; }
/* Der Punkt — in der Farbe der Station. */
.quiz-station::after {
  content: '';
  position: absolute;
  left: 0; top: 0.32rem;
  width: 0.55rem; height: 0.55rem;
  border-radius: 50%;
  background: var(--station);
}
/* Die drei Farben folgen der Sprache, die die Karte oben schon spricht:
   die Lösung ist grün, der Akzent gehört der laufenden Frage. Das Intro
   bekommt Türkis — es ist das einzige, was NICHT zur Frage selbst gehört,
   sondern davor liegt.
   ⚠️ `--xxl-info`, NICHT `--xxl-accent2`: der Adminbereich trägt
   `body.marke-seite`, und dort IST `--xxl-accent2` die hellere Stufe des
   Akzents (#C77BF5 neben #9732E4). Gemessen, nicht geschätzt — am Bild waren
   die beiden Punkte nicht zu unterscheiden, die berechnete Farbe schon. */
.quiz-station-davor { --station: var(--xxl-info); }
.quiz-medienplatz[data-station="question"] { --station: var(--xxl-accent); }
.quiz-medienplatz[data-station="answer"]   { --station: var(--xxl-success); }

/* Der Name der Station. Klein, gesperrt, in ihrer Farbe: er liest sich als
   Zeitangabe („davor · zur frage · zur auflösung") und nicht als Überschrift
   eines weiteren Kastens. */
.quiz-stationsmarke {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  /* ⚠️ MIT Rückfall: dieselbe Marke beschriftet seit A.20.8.0 auch die
     Live-Vorschau im Formular, und dort steht sie in keiner Station. Ohne den
     zweiten Wert wäre `color` dort ungültig und die Marke erbte die volle
     Textfarbe — sie war lauter als die Vorschau, die sie beschriftet. */
  color: var(--station, var(--xxl-text-dim));
  white-space: nowrap;
}
/* Als Station verliert der Intro-Block seinen eigenen Kasten — sonst stünde
   ein gerahmtes Kästchen an einer Achse, an der zwei rahmenlose hängen. */
.quiz-rundenmedien.quiz-station {
  border: 0;
  border-radius: 0;
  background: none;
  padding: 0 0 0 1.15rem;
}
/* Die Knöpfe einer Station sind ANGEBOTE („+ Bild, Ton oder Video"), keine
   Handlungen, die man ständig braucht. In voller Knopfgröße standen bei einer
   Runde mit zwanzig medienlosen Fragen VIERZIG laute Knöpfe in der Liste —
   und die Frage selbst war das Leiseste daran. Am Handy hebt der
   `pointer: coarse`-Block sie wieder auf 44 px.
   ⚠️ Die Enthüllungs-Wahl ist ausgenommen: die hat ihr eigenes, noch
   kleineres Maß und soll es behalten. */
.quiz-station .xxl-btn-ghost:not(.quiz-effekt-wahl) {
  font-size: 0.85rem;
  padding: 0.28rem 0.6rem;
}

/* Innerhalb einer Station sind „Intro dieser Frage" und „Wasserzeichen" eine
   Ebene TIEFER als der Stationsname — in Weiß waren sie lauter als er.
   ⚠️ Nur in der Station: dieselbe Klasse trägt auch der Quizkopf (Logo,
   Banner, Übergabebild), und der wurde nicht bestellt. */
.quiz-station .quiz-markeplatz-titel {
  font-size: 0.85rem;
  color: var(--xxl-text-dim);
}

/* --- Der ausgezeichnete Fragetext (A.20.8.0) -------------------------------
   Sein Befund vom 25.08.: „Ich kann die Frage noch immer nicht in einem Editor
   bearbeiten, in dem ich die Darstellung anpassen kann."

   ⚠️⚠️ Der Befund dahinter: sein handgesetzter Zeilenumbruch kam NIRGENDS an.
   Jede Anzeige setzte den Text mit `textContent`, und im HTML wird aus einem
   Umbruch ein Leerzeichen. Jetzt baut `public/fragetext.js` daraus Zeilen —
   und diese beiden Klassen sind, was dabei herauskommt.

   Beide Maße sind in `em`: dieselbe Auszeichnung steht auf einem Beamer in
   4 rem und in der Fragenliste in 1,05 rem, und sie soll in beiden dasselbe
   VERHÄLTNIS haben. */
.fragetext-zeile { display: block; }
/* Eine Leerzeile ist ein Absatz und muss Platz behalten — als leerer Block
   wäre sie 0 px hoch und die Trennung, die er getippt hat, verschwunden. */
.fragetext-zeile:empty { height: 0.55em; }
/* ⚠️ BETONUNG IST FARBE, NICHT GEWICHT. Am eigenen Bühnenbild gesehen: die
   ganze Frage steht dort in 700, ein `<strong>` daneben ist nicht zu
   unterscheiden — „**sechs Wochen**" sah aus wie der Rest des Satzes. Ein
   Knopf, der nichts sichtbar macht, ist schlimmer als keiner: man drückt ihn
   und glaubt, es sei betont. */
.fragetext-zeile strong,
.fragetext-thema strong {
  color: var(--xxl-accent-light);
  font-weight: 800;
}
/* Die THEMA-Zeile über der Frage. Kleiner und in der Akzentfarbe: sie ist die
   Ansage, nicht die Frage — stünde sie in derselben Größe da, schwächte sie
   genau das, was gefragt wird.
   ⚠️ `max(var(--fragetext-mindest), …)`: 13 px sind die Untergrenze aus der
   Handy-Regel. In der Fragenliste wären 0,68 em sonst 11 px.

   ⚠️⚠️ UND SIE IST ABSCHALTBAR — an einem FREI GESETZTEN Block (A.20.11.0).
   Der Grund ist gemessen, nicht vermutet: zieht man den Block klein, sinkt die
   Grundschrift unter 19 px, und ab da greift die Untergrenze. Die Themazeile
   wird dann GRÖSSER als die Frage, die sie überschreibt — die Rangfolge kippt.
   In der Vorschau (kleine Fläche) passierte das viel früher als auf der Bühne:
   derselbe Text stand dort auf siebzehn Zeilen und im Saal auf zwölf, ein
   Unterschied von 17 Prozentpunkten in der Höhe. Eine Vorschau, die anders
   umbricht als der Beamer, ist genau das, wogegen `buehnenbild.js` gebaut ist.

   Ein von Hand gesetzter Block ist ein BILD, das er komponiert hat, keine
   Fläche zum Lesen — dort gelten die Verhältnisse, nicht die 13 px. Überall
   sonst (Fragenliste, Karte, Pult, nicht gesetzter Block) bleibt alles, wie es
   war: `--fragetext-mindest` steht dort weiter auf 13 px. */
.fragetext-thema {
  display: block;
  font-size: max(var(--fragetext-mindest, 0.8125rem), 0.68em);
  line-height: 1.25;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--xxl-accent);
  margin-bottom: 0.35em;
}

/* --- Die übrigen Zeilenarten und Wortmarken (A.20.9.0) ---------------------
   Sein Befund auf A.20.8.0: „Das reicht mir noch lange nicht an
   Formatierungsmöglichkeiten." Am Bild dazu sieht man den Grund mit: auf der
   Bühne steht ALLES in derselben Größe, demselben Gewicht, derselben Farbe.

   ⚠️⚠️ DIE LATTE FÜR JEDE EINZELNE VON DIESEN: sie muss sich AUF DER BÜHNE
   abheben — dort, wo die ganze Frage in 700 und in clamp(1.6rem, 4.5vw, 4rem)
   steht. Genau daran ist in A.20.8.0 das schlichte `<strong>` gescheitert.
   Deshalb trägt hier jede Art ZWEI Unterschiede, nie nur einen: Größe UND
   Farbe, oder Farbe UND Fläche, oder Einzug UND Kante.

   Alle Maße in `em`: dieselbe Auszeichnung steht auf dem Beamer in 4 rem und
   in der Fragenliste in 1,05 rem und soll in beiden dasselbe VERHÄLTNIS
   haben. */

/* ZUSATZ (`> `) — der Nachsatz, der nicht so laut sein soll wie die Frage.
   Genau sein Fall: „länger als jeder andere in diesem Jahr" stand bisher so
   groß da wie die Frage selbst. Kleiner UND gedämpft. */
.fragetext-zusatz {
  display: block;
  font-size: max(var(--fragetext-mindest, 0.8125rem), 0.62em);
  line-height: 1.3;
  font-weight: 600;
  color: var(--xxl-text-muted);
  margin-top: 0.15em;
}

/* AUFZÄHLUNG (`- `) und NUMMER (`1. `) — beide linksbündig INNERHALB des
   mittigen Satzblocks. Eine zentrierte Aufzählung ist keine: das Auge
   braucht die gemeinsame linke Kante, um die Punkte als Reihe zu lesen. */
.fragetext-punkt,
.fragetext-nummer {
  display: block;
  text-align: left;
  font-size: max(var(--fragetext-mindest, 0.8125rem), 0.85em);
  line-height: 1.3;
  padding-left: 1.4em;
  text-indent: -1.4em;   /* die Folgezeile rückt unter den Text, nicht unter den Punkt */
  margin-top: 0.18em;
}
.fragetext-punkt::before {
  content: '•';
  color: var(--xxl-accent);
  display: inline-block;
  width: 1.4em;
  text-indent: 0;
}
.fragetext-nummer-zahl {
  color: var(--xxl-accent);
  display: inline-block;
  width: 1.4em;
  text-indent: 0;
}

/* ZITAT (`" `) — Songzeile, Spruch. Kursiv UND an einer eigenen Kante: die
   Schrägstellung allein verschwindet auf einem Beamer aus zehn Metern. */
.fragetext-zitat {
  display: block;
  text-align: left;
  font-style: italic;
  font-weight: 600;
  font-size: max(var(--fragetext-mindest, 0.8125rem), 0.9em);
  line-height: 1.3;
  color: var(--xxl-text-muted);
  border-left: 0.14em solid var(--xxl-accent);
  padding-left: 0.55em;
  margin: 0.2em 0;
}

/* TRENNLINIE (`---`) — reine Fläche, deshalb ohne Inhalt. Sie steht mittig
   und nimmt nicht die volle Breite: eine randlose Linie über den ganzen Satz
   sieht aus wie ein Fehler im Bild. */
.fragetext-linie {
  display: block;
  width: 38%;
  height: 0.06em;
  min-height: 2px;
  margin: 0.55em auto;
  background: var(--xxl-accent);
  opacity: 0.55;
  border-radius: 0.03em;
}

/* UNTERSTRICHEN (`__so__`) — das gesuchte Wort. Der Strich trägt die
   Akzentfarbe und hat Abstand zur Schrift; ein anliegender Standardstrich
   verschmiert bei 4 rem mit den Unterlängen. */
.fragetext-zeile u,
.fragetext-thema u,
.fragetext-zusatz u,
.fragetext-punkt u,
.fragetext-nummer u,
.fragetext-zitat u {
  text-decoration: underline;
  text-decoration-color: var(--xxl-accent);
  text-decoration-thickness: 0.07em;
  text-underline-offset: 0.16em;
}

/* MARKIERT (`==so==`) — die lauteste Stufe: nicht nur andere Farbe, sondern
   eine eigene FLÄCHE. Dunkle Schrift auf der Signalfarbe, damit der Kontrast
   nicht von der Bühnenhelligkeit abhängt. */
.fragetext-zeile mark,
.fragetext-thema mark,
.fragetext-zusatz mark,
.fragetext-punkt mark,
.fragetext-nummer mark,
.fragetext-zitat mark {
  background: var(--xxl-warning);
  color: #241033;
  padding: 0.04em 0.24em;
  border-radius: 0.16em;
  box-decoration-break: clone;          /* über einen Zeilenumbruch hinweg */
  -webkit-box-decoration-break: clone;
}

/* Die Textfläche für die Frage. Vier Zeilen als Grundmaß, und sie wächst mit
   (die Höhe setzt der Editor selbst) — zwei Zeilen zeigten von einer
   vierzeiligen Frage die Hälfte. */
.quiz-fragefeld {
  min-height: 5.5rem;
  line-height: 1.45;
  resize: vertical;
}
/* Die Werkzeuge stehen UNTER dem Feld — zwischen Beschriftung und Feld darf
   nichts stehen, siehe die Warnung in `fragen-editor.html`. */
.quiz-schriftwerkzeug {
  display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap;
  margin: 0.4rem 0 0.6rem;
}
.quiz-schriftwerkzeug .xxl-btn-ghost {
  padding: 0.2rem 0.6rem;
  font-size: 0.85rem;
  min-width: 2.2rem;
}
/* ZWEI GRUPPEN statt einer Reihe (A.20.9.0). Bei zehn Knöpfen nebeneinander
   sucht man jeden einzeln; mit der Trennung sucht man erst „Zeile oder Wort"
   und dann in einer Handvoll. Die Marke ist klein und gesperrt — dieselbe
   Sprache wie die Stationen im Formular. */
.quiz-werkzeuggruppe {
  display: inline-flex; align-items: center; gap: 0.3rem; flex-wrap: wrap;
  padding-left: 0.55rem;
  border-left: 2px solid var(--xxl-border);
}
.quiz-werkzeuggruppe:first-child { padding-left: 0; border-left: 0; }
.quiz-werkzeugmarke {
  font-size: 0.68rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--xxl-text-dim);
  margin-right: 0.1rem;
}
/* Der Marker-Knopf zeigt, was er tut — sonst ist „M" nur ein Buchstabe. */
.quiz-werkzeug-mark {
  background: var(--xxl-warning) !important;
  color: #241033 !important;
  font-weight: 800;
}

/* --- Farbe und Größe (A.20.10.0) -------------------------------------------
   Sein Befund: „Farben und Schriftgröße auch noch."

   ⚠️ Ein Farbknopf MUSS seine Farbe zeigen. Eine Reihe gleich aussehender
   Knöpfe mit den Namen daneben wäre schmaler, aber man müsste bei jedem lesen,
   statt zu sehen — und auf einer Bühne wählt man Farben nach dem Auge. */
.quiz-farbpunkt {
  width: 1.15rem;
  height: 1.15rem;
  border-radius: 50%;
  border: 2px solid transparent;
  padding: 0;
  cursor: pointer;
  flex: 0 0 auto;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.35) inset;
}
.quiz-farbpunkt:hover,
.quiz-farbpunkt:focus-visible {
  border-color: var(--xxl-text);
  outline: none;
}
/* Der freie Wähler steht am Ende der Palette und ist genauso groß wie ein
   Punkt — er ist eine Farbe mehr, keine andere Art von Bedienung. */
.quiz-farbwahl {
  width: 1.5rem;
  height: 1.35rem;
  padding: 0;
  border: 1px solid var(--xxl-border);
  border-radius: 0.25rem;
  background: none;
  cursor: pointer;
  flex: 0 0 auto;
}
/* A− und A+ zeigen im Knopf selbst, in welche Richtung es geht.
   ⚠️ Mit derselben Spezifität geschrieben wie `.quiz-schriftwerkzeug
   .xxl-btn-ghost` weiter oben — NICHT mit `!important`. Das stand hier zuerst
   und hat prompt den 13-px-Block für Handys überstimmt: `mobil` meldete „A−"
   mit 11,5 px, und die Regel dort war wirkungslos, weil sie kein `!important`
   trägt. Wer eine Größe mit `!important` festnagelt, nagelt sie auch für die
   Fälle fest, in denen sie falsch ist. */
.quiz-schriftwerkzeug .quiz-werkzeug-kleiner { font-size: 0.72rem; }
.quiz-schriftwerkzeug .quiz-werkzeug-groesser { font-size: 1rem; line-height: 1; }
.quiz-schriftwerkzeug-hinweis {
  font-size: 0.8rem;
  color: var(--xxl-text-muted);
}

/* Die Live-Vorschau: ein Ausschnitt der Bühne, klein. Sie zeigt denselben
   Grund und dieselbe Satzart — eine Vorschau auf hellem Grund in Fließtext
   hätte über das Aussehen auf dem Beamer nichts gesagt. */
.quiz-fragevorschau { display: block; }
.quiz-fragevorschau-buehne {
  margin-top: 0.35rem;
  background: var(--xxl-sidebar);
  border: 1px solid var(--xxl-border);
  border-radius: 0.5rem;
  padding: 0.6rem;
  text-align: center;
  /* ⚠️⚠️⚠️ DIESE EINE ZEILE IST NÖTIG, SEIT DER SCHIRM SELBST IN `--bw`
     RECHNET (A.20.47.0) — und der Grund ist eine Feinheit, die still danebengeht:
     `cqw` wird IMMER gegen den nächsten Container AUSSERHALB des Elements
     aufgelöst. Für die Kinder des Schirms ist das der Schirm (er trägt
     `container-type`), für den SCHIRM SELBST aber der nächste darüber — und
     ohne einen solchen fällt `cqw` auf die FENSTERBREITE zurück.
     Gemessen am 27.08.: Polsterung und Fuge des Schirms wuchsen dadurch mit dem
     Fenster mit, die Vorschaufläche stand bei 1280 px auf einem Verhältnis von
     2,304 und bei 1920 px auf 2,403 — die Bühne hat konstant 2,222. Eine Fläche,
     die mit dem Fenster ihre Form ändert, kann kein Prozentmaß tragen.
     ▶ Der Schirm ist `width: 100%`, also ist die Content-Breite dieses Kastens
       genau seine Breite. Damit rechnet er gegen sich selbst. */
  container-type: inline-size;
}
/* ▶ DER SCHIRM (A.20.9.0) — er entspricht `.stage-main`, also dem ganzen
   Beamerbild. Alles darin rechnet in `cq`-Einheiten gegen SEINE Breite, genau
   wie die Bühne in `vw` gegen die Bildschirmbreite rechnet.

   Ohne das war die Vorschau eine Lüge: sie stand mit fester Schrift in 1,35 rem
   auf 800 px Breite, der Beamer mit 4,5vw auf 1280 px — in der Vorschau passte
   alles, auf der Bühne fehlten drei Zeilen. Am eigenen Bild gefunden, von
   keinem Prüfpunkt.

   ⚠️ DIE ABSTÄNDE SIND GEMESSEN, nicht geschätzt: auf 1280×720 ist die Zeile
   mit Runde und Punkten 35 px hoch (4,9 %), der Stand darunter 81 px (11,3 %),
   der Rand des Bühnenblocks 32 px (2,5 %). Ohne diese Reserve behauptete die
   Vorschau „passt locker", wo der Beamer verkleinern muss. */
.quiz-vorschau-schirm {
  aspect-ratio: 16 / 9;
  width: 100%;
  border-radius: 0.35rem;
  background: var(--xxl-bg);
  container-type: inline-size;
  /* ⚠️ HIER STAND `display: flex; flex-direction: column`. Der Schirm ist seit
     A.20.47.0 dasselbe Raster wie die Bühne — die gemeinsame Regel weiter unten
     setzt `display: grid`. Ein zweites `display` an dieser Stelle wäre eine
     Angabe, die nur deshalb nicht wirkt, weil sie zufällig weiter oben steht. */
  /* ⚠️⚠️ DIESE DREI ZAHLEN SIND DER PLATZ FÜR KOPF- UND FUSSZEILE DER BÜHNE —
     und sie sind am 25.08. NACHGEMESSEN worden (A.20.11.0). Vorher standen hier
     4,9 % und 11,3 %; damit war die Fläche relativ HÖHER als die echte Bühne
     (42,2 % ihrer Breite statt 38,8 %), und die Vorschau zeigte weniger
     Gedränge, als der Beamer hat — die unsichere Richtung: es sah aus, als
     passe es, und im Saal wurde geschnitten.

     Gemessen auf 1280×720: die Bühnenfläche beginnt bei 81 px und endet bei
     553 px, ist also 472 px hoch bei 1216 px Breite. Daraus:
       oben   81/1280 = 6,3 %      unten (720−553)/1280 = 13,05 %
     Aufgefallen ist es erst, als der Textblock von Hand gesetzt werden konnte:
     solange BEIDE Seiten den Text automatisch einpassten, glichen sie den
     Unterschied selbst wieder aus und er blieb unsichtbar.

     ⚠️ Kalibriert ist damit auf 1280×720. Auf einem 1920er Beamer greift auf
     der Bühne die Obergrenze `4rem` der Schriftgröße, und die Vorschau kennt
     sie nicht — dort steht die Frage dann relativ kleiner, als sie hier
     aussieht. Wer das angleichen will, muss sich für EINE Bezugsbreite
     entscheiden; das ist eine Entscheidung, keine Rechnung. */
  /* ▶▶ NEU GEMESSEN AM 26.08. (A.20.27.0), nachdem die Bühne ein festes Feld
     ist — und jetzt zum ersten Mal eine Konstante statt einer Momentaufnahme:
     das Layout der Bühne ist auf JEDEM Schirm dasselbe.

     Gemessen (`/tmp/…/kanten.js`, drei Fenstergrößen, alle identisch):
       main  1280x720 · top 35 px bei y=32 · wrap 1216x558 bei y=81 · bot 35 px
       → oben 81/1280 = 6,33 %   unten (720−639)/1280 = 6,33 %   Fläche 2,179

     ⚠️⚠️ DIE ALTE ZAHL 13,05 % UNTEN WAR FALSCH — und zwar um mehr als das
     Doppelte. Sie stammte vom 25.08. aus einem Zustand, in dem unten mehr stand
     (die Fußzeile endete bei 553 statt bei 639). Die Vorschaufläche war dadurch
     RELATIV FLACHER als die echte: 2,575 statt 2,179. Und weil die achtzeilige
     Frage in der flacheren Fläche nicht passte, wurde sie dort automatisch
     verkleinert — im Saal passte sie und blieb groß. Genau das war sein Befund
     „erheblicher Unterschied": nicht die Schrift war falsch gerechnet, sondern
     die FLÄCHE, in der gerechnet wurde.

     ⚠️ Oben und unten sind jetzt GLEICH, und das ist kein Zufall: Kopf- und
     Fußzeile sind beide 35 px hoch und sitzen im selben Polster. Wer hier je
     wieder eine krumme Zahl stehen sieht, hat einen Zustand erwischt statt das
     Layout.

     ▶▶▶ UND SEIT A.20.47.0 STEHT HIER GAR KEINE ZAHL MEHR. Die drei oben sind
     Geschichte: der Schirm teilt sich die Aufteilung mit `.stage-main` (siehe
     die gemeinsame Regel weiter unten, „DIE VIER ZAHLEN DER BÜHNE"), und die
     Fußzeile ist kein Polster mehr, sondern eine leere Rasterzeile — der
     Platzhalter für die Spielerkacheln. Was oben und unten frei bleibt, rechnet
     damit dieselbe Regel aus wie im Saal.
     ⚠️ Die Kopfzeile, für die die 6,33 % oben standen, GIBT ES NICHT MEHR:
       Runde und Punkte sind mit A.20.47.0 nach unten in die Statuszone
       gewandert. Wer die alte Zahl stehen ließe, reservierte Platz für eine
       Zeile, die es nicht gibt. */
  overflow: hidden;
}
/* ▶ DIE FLÄCHE entspricht `.stage-question-wrap` — der Bereich, gegen den frei
   gesetzte Bilder ihre Prozentmaße rechnen. Deshalb steht hier dasselbe
   `position: relative` und dasselbe `overflow: hidden` wie dort, und deshalb
   trägt sie KEIN eigenes Polster: `left: 74%` würde sonst gegen eine andere
   Breite rechnen als auf der Bühne, und dasselbe Bild säße in Vorschau und
   Saal an verschiedenen Stellen. */
/* Dieselbe Regel wie auf der Bühne — sonst zeigte die Vorschau einen mittig
   beschnittenen Text und der Beamer einen oben bündigen. */
/* ⚠️ Wie beim Wrap auf der Bühne: die Regel für `.laeuft-ueber` ist hier
   gelöscht (Befund 14) — die Klasse sitzt an der Zone. */
/* Dieselbe Regel wie auf der Bühne (A.20.11.0): freier Text oben, alles Übrige
   sammelt sich unten. */
/* ⚠️ Und ebenso `justify-content: flex-end` für `.text-frei`: im Raster
   wirkungslos (Befund 14). Die Klasse selbst bleibt. */
.quiz-vorschau-flaeche {
  position: relative;
  overflow: hidden;
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  /* Der Abstand der Bühne: `clamp(0.75rem, 2vh, 1.5rem)` greift dort mit dem
     mittleren Term, 2vh = 14,4 px = 0,9rem. */
  gap: calc(0.9 * var(--vs));
  /* Damit ein gezogenes Bild nicht die ganze Seite mitscrollt. */
  touch-action: none;
}
/* ⚠️⚠️ DIE STAPELORDNUNG DER BÜHNE, WORT FÜR WÖRTLICH (A.20.11.1). Dort steht
   `.stage-question-wrap > *:not(.stage-media) { position: relative; z-index: 1 }` —
   alles Geschriebene liegt über den Bildern, damit ein groß gezogenes Bild zum
   Hintergrund wird und nie die Frage verdeckt. In der Vorschau trug das bis
   A.20.11.1 nur der TEXT; sobald die Antwortmöglichkeiten dazukamen,
   verschwanden „B" und „D" hinter dem freien Bild, während sie im Saal
   darüber standen. Am eigenen Bild gesehen, kein Prüfpunkt meldet das.

   ⚠️ Diese Regel MUSS VOR `.quiz-vorschau-text.ist-frei` stehen: beide haben
   dieselbe Spezifität (0,2,0), und bei Gleichstand gewinnt die SPÄTERE. Weiter
   unten stehend nagelte sie den frei gesetzten Textblock wieder in den Fluss. */
/* ⚠️⚠️⚠️ SEIT A.20.47.0 DARF DIESE REGEL DIE ZONEN NICHT TREFFEN — wortgleich
   dieselbe Falle wie auf der Bühne (siehe dort bei `.stage-question-wrap`).
   Die direkten Kinder der Fläche sind jetzt die vier Zonen; bekämen SIE
   `position: relative`, wäre jede von ihnen der neue Bezugsrahmen für die
   absolut gesetzten Blöcke darin — und die 81 Platzierungen aus der Datenbank
   sprängen im Editor woanders hin als im Saal.
   ▶ Getroffen wird deshalb nur, was WIRKLICH gestapelt werden soll: die
     Inhalte IN den Zonen, dazu die Griffe, die direkte Kinder der Fläche
     geblieben sind.

   ⚠️⚠️⚠️ UND `:not(.ist-frei)` IST PFLICHT — am 27.08. genau daran gescheitert.
   Die alte Fassung hiess `.quiz-vorschau-flaeche > *:not(…)` und wog 0,2,0; die
   Regeln für frei gesetzte Blöcke tragen ihren Vorfahren und wiegen 0,3,0, also
   gewannen sie. Mit der Zone im Selektor wiegt DIESE Regel ebenfalls 0,3,0 —
   bei Gleichstand gewinnt die spätere, und das ist sie. Ergebnis: die frei
   gesetzten Antwortmöglichkeiten standen wieder `relative` im Fluss, während
   sie im Saal absolut lagen. `test:fragetext` hat es gemeldet
   („stage-options ist-frei / relative"). */
.quiz-vorschau-flaeche > span,
.quiz-vorschau-flaeche .stage-zone-frage > *:not(.ist-frei),
.quiz-vorschau-flaeche .stage-zone-antworten > *:not(.quiz-vorschau-medien):not(.ist-frei),
.quiz-vorschau-flaeche .stage-zone-status > *:not(.ist-frei) { position: relative; z-index: 1; }
/* ▶ DAS RASTER (A.20.12.0). Sein Befund vom 25.08.: „Ein snap to grid wäre
   noch super."

   ⚠️⚠️ ES STEHT NUR DA, SOLANGE GEZOGEN WIRD (`.zieht` kommt aus
   `admin-buehnenvorschau.js`, solange `buehneZug` gesetzt ist). Der Grund ist
   nicht Zurückhaltung, sondern die Aufgabe dieser Fläche: sie zeigt, wie die
   Frage im SAAL aussieht, und dort gibt es kein Raster. Ein dauerhaft
   liegendes Gitter machte aus der Vorschau eine technische Zeichnung — und
   was man gestaltet, wäre nicht mehr das, was man sieht.

   ⚠️⚠️ DIE WEITEN KOMMEN AUS `buehnenbild.js` HEREIN (`--raster-x` und
   `--raster-y`, gesetzt im Formular). Hier steht KEINE Zahl. Eine zweite Zahl
   im CSS hieße: das Raster ließe sich verstellen, ohne dass die Linien
   mitgingen — die Vorschau zeigte dann Linien, auf denen nichts einrastet. Die
   Ersatzwerte unten greifen nur, wenn die Bindung ausfällt.

   ⚠️⚠️ ZWEI VERSCHIEDENE WEITEN, DAMIT DIE ZELLEN QUADRATE SIND (A.20.14.0,
   sein Befund: „es sollte sich beim Raster um Quadrate handeln"). Die
   Prozentangabe in einem `repeating-linear-gradient` rechnet gegen die Länge
   der Verlaufslinie — bei `to right` gegen die BREITE, bei `to bottom` gegen
   die HÖHE. Genau wie x/y selbst. Und weil die Fläche 2,58-mal so breit wie
   hoch ist, muss die senkrechte Weite rund 2,5-mal so groß sein wie die
   waagerechte; mit gleichen Werten kamen bis A.20.13.0 Streifen von 33 × 13 px
   heraus.

   ⚠️ Die Linien liegen HINTER den Bildern (z-index 0, wie `.quiz-vorschau-
   medien`; das ::before steht davor im Kasten und wird deshalb zuerst
   gezeichnet). Vor den Bildern wäre das Gitter über einem groß gezogenen Bild
   ein Netz über der halben Bühne. */
.quiz-vorschau-flaeche.zieht::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    repeating-linear-gradient(to right,
      var(--xxl-accent) 0 1px, transparent 1px var(--raster-x, 4%)),
    repeating-linear-gradient(to bottom,
      var(--xxl-accent) 0 1px, transparent 1px var(--raster-y, 10%));
  opacity: 0.3;
}
/* ▶ DER TEXTBLOCK ALS GANZES (A.20.11.0). Sein Befund vom 25.08.: „Den
   Textblock muss ich auch verschieben und in der Größe bearbeiten können."

   ⚠️ WARUM EINE HÜLLE UM DEN TEXT und nicht die Griffe in den Text selbst:
   der Text kommt über `x-html` herein. Alles, was IN diesem Element steht,
   wird bei jedem Tastendruck weggeschrieben — die Griffe wären nach dem ersten
   Buchstaben weg. Die Hülle trägt deshalb Platz und Griffe, der Text darin
   bleibt allein Text. */
.quiz-vorschau-text {
  position: relative;
  z-index: 1;            /* wie auf der Bühne: Text liegt IMMER über den Bildern */
  /* ⚠️⚠️ DIE HÜLLE FÄNGT KEINE KLICKS AB — sonst käme Befund 4 aus A.20.9.0
     zurück: der Text liegt über den Bildern, und ein Bild unter ihm wäre nicht
     mehr zu greifen. Nur die beiden GRIFFE nehmen den Zeiger an (unten). */
  pointer-events: none;
  max-width: 100%;
  min-width: 0;
}
/* Frei gesetzt: dieselbe Formel wie auf der Bühne, nur in dieser Fläche.
   ⚠️⚠️ `width: max-content` wie dort — sonst begrenzt der Platz bis zum Rand
   die Breite, und die Vorschau bricht die Zeile an einer anderen Stelle um als
   der Beamer. Genau so gesehen: derselbe Satz stand hier auf DREI Zeilen und
   im Saal auf ZWEI. */
.quiz-vorschau-text.ist-frei {
  position: absolute;
  transform: translate(-50%, -50%);
  width: max-content;
  max-width: none;
  /* Wie auf der Bühne — sonst bricht die Vorschau anders um als der Beamer. */
  --fragetext-mindest: 0px;
}
.quiz-fragevorschau-text {
  position: relative;
  z-index: 1;            /* wie auf der Bühne: Text liegt IMMER über den Bildern */
  /* ⚠️⚠️ DER TEXT FÄNGT KEINE KLICKS AB. Er liegt über den Bildern — das ist
     auf der Bühne richtig und in der Vorschau genauso —, aber hier bedient man
     ausschließlich Bilder: geschrieben wird im Feld darüber. Ohne diese Zeile
     ist ein Bild, über dem Text steht, schlicht nicht zu greifen. Der
     Prüfstand hat es gefunden, und zwar nur, weil sein Prüffall eine LANGE
     Frage benutzt: bei einer kurzen überlappt nichts, und der Fehler wäre erst
     ihm selbst aufgefallen — an genau der Frage, die er gestalten wollte. */
  pointer-events: none;
  /* ⚠️ 4.5cqw ist EXAKT das 4.5vw der Bühne, nur gegen diese Fläche gerechnet.
     DREI Größen wie dort (seit A.20.11.0): der Grund, die Skala aus dem
     Einpassen und `--fragetext-frei` — die Größe, die er am Block selbst
     gezogen hat. Sie kommt von der Hülle herunter und ist ohne freien Block 1. */
  /* ⚠️ ÜBER `--bw`, NICHT ÜBER `cqw` (A.20.27.0): `cqw` rechnet gegen die
     Content-Box des Schirms und war damit um 5,26 % zu klein. `--bw` ist ein
     Prozent der BÜHNENBREITE — dieselbe Größe, gegen die auch `.stage-question`
     ihre 4,5 rechnet. */
  --frage-grund: calc(4.5 * var(--bw));
  font-size: calc(var(--frage-grund) * var(--fragetext-skala, 1) * var(--fragetext-frei, 1));
  font-weight: 700;
  line-height: 1.22;     /* wie .stage-question */
  color: var(--xxl-text);
  /* Dasselbe Zeilenmaß wie auf der Bühne — in `ch`, nicht in `vw`: sonst zeigt
     die Vorschau einen Umbruch, den der Beamer nicht macht. */
  max-width: 30ch;
  margin: 0 auto;
  text-wrap: balance;
}

/* Die Medien in der Vorschau — derselbe Aufbau wie `.stage-media`: EIN
   Behälter für beides, die Reihe im Fluss und die freien absolut darüber. */
.quiz-vorschau-medien {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0.4rem;
  width: 100%;
  min-height: 0;
  flex: 1 1 auto;
  z-index: 0;
}
.quiz-vorschau-medien.nur-frei { flex: 0 0 auto; height: 0; min-height: 0; }

/* ▶ DER BEHÄLTER FÜR DAS LÖSUNGSBILD (A.20.29.0). Sein Befund vom 26.08.: „das
   Lösungsbild wird gar nicht angezeigt als Anzeigemöglichkeit und um es zu
   verschieben."

   ⚠️ DER GRÜNE RAHMEN GEHÖRT DAZU. Auf der Bühne trägt jedes Auflösungs-Medium
   `border: 3px solid var(--xxl-success)` (`.stage-media-answer
   .stage-media-image`) — er ist nicht Zierat, er kostet Platz. Ohne ihn zeigte
   die Vorschau ein Bild, das im Saal drei Pixel größer ist, und ein knapp
   gesetztes Bild ragte dort über eine Kante, die hier noch frei war.

   ⚠️ 0,1875 · `--vs` sind die 3 px der Bühne auf 1280 px umgerechnet — dieselbe
   Rechnung wie bei den 2 px der Antwortkästen (`.stage-option`, 0,125). `max()`
   mit 1 px, damit der Rahmen in einer kleinen Vorschau nicht ganz verschwindet:
   ein Rahmen, den man nicht sieht, ist keine ehrliche Vorschau, sondern gar
   keine. */
.quiz-vorschau-medien.ist-aufloesung img,
.quiz-vorschau-medien.ist-aufloesung video {
  border: max(1px, calc(0.1875 * var(--vs))) solid var(--xxl-success);
}

.quiz-vorschau-medium {
  position: relative;
  max-width: 100%;
  min-width: 0;
  cursor: grab;
  /* Ein Bild, das man anfassen kann, muss aussehen, als könnte man es
     anfassen. Die Kante erscheint erst beim Zeigen — sonst stünde in der
     Vorschau eine Umrandung, die der Beamer nicht hat. */
  border: 2px solid transparent;
  border-radius: 0.25rem;
  transition: border-color 0.12s ease;
}
.quiz-vorschau-medium:hover { border-color: var(--xxl-accent); }
.quiz-vorschau-medium:active { cursor: grabbing; }
.quiz-vorschau-medium img,
.quiz-vorschau-medium video {
  display: block;
  max-width: 100%;
  /* Das 46vh der Bühne, umgerechnet: bei 16:9 sind 46vh genau 25.9vw — und
     `cqw` gegen diese Fläche ist dasselbe wie `vw` gegen den Beamer. */
  max-height: 25.9cqw;
  object-fit: contain;
  border-radius: 0.2rem;
  pointer-events: none;      /* das Ziehen gehört dem Rahmen, nicht dem Bild */
  -webkit-user-drag: none;
  user-select: none;
}
/* Frei gesetzt: dieselbe Formel wie auf der Bühne, nur in dieser Fläche.
   ⚠️ Die Maße kommen aus `buehnenbild.js` ins style-Attribut — hier steht nur,
   was NICHT vom einzelnen Bild abhängt. */
.quiz-vorschau-medium.ist-frei {
  position: absolute;
  transform: translate(-50%, -50%);
  max-width: none;
}
.quiz-vorschau-medium.ist-frei img,
.quiz-vorschau-medium.ist-frei video {
  max-height: none;
  width: 100%;
  height: auto;
}

/* ===========================================================================
   DIE ANTWORTMÖGLICHKEITEN IN DER VORSCHAU (A.20.11.1)
   Sein Befund vom 25.08.: „Antwortmöglichkeiten in die Vorschau rein."

   ⚠️⚠️ HIER STEHT KEINE ZWEITE GESTALTUNG. Die Kästen tragen dieselben Klassen
   wie auf der Bühne und werden von denselben Funktionen gefüllt
   (`buehneWahl.zeige` / `buehneTipp.zeige`). Was hier steht, sind ALLEIN die
   MASSE — denn die Bühne rechnet in `rem`, `vw` und `vh` gegen das FENSTER,
   und dieses Fenster gibt es hier nicht. Ohne diese Umrechnung stünden in einer
   665 px breiten Vorschau die Kästen eines 1280er Beamers: viel zu groß, und
   die Vorschau löge genau da, wo sie gebraucht wird.

   ▶ `--vs` IST DIE UMRECHNUNG: EIN rem der Bühne, gegen diese Fläche gerechnet.
   Bezug ist 1280×720 — dieselbe Bezugsbreite, auf die auch die Polster des
   Schirms kalibriert sind (siehe `.quiz-vorschau-schirm`). Dort ist
   1rem = 16px = 1.25vw, und 1vh = 0.5625vw (16:9).
   Jeder Wert unten ist der auf 1280×720 WIRKSAME Wert der Bühnenregel, geteilt
   durch 16. ⚠️ Nicht einfach der mittlere Term der `clamp()` — bei der
   waagerechten Polsterung der Option gewinnt dort die OBERGRENZE (1.4vw wären
   17,9 px, gedeckelt auf 1.1rem = 17,6 px). Wer hier einen Wert ändert, muss
   auf der Bühne nachsehen, welcher Term wirklich greift.
   =========================================================================== */
/* ⚠️⚠️ `--vs` HÄNGT JETZT AN `--bw` (A.20.27.0) und nicht mehr an `cqw`.
   Der Grund steht eine Regel weiter unten: `cqw` ist ein Prozent der
   CONTENT-Box des Schirms, die Bühne rechnet aber gegen ihre volle Breite.
   Die alte Schreibweise war deshalb um dieselben 5,26 % zu klein wie alles
   andere — nur fiel es hier nicht auf, weil `--vs` nirgends gegen einen
   Bühnenwert gehalten wurde. */
.quiz-vorschau-flaeche { --vs: calc(1.25 * var(--bw)); }
/* ▶▶ UND DIE BEIDEN BÜHNENMASSE (A.20.27.0) — hier liegt die ganze Eichung.
   Auf der Bühne sind `--bw`/`--bh` feste 12,8 px und 7,2 px (ein Prozent von
   1280x720). Hier sind sie ein Prozent DIESER Fläche. Damit rechnet jede Regel,
   die beide Seiten teilen, in beiden dasselbe Verhältnis aus — und kann gar
   nicht mehr auseinanderlaufen.

   ⚠️ `--bh` ist 0,5625cqw und NICHT 1cqh: die Höhe eines 16:9-Feldes ist
   9/16 seiner Breite. Mit `cqh` bräuchte der Schirm `container-type: size`
   statt `inline-size`, und dann müsste er seine Höhe selbst kennen — er bekommt
   sie aber aus `aspect-ratio`. Eine Zahl statt einer zweiten Abhängigkeit.

   ⚠️ Steht am SCHIRM und nicht an der Fläche: die Prozentmaße der Bühne rechnen
   gegen `.stage-main`, und das ist der Schirm. Die Fläche ist das, was zwischen
   Kopf- und Fußzeile davon übrig bleibt. */
/* ⚠️⚠️⚠️ 1.0526 UND NICHT 1 — DAS IST DER LETZTE REST DES BEFUNDS, und er hat
   fünf Messungen gebraucht. `container-type: inline-size` macht die
   CONTENT-Box zur Bezugsgröße: `1cqw` ist ein Prozent von 665 px, nicht von den
   700 px, die der Schirm breit ist. Die Bühne dagegen rechnet `--bw` gegen ihre
   VOLLE Breite von 1280 px — ihr Polster steckt mit drin.

   Gemessen: Schirm 700,2 px, Fläche 665,2 px. 665,2/700,2 = 0,95, und
   1/0,95 = 1,0526. Genau diese 5,26 % standen im Befund-Lauf als „5,3 %
   daneben" — auf beiden Fenstergrößen dieselbe Zahl, was schon sagte, dass es
   kein Zufall sein kann, sondern ein fester Bezugsfehler.

   ⚠️ DIE 0,95 SIND DAS SEITLICHE POLSTER DES SCHIRMS (2 × 2,5 %). Wer es
   ändert, muss diese Zahl mitändern — sonst laufen Vorschau und Bühne wieder
   auseinander, und zwar um genau den Betrag der Änderung.

   ▶▶ UND GENAU DAS IST AM 27.08. PASSIERT. Das Polster des Schirms ist mit den
   festen Zonen von 2,5 % auf **1,6 %** je Seite gefallen — es ist jetzt
   dasselbe wie das der Bühne (`calc(1.6 * var(--bw))` bei 1280 px = 20,48 px).
   Damit ist die Fläche 1 − 0,032 = **0,968** des Schirms, und 1/0,968 = 1,0331.
   ⚠️ Diese Zeile ist die einzige Stelle, an der das Polster noch einmal
   auftaucht. Wer es wieder ändert, ändert hier mit — sonst steht dieselbe
   Frage in Vorschau und Saal in verschieden großer Schrift, und zwar um genau
   den Betrag der Änderung. */
.quiz-vorschau-schirm { --bw: 1.0331cqw; --bh: 0.581119cqw; }

.quiz-vorschau-flaeche .stage-options {
  gap: calc(0.54 * var(--vs));          /* clamp(0.4rem, 1.2vh, 0.9rem) → 8,64 px */
  font-size: calc(1.76 * var(--vs));    /* clamp(1rem, 2.2vw, 1.9rem) → 28,16 px */
  width: min(96.8cqw, 46ch);            /* min(92vw, 46ch), 92vw sind 96,8 % der Fläche */
}
.quiz-vorschau-flaeche .stage-option {
  gap: calc(0.96 * var(--vs));          /* clamp(0.5rem, 1.2vw, 1rem) → 15,36 px */
  padding: calc(0.45 * var(--vs)) calc(1.1 * var(--vs));
  border-width: max(1px, calc(0.125 * var(--vs)));   /* 2 px der Bühne */
}
.quiz-vorschau-flaeche .stage-tipp { gap: calc(0.5 * var(--vs)); }
/* ⚠️ Hier gewinnt auf der Bühne die OBERGRENZE der clamp: 2.6vw wären 33,3 px,
   gedeckelt auf 2rem = 32 px. */
.quiz-vorschau-flaeche .stage-buzz-state { font-size: calc(2 * var(--vs)); }
.quiz-vorschau-flaeche .stage-zahl-hinweis {
  font-size: calc(1.08 * var(--vs));    /* clamp(1rem, 2.4vh, 1.6rem) → 17,28 px */
}

/* ▶ WAS IM SPIEL SONST NOCH DAZUKOMMT (A.20.24.0): HINWEISE, LÖSUNG und der
   Kasten „wer ist dran". Dieselbe Umrechnung wie alles darüber — der auf
   1280×720 WIRKSAME Wert der Bühnenregel, geteilt durch 16.

   ⚠️ Ohne diese Maße rechneten die drei in `vw` und `rem` gegen das FENSTER
   statt gegen die 665 px breite Vorschau: der Kasten „wer ist dran" käme mit
   57,6 px Schrift in eine Fläche, die im Saal 1216 px breit ist. Er ist das
   Größte, was dort auftauchen kann — er ist auch der, bei dem es am meisten
   auffiele. */
.quiz-vorschau-flaeche .stage-hinweise {
  gap: calc(0.405 * var(--vs));         /* clamp(0.25rem, 0.9vh, 0.6rem) → 6,48 px */
  max-width: 75.8cqw;                   /* min(58rem, 72vw) → 921,6 px = 75,8 % der Fläche */
}
.quiz-vorschau-flaeche .stage-hinweis {
  gap: calc(0.6 * var(--vs));           /* 0,6rem → 9,6 px */
  /* ⚠️ 1,76 — dieselbe Zahl wie `.stage-options`, und das ist kein Zufall: auf
     der Bühne steht dort dieselbe `clamp(…, 2.2vw, …)`, weil die Hinweise bei
     dieser Frageform DER Inhalt sind. */
  font-size: calc(1.76 * var(--vs) * var(--hinweis-skala, 1));
}
.quiz-vorschau-flaeche .stage-hinweis.ist-offen {
  font-size: calc(0.9 * var(--vs));     /* clamp(0.85rem, 2vh, 1.4rem) → 14,4 px */
}
.quiz-vorschau-flaeche .stage-hinweis-wert {
  margin-top: calc(0.4 * var(--vs));    /* 0,4rem → 6,4 px */
  font-size: calc(1.08 * var(--vs));    /* clamp(1rem, 2.4vh, 1.7rem) → 17,28 px */
}
.quiz-vorschau-flaeche .stage-answer {
  font-size: calc(2.56 * var(--vs));    /* clamp(1.3rem, 3.2vw, 2.6rem) → 40,96 px */
  max-width: 94.7cqw;                   /* min(90vw, 32ch) → 1152 px = 94,7 % der Fläche */
  border-width: max(1px, calc(0.1875 * var(--vs)));   /* 3 px der Bühne */
}
.quiz-vorschau-flaeche .stage-dran {
  font-size: calc(3.6 * var(--vs));     /* clamp(1.6rem, 4.5vw, 4rem) → 57,6 px */
}
.quiz-vorschau-flaeche .stage-reihe-zeile {
  gap: calc(0.9 * var(--vs));
  width: min(calc(48 * var(--vs)), 90%);
  padding: calc(0.35 * var(--vs)) calc(0.9 * var(--vs));
  border-width: max(1px, calc(0.125 * var(--vs)));
  border-radius: calc(0.6 * var(--vs));
}
.quiz-vorschau-flaeche .stage-reihe-nummer {
  min-width: calc(2 * var(--vs));
  font-size: calc(1.17 * var(--vs));    /* clamp(1.1rem, 2.6vh, 2rem) → 18,72 px */
}
.quiz-vorschau-flaeche .stage-reihe-text {
  font-size: calc(1.35 * var(--vs));    /* clamp(1.1rem, 3vh, 2.4rem) → 21,6 px */
}
.quiz-vorschau-flaeche .stage-paar-spalten { gap: calc(3.2 * var(--vs)); }
.quiz-vorschau-flaeche .stage-paar-spalte  { gap: calc(0.4 * var(--vs)); }
.quiz-vorschau-flaeche .stage-paar-karte {
  padding: calc(0.3 * var(--vs)) calc(0.9 * var(--vs));
  border-width: max(1px, calc(0.125 * var(--vs)));
  border-radius: calc(0.6 * var(--vs));
  font-size: calc(1.17 * var(--vs));
}

/* Der Griff für die Größe. Unten rechts, wo ihn jeder sucht. */
.quiz-vorschau-griff {
  position: absolute;
  right: -0.45rem;
  bottom: -0.45rem;
  width: 0.9rem;
  height: 0.9rem;
  border-radius: 0.2rem;
  background: var(--xxl-accent);
  border: 2px solid var(--xxl-bg);
  cursor: nwse-resize;
  z-index: 2;
}

/* ▶ DIE ZWEI GRIFFE AM TEXTBLOCK (A.20.11.0).
   Warum der Text ÜBERHAUPT Griffe braucht und nicht — wie ein Bild — einfach
   angefasst wird: er liegt über den Bildern und deckt einen großen Teil der
   Fläche ab. Wäre die ganze Textfläche zu greifen, wäre jedes Bild unter ihr
   nicht mehr zu greifen — genau Befund 4 aus A.20.9.0, nur andersherum. Zwei
   kleine Griffe nehmen den Zeiger, der Rest lässt ihn durch.

   ⚠️⚠️ SIE HEISSEN NICHT `quiz-vorschau-griff`. Der Prüfstand `fragetext`
   sucht die Griffe der BILDER über genau diese Klasse und nimmt den ersten
   Treffer; ein dritter Griff mit demselben Namen hätte ihn auf den Text
   zeigen lassen, ohne dass irgendetwas rot wird.

   ⚠️⚠️ SIE SITZEN IN DER MITTE DES BLOCKS, nicht an seinen Ecken — und das ist
   der zweite Anlauf. Beim ersten standen sie oben links und unten rechts, wie
   beim Bild. Gemessen (`scripts/befund-textblock.js`): sobald der Block auch
   nur 28 px über den oberen Rand ragte, war der ZIEH-Griff abgeschnitten und
   der Block nicht mehr zu bewegen. Und Überstehen ist hier der Normalfall,
   nicht die Ausnahme: ein frei gesetzter Block wird nicht mehr eingepasst.
   Die Mitte ist der EINE Punkt, der immer da ist, wo er hingehört — x/y sind
   ja genau dieser Punkt.

   ⚠️ Damit stehen sie auch innerhalb des Blocks, und `mobil` meldet keinen
   Überstand über den rechten Rand.

   ⚠️ DAS ZEICHEN KOMMT AUS `::before`. Ein Textknoten im Griff wäre für die
   Lesbarkeitsprobe von `mobil` ein 11-px-Text und damit ein Befund — erzeugter
   Inhalt steht nicht in `childNodes` und wird dort nicht gezählt. */
.quiz-vorschau-textzug,
.quiz-vorschau-textecke {
  position: absolute;
  width: 1.15rem;
  height: 1.15rem;
  border-radius: 0.25rem;
  background-color: var(--xxl-accent);
  border: 2px solid var(--xxl-bg);
  z-index: 3;
  pointer-events: auto;      /* die Hülle lässt durch, die Griffe nicht */
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--xxl-bg);
  line-height: 1;
  /* ⚠️ Sie liegen mitten IM Text — anders geht es nicht, wenn sie immer
     erreichbar sein sollen (siehe oben). Deshalb stehen sie im Ruhezustand
     blass da und werden erst deutlich, sobald der Zeiger in die Vorschau
     kommt. Ganz ausblenden wäre falsch: am Handy gibt es kein Zeigen, und ein
     Bedienweg, den man nicht sieht, existiert nicht. */
  /* ⚠️ 0,55 und nicht weniger: auf dem Handybild bei 0,32 waren die Griffe in
     der kleinen Vorschau schlicht NICHT ZU FINDEN — und dort gibt es kein
     Zeigen, das sie hervorholt. Am eigenen Bild entschieden. */
  opacity: 0.55;
  transition: opacity 0.12s ease;
}
.quiz-vorschau-flaeche:hover .quiz-vorschau-textzug,
.quiz-vorschau-flaeche:hover .quiz-vorschau-textecke { opacity: 0.9; }
.quiz-vorschau-textzug:hover,
.quiz-vorschau-textecke:hover { opacity: 1; }
.quiz-vorschau-textzug {
  left: 50%; top: 50%;
  transform: translate(-108%, -50%);
  cursor: grab;
}
.quiz-vorschau-textzug:active { cursor: grabbing; }
.quiz-vorschau-textzug::before { content: '\2725'; font-size: 0.7rem; }
/* Der Größen-Griff steht NEBEN dem Zieh-Griff, nicht an einer Ecke. Gezogen
   wird ohnehin am ABSTAND zum Mittelpunkt (`admin-buehnenvorschau.js`), und
   dessen Rechnung braucht keine Ecke — sie braucht nur einen Punkt, den man
   auch trifft. */
.quiz-vorschau-textecke {
  left: 50%; top: 50%;
  transform: translate(8%, -50%);
  cursor: nwse-resize;
}
.quiz-vorschau-textecke::before { content: '\21F2'; font-size: 0.7rem; }

/* ▶ DIE GRIFFE DER ANTWORTMÖGLICHKEITEN (A.20.17.0). Sein Befund vom 25.08.:
   „außerdem kann ich bei Multiple Choice die Antwortmöglichkeiten nicht bewegen
   oder in ihrer Größe verändern."

   ⚠️⚠️ ZWEI GRIFFE, NICHT DER GANZE BLOCK — wie beim Textblock und anders als
   bei der Zustandszeile. Der Optionsblock deckt die halbe untere Bühnenfläche
   ab; wäre er in ganzer Fläche zu greifen, wäre jedes Bild darunter nicht mehr
   anzufassen. Das ist Befund 4 aus A.20.9.0, und in A.20.14.0 kam es als
   Mess-Falle zurück: der Größen-Griff eines tief stehenden Bildes lag unter den
   Möglichkeiten, der Lauf traf den Kasten „D" und meldete den Bau als kaputt.

   ⚠️⚠️ SIE HEISSEN NICHT `quiz-vorschau-griff` UND NICHT `…-textzug`. Der
   Prüfstand `fragetext` sucht die Griffe der BILDER über die eine Klasse und
   die des TEXTBLOCKS über die andere und nimmt jeweils den ersten Treffer; ein
   dritter Griff mit einem der beiden Namen hätte ihn stillschweigend auf das
   falsche Ding zeigen lassen, ohne dass irgendetwas rot wird.

   ⚠️⚠️ IHR PLATZ KOMMT AUS DEM JAVASCRIPT (`_antwortenZeichnen`), nicht von
   hier: `left`/`top` werden an die MITTE des Blocks gesetzt, wie er GERADE
   steht. Der Grund ist der Behälter selbst — `buehneWahl.zeige()` leert ihn bei
   jedem Zustandsbild (`kasten.textContent = ''`), ein Griff DARIN wäre also nach
   dem ersten Tastendruck weg. Und eine Hülle darum hätte die Vorschau anders
   gebaut als die Bühne, wo es keine gibt.

   ⚠️ Die Mitte ist auch hier der EINE Punkt, der immer erreichbar ist: bei vier
   Möglichkeiten ist es der Kreuzungspunkt der Lücken, bei zweien die Lücke
   dazwischen. An einer Ecke wären sie abgeschnitten, sobald der Block übersteht
   — und Überstehen ist bei einem frei gesetzten Block der Normalfall.

   ⚠️ Zeichen aus `::before`, wie beim Textblock: ein Textknoten wäre für die
   Lesbarkeitsprobe von `mobil` ein 11-px-Text und damit ein Befund. */
/* ⚠️⚠️ `span.` IM SELEKTOR IST PFLICHT, UND ZWAR AM BEFUNDLAUF GEFUNDEN. Diese
   beiden sind direkte Kinder von `.quiz-vorschau-flaeche` — anders als die
   Griffe des Textblocks, die in ihrer Hülle sitzen. Dort greift aber
   `.quiz-vorschau-flaeche > *:not(.quiz-vorschau-medien) { position: relative }`
   mit 0,2,0, und ein reines `.quiz-vorschau-antwortzug` hat nur 0,1,0: die
   Griffe blieben im FLUSS stehen, irgendwo neben dem Block, und der erste Lauf
   meldete „unter dem Griff liegt der Vorschau-Schirm". Mit dem Typ im Selektor
   (0,2,1) gewinnt diese Regel eindeutig — und nicht bloß, weil sie zufällig
   weiter unten in der Datei steht. */
.quiz-vorschau-flaeche > span.quiz-vorschau-antwortzug,
.quiz-vorschau-flaeche > span.quiz-vorschau-antwortecke,
.quiz-vorschau-flaeche > span.quiz-vorschau-antwortbreite {
  position: absolute;
  width: 1.15rem;
  height: 1.15rem;
  border-radius: 0.25rem;
  background-color: var(--xxl-accent);
  border: 2px solid var(--xxl-bg);
  z-index: 3;
  pointer-events: auto;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--xxl-bg);
  line-height: 1;
  touch-action: none;
  opacity: 0.55;
  transition: opacity 0.12s ease;
}
.quiz-vorschau-flaeche:hover .quiz-vorschau-antwortzug,
.quiz-vorschau-flaeche:hover .quiz-vorschau-antwortecke,
.quiz-vorschau-flaeche:hover .quiz-vorschau-antwortbreite { opacity: 0.9; }
.quiz-vorschau-antwortzug:hover,
.quiz-vorschau-antwortecke:hover,
.quiz-vorschau-antwortbreite:hover { opacity: 1; }
.quiz-vorschau-flaeche > span.quiz-vorschau-antwortzug {
  transform: translate(-108%, -50%);
  cursor: grab;
}
.quiz-vorschau-antwortzug:active { cursor: grabbing; }
.quiz-vorschau-antwortzug::before { content: '\2725'; font-size: 0.7rem; }
/* Der Größen-Griff steht NEBEN dem Zieh-Griff, nicht an einer Ecke — gezogen
   wird ohnehin am ABSTAND zum Mittelpunkt, und dessen Rechnung braucht keine
   Ecke, sondern nur einen Punkt, den man trifft. */
.quiz-vorschau-flaeche > span.quiz-vorschau-antwortecke {
  transform: translate(8%, -50%);
  cursor: nwse-resize;
}
.quiz-vorschau-antwortecke::before { content: '\21F2'; font-size: 0.7rem; }

/* ▶ DER GRÖSSEN-GRIFF DER ZUSTANDSZEILE (A.20.23.0). Seine Frage zu A.20.15.0:
   „Soll sich die Zeile auch in der GRÖSSE ziehen lassen?"

   ⚠️⚠️ DER TYP MUSS IM SELEKTOR STEHEN, aus demselben Grund wie bei den
   Griffen darüber: `.quiz-vorschau-flaeche > *:not(…) { position: relative }`
   hat 0,2,0, ein reines `.quiz-vorschau-standecke` nur 0,1,0 — der Griff bliebe
   im FLUSS stehen, irgendwo neben der Pille. Mit dem Typ (0,2,1) gewinnt diese
   Regel eindeutig und nicht bloß, weil sie weiter unten steht.

   ⚠️ Er sitzt MITTIG auf der Pille und nicht an ihrem Ende: die Pille ist der
   Zieh-Griff in ganzer Fläche, und ein Griff an der Kante wäre abgeschnitten,
   sobald die Zeile über den Rand gezogen wird — dieselbe Begründung wie bei den
   drei Griffen der Möglichkeiten. */
.quiz-vorschau-flaeche > span.quiz-vorschau-standecke {
  position: absolute;
  width: 1.15rem;
  height: 1.15rem;
  border-radius: 0.25rem;
  background-color: var(--xxl-accent);
  border: 2px solid var(--xxl-bg);
  z-index: 3;
  pointer-events: auto;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--xxl-bg);
  line-height: 1;
  touch-action: none;
  opacity: 0.55;
  transition: opacity 0.12s ease;
  transform: translate(-50%, -50%);
  cursor: nwse-resize;
}
.quiz-vorschau-flaeche:hover .quiz-vorschau-standecke { opacity: 0.9; }
.quiz-vorschau-standecke:hover { opacity: 1; }
.quiz-vorschau-standecke::before { content: '\21F2'; font-size: 0.7rem; }
/* ▶ DIE BREITE (A.20.18.0). Sein Befund vom 25.08.: „Die Breite der
   Antwortmoeglichkeiten moechte ich auch ziehen koennen."

   ⚠️ Der DRITTE Griff steht in derselben Reihe, rechts neben der Ecke — nicht
   an der rechten Kante des Blocks. Der Grund ist derselbe wie bei den beiden
   anderen: ein frei gesetzter Block ragt im Normalfall ueber den Rand, und was
   an seiner Kante sitzt, ist dann abgeschnitten und nicht mehr zu greifen.

   ⚠️ Der Versatz 124 % ist kein Zufall: der Zieh-Griff steht bei −108 %, die
   Ecke bei 8 % — also 116 % Abstand. Derselbe Abstand noch einmal, und die drei
   stehen gleichmaessig nebeneinander.

   ⚠️ Das Zeichen ist der WAAGERECHTE Doppelpfeil und der Zeiger `ew-resize`:
   beides sagt „nach links und rechts", waehrend die Ecke mit `nwse-resize`
   „groesser und kleiner" sagt. Zwei Griffe nebeneinander, die dasselbe Zeichen
   traegen, waeren im Halbdunkel ein Fehlgriff. */
.quiz-vorschau-flaeche > span.quiz-vorschau-antwortbreite {
  transform: translate(124%, -50%);
  cursor: ew-resize;
}
.quiz-vorschau-antwortbreite::before { content: '\21D4'; font-size: 0.7rem; }

/* ▶ DIE GRIFFE DER DREI ZUGABEN (A.20.28.0). Sein Befund vom 26.08.: „Es gibt
   noch immer Sachen die nicht verschoben werden können."

   ⚠️⚠️ SIE HEISSEN NICHT WIE DIE ANDEREN SECHS. Der Prüfstand `fragetext` sucht
   die Griffe der BILDER über `.quiz-vorschau-griff`, die des TEXTBLOCKS über
   `.quiz-vorschau-textzug`/`-textecke` und die der MÖGLICHKEITEN über
   `.quiz-vorschau-antwort*` — jeweils den ERSTEN Treffer. Ein siebter Griff mit
   einem dieser Namen hätte den Lauf stillschweigend auf das falsche Ding zeigen
   lassen, ohne dass irgendwo etwas rot wird. Dieselbe Falle stand schon in
   A.20.17.0 in dieser Datei.

   ⚠️⚠️ ES SIND DREI PAARE MIT DEMSELBEN NAMEN, unterschieden durch
   `data-zugabe` (`loesung` · `dran` · `hinweise`). Ihren Platz bekommen sie aus
   dem JavaScript (`_zugabenZeichnen`), und zwar EINZELN je Art — sie sitzen an
   der MITTE ihres Blocks, wie er gerade wirklich steht. Aus dem gespeicherten
   x/y ginge das nicht: solange der Block im FLUSS steht, gibt es dort gar
   keinen Platz, und der Griff läge in der Bildmitte statt am Kasten.

   ⚠️ Die MITTE und nicht die Ecke — dieselbe Begründung wie bei allen anderen:
   ein frei gesetzter Block ragt im Normalfall über den Rand, und was an seiner
   Kante sitzt, ist dann abgeschnitten.

   ⚠️⚠️ `span.` IM SELEKTOR IST PFLICHT. Diese Griffe sind direkte Kinder von
   `.quiz-vorschau-flaeche`, und dort greift
   `.quiz-vorschau-flaeche > *:not(.quiz-vorschau-medien) { position: relative }`
   mit 0,2,0. Ein reines `.quiz-vorschau-zugabezug` hat nur 0,1,0 — die Griffe
   blieben im FLUSS stehen, irgendwo neben dem Block. Mit dem Typ (0,2,1)
   gewinnt diese Regel eindeutig und nicht bloß, weil sie weiter unten steht.

   ⚠️ Zeichen aus `::before`: ein Textknoten wäre für die Lesbarkeitsprobe von
   `mobil` ein 11-px-Text und damit ein Befund. */
.quiz-vorschau-flaeche > span.quiz-vorschau-zugabezug,
.quiz-vorschau-flaeche > span.quiz-vorschau-zugabeecke {
  position: absolute;
  width: 1.15rem;
  height: 1.15rem;
  border-radius: 0.25rem;
  background-color: var(--xxl-accent);
  border: 2px solid var(--xxl-bg);
  z-index: 3;
  pointer-events: auto;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--xxl-bg);
  line-height: 1;
  touch-action: none;
  opacity: 0.55;
  transition: opacity 0.12s ease;
}
.quiz-vorschau-flaeche:hover .quiz-vorschau-zugabezug,
.quiz-vorschau-flaeche:hover .quiz-vorschau-zugabeecke { opacity: 0.9; }
.quiz-vorschau-zugabezug:hover,
.quiz-vorschau-zugabeecke:hover { opacity: 1; }
.quiz-vorschau-flaeche > span.quiz-vorschau-zugabezug {
  transform: translate(-108%, -50%);
  cursor: grab;
}
.quiz-vorschau-zugabezug:active { cursor: grabbing; }
.quiz-vorschau-zugabezug::before { content: '\2725'; font-size: 0.7rem; }
.quiz-vorschau-flaeche > span.quiz-vorschau-zugabeecke {
  transform: translate(8%, -50%);
  cursor: nwse-resize;
}
.quiz-vorschau-zugabeecke::before { content: '\21F2'; font-size: 0.7rem; }

/* ▶ DIE ANDEREN FASSUNGEN DER ZUSTANDSZEILE (A.20.15.0). Sein Befund vom
   25.08.: „wenn Texte sich ändern brauche ich die Möglichkeit auch die
   geänderten Versionen zu sehen, um alles ordentlich platzieren zu können."

   ⚠️ Sie stehen ÜBER der Hinweiszeile und getrennt von den Wegen zurück: das
   eine ist ein BLICK auf die Bühne, das andere greift in die Frage ein. In
   einer Reihe nebeneinander sähen beide gleich aus, und man klickte
   versehentlich etwas zurück, was man nur ansehen wollte. */
.quiz-vorschau-fassungen {
  display: flex; align-items: center; gap: 0.35rem; flex-wrap: wrap;
  margin-top: 0.4rem;
}
/* ⚠️⚠️ ZWEI REIHEN, ZWEI NAMEN, EIN AUSSEHEN (A.20.25.1).
   `.quiz-vorschau-fassung` sind die FASSUNGEN der Zustandszeile (A.20.15.0),
   `.quiz-vorschau-zugabe` die drei Zuschalter aus A.20.24.0. Beide sehen gleich
   aus, weil beide dasselbe sind: ein BLICK, kein Zustand der Frage. Das
   Aussehen steht deshalb unter einem dritten Namen.

   ⚠️ SIE DÜRFEN SICH DIE KENN-KLASSE NICHT TEILEN, und das ist kein
   Schönheitsfehler: `fragetext` zählt „an einer Buzzer-Frage stehen VIER
   Fassungen zur Auswahl" über `.quiz-vorschau-fassung`. Trugen die Zuschalter
   dieselbe Klasse, zählte der Prüfstand sechs — und meldete rot an einem Bau,
   an dem nichts kaputt war. Genau so ist es beim ersten Anlauf passiert. */
.quiz-vorschau-blickknopf {
  /* ⚠️⚠️ 0,82 rem = 13,12 px, NICHT die 0,78 rem der Nachbarn. `mobil` hat es
     gefunden: seine 13-px-Regel (A.20.7.1, „mach die Zeilen für Mobil noch")
     gilt für jeden Text, den jemand LIEST, und diese Knöpfe tragen ganze Sätze.
     ⚠️ Dabei aufgefallen: `.quiz-vorschau-zurueck` und `.quiz-vorschau-anteil`
     standen seit A.20.9.0 bzw. A.20.13.0 ebenfalls auf 12,48 px — nur sieht
     `mobil` sie nie, weil sein Prüffall kein frei gesetztes Bild hat. Sie sind
     mit angehoben; ein Text ist nicht deshalb lesbar, weil kein Prüfstand
     hinsieht. */
  font-size: 0.82rem;
  padding: 0.15rem 0.5rem;
}
/* Der gewählte Knopf muss sich mit ZWEI Unterschieden abheben, nicht mit
   einem: auf dem Editor-Grund ist eine einzelne Farbänderung an einem kleinen
   Knopf kaum zu sehen. Deshalb Fläche UND Kante. */
.quiz-vorschau-blickknopf.ist-gewaehlt {
  border-color: var(--xxl-accent);
  background: rgba(var(--xxl-accent-rgb), 0.18);
  color: var(--xxl-text);
}
.quiz-vorschau-leiste {
  display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap;
  margin-top: 0.4rem;
}
.quiz-vorschau-zurueck { font-size: 0.82rem; padding: 0.15rem 0.5rem; }
/* ▶ WIE VIEL VOM BILD SICHTBAR IST (A.20.13.0). Sein Befund vom 25.08.: „und
   die Anzeige wieviel von dem Bild sichtbar ist."

   ⚠️ SIE IST EINE MELDUNG, KEIN KNOPF — deshalb dieselbe Größe wie die Knöpfe
   daneben, aber ohne Rand und ohne Zeigefinger. Wer sie anklickt, soll nicht
   erwarten, dass etwas passiert.

   ⚠️ Warnfarbe, nicht Akzentfarbe: sie steht nur da, wenn etwas fehlt. In der
   Leiste stehen sonst nur Wege zurück; eine Meldung, die aussieht wie ein
   Angebot, wird als Angebot gelesen. */
.quiz-vorschau-anteil {
  font-size: 0.82rem;
  padding: 0.15rem 0.5rem;
  border-radius: 0.25rem;
  color: var(--xxl-warning);
  background: rgba(255, 201, 79, 0.14);
  white-space: nowrap;
}

/* ▶ DER SCHALTER FÜRS EINRASTEN (A.20.22.0). Seine Frage zu A.20.12.0.

   ⚠️ Er ist ein ZUSTAND, kein Handgriff — deshalb sieht man ihm an, wie er
   steht, und nicht nur, dass man ihn drücken kann. An: Akzentfarbe mit Rand,
   wie ein gedrückter Knopf. Aus: gedämpft, wie alles Abgeschaltete in diesem
   Haus. Die Knöpfe daneben („↩ zurück …") sind Handgriffe und bleiben schlicht;
   stünde der Schalter genauso da, läse man ihn als Handgriff und drückte ihn,
   um „einmal einzurasten". */
.quiz-vorschau-raster {
  font-size: 0.82rem;
  padding: 0.15rem 0.5rem;
  border: 1px solid var(--xxl-accent);
  color: var(--xxl-accent);
  white-space: nowrap;
}
.quiz-vorschau-raster.ist-aus {
  border-color: var(--xxl-border);
  color: var(--xxl-text-dim);
}

/* --- Das Frageformular: drei Gruppen statt eines Streifens (A.20.7.0) ------
   Derselbe Befund, dieselbe Antwort in derselben Sprache wie die Stationen
   oben: eine Kante, eine kleine gesperrte Marke. Vorher standen dreizehn
   Beschriftungen und Felder gleich laut untereinander, über 1180 px. */
.quiz-feldgruppe {
  border-left: 2px solid var(--xxl-border);
  padding-left: 0.85rem;
  margin-bottom: 0.9rem;
}
/* Die Wahl der Frageform trifft man EINMAL — sie muss nicht so laut sein wie
   „Speichern". */
.quiz-formwahl .xxl-btn-ghost,
.quiz-formwahl .xxl-btn-accent {
  font-size: 0.85rem;
  padding: 0.35rem 0.7rem;
}
/* Die Frage ist die Hauptsache — nur sie bekommt die Akzentkante. */
.quiz-feldgruppe-haupt { border-left-color: var(--xxl-accent); }
.quiz-gruppenkopf {
  display: block;
  margin-bottom: 0.55rem;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--xxl-text-dim);
}
.quiz-feldgruppe-haupt > .quiz-gruppenkopf { color: var(--xxl-accent); }
/* Zwei schmale Zahlenfelder nebeneinander statt zweimal volle Breite. */
.quiz-feldzeile {
  display: flex; align-items: flex-start; gap: 1.5rem; flex-wrap: wrap;
}
.quiz-feldspalte { flex: 0 1 22rem; min-width: 9rem; }

/* ⚠️ `styles.css` wird NACH `tailwind.css` geladen, und `.xxl-input`
   (`width: 100%`) wiegt genauso schwer wie `.w-24`. Ergebnis bis A.20.6.0:
   die Breitenangabe an einem Eingabefeld war IMMER wirkungslos, sobald das
   Feld nicht zufällig in einem Flex-Kasten stand — das Feld für „10 Punkte"
   war so breit wie die ganze Karte. Hier gewinnt sie zurück. */
.xxl-input.w-20 { width: 5rem; }
.xxl-input.w-24 { width: 6rem; }
.xxl-input.w-32 { width: 8rem; }
.xxl-input.w-40 { width: 10rem; }

/* --- Antwortmöglichkeiten im Editor (A.6.0.0) ------------------------------ */

.quiz-optionen {
  border: 1px dashed var(--xxl-border);
  border-radius: 0.4rem;
  padding: 0.6rem 0.65rem;
}
.quiz-option-zeile {
  display: flex; align-items: center; gap: 0.5rem;
  margin-bottom: 0.4rem;
}
/* Der Haken IST die Lösungseingabe — bei einer Wahlfrage gibt es kein eigenes
   Lösungsfeld. Deshalb ein richtiger Knopf und keine 13-px-Checkbox: man muss
   auf einen Blick sehen, was als richtig gilt. */
.quiz-option-haken {
  flex: 0 0 auto;
  width: 2rem; height: 2rem;
  display: inline-flex; align-items: center; justify-content: center;
  border: 2px solid var(--xxl-border);
  border-radius: 0.35rem;
  background: transparent;
  color: transparent;
  font-weight: 800;
  cursor: pointer;
}
.quiz-option-haken:hover { border-color: var(--xxl-success); }
.quiz-option-haken.ist-richtig {
  border-color: var(--xxl-success);
  background: var(--xxl-success);
  color: #0c2408;
}
.quiz-option-marke {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.6rem; height: 1.6rem;
  border-radius: 50%;
  background: rgba(var(--xxl-accent-rgb), 0.15);
  color: var(--xxl-accent);
  font-weight: 800; font-size: 0.85rem;
}

/* Die Optionen in der Ansicht (nicht im Formular). */
.quiz-option-anzeige {
  display: flex; align-items: center; gap: 0.5rem;
  font-size: 0.9rem;
  color: var(--xxl-text-muted);
  padding: 0.1rem 0;
}
.quiz-option-anzeige.ist-richtig { color: var(--xxl-success); font-weight: 600; }

/* Die Form der Frage steht VOR der Frage: sie ändert, was darunter steht. */
.quiz-typmarke {
  display: inline-block;
  margin-bottom: 0.25rem;
  padding: 0.05rem 0.5rem;
  border: 1px solid var(--xxl-accent);
  border-radius: 999px;
  font-size: 0.7rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--xxl-accent);
}

/* ===========================================================================
   A.5.1.0 — Enthüllung (Unschärfe, Puzzle) und wegklappbarer QR
   =========================================================================== */

/* Das Bild bestimmt die Größe, die Kacheln legen sich darüber. Deshalb ein
   eigener Rahmen mit position: relative — ohne ihn bezögen sich die absolut
   gesetzten Kacheln auf den nächsten positionierten Vorfahren, und das wäre
   irgendwo weit außerhalb. */
.stage-media-buehne {
  position: relative;
  display: flex; align-items: center; justify-content: center;
  max-width: 100%; max-height: 100%;
  min-height: 0;
  /* Beschneidet die Unschärfe an der Bildkante. Ohne das blutet der Weichzeichner
     über den Rand hinaus und das Bild franst aus, statt als Fläche zu stehen. */
  overflow: hidden;
  border-radius: calc(0.5 * 1.25 * var(--bw));
}
.stage-media-buehne .stage-media-image {
  /* filter (Unschärfe) wird per JS gesetzt; die Übergangszeit sorgt dafür,
     dass das letzte Stück zum scharfen Bild nicht springt. */
  transition: filter 0.15s linear;
}

.stage-puzzle {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  border-radius: calc(0.5 * 1.25 * var(--bw));
  overflow: hidden;
  box-shadow: var(--xxl-shadow-lg);
}
.stage-puzzle-teil {
  position: absolute;
  top: 0; left: 0;
  background-repeat: no-repeat;
  /* Das Gleiten an den richtigen Platz. Etwas langsamer als der Takt, in dem
     Teile gesetzt werden — so überlagern sich mehrere Bewegungen und das Bild
     ordnet sich sichtbar, statt zu zucken. */
  transition: transform 0.55s cubic-bezier(0.22, 0.8, 0.3, 1);
  will-change: transform;
}
@media (prefers-reduced-motion: reduce) {
  .stage-puzzle-teil { transition: none; }
}

/* Kachel-Aufdeckung: Deckel über dem Bild, die nacheinander verschwinden.
   Anders als beim Puzzle bleibt das Bild darunter SICHTBAR — es wird nur
   verdeckt. Der feine Rand macht das Raster erkennbar; ohne ihn sieht eine
   halb aufgedeckte Fläche aus wie ein Anzeigefehler. */
.stage-kachel-teil {
  position: absolute;
  top: 0; left: 0;
  background: var(--xxl-card);
  box-shadow: inset 0 0 0 1px var(--xxl-border);
  opacity: 1;
  transition: opacity 0.45s ease-out;
  will-change: opacity;
}
@media (prefers-reduced-motion: reduce) {
  .stage-kachel-teil { transition: none; }
}

/* --- QR in der Ecke, wegklappbar ------------------------------------------ */
.stage-qr-ecke {
  position: fixed;
  right: clamp(calc(0.75 * 1.25 * var(--bw)), calc(2 * var(--bw)), calc(2 * 1.25 * var(--bw)));
  bottom: clamp(calc(0.75 * 1.25 * var(--bw)), calc(2 * var(--bw)), calc(2 * 1.25 * var(--bw)));
  z-index: 5;
  display: flex; flex-direction: column; align-items: center; gap: calc(0.35 * 1.25 * var(--bw));
}
/* Der Knopf bleibt IMMER da — sonst könnte man den Code nicht zurückholen,
   ohne die Seite neu zu laden. Nur der Code selbst verschwindet. */
.stage-qr-ecke.is-versteckt .stage-qr-small { display: none; }
.stage-qr-toggle {
  font-size: clamp(calc(0.65 * 1.25 * var(--bw)), calc(1 * var(--bw)), calc(0.85 * 1.25 * var(--bw)));
  padding: 0.25em 0.7em;
  border-radius: 999px;
  cursor: pointer;
  color: var(--xxl-text-muted);
  background: var(--xxl-sidebar);
  border: 1px solid var(--xxl-border);
  opacity: 0.4;
  transition: opacity 0.2s;
}
.stage-qr-ecke:hover .stage-qr-toggle { opacity: 1; }
.stage-qr-toggle:hover { color: var(--xxl-text); border-color: var(--xxl-accent); }

/* ===========================================================================
   A.5.2.0 — Das Gesicht der ANLAGE vs. das Gesicht eines QUIZ
   =========================================================================== */

/* Die Grundfarben stammen aus dem Logo des ersten Quiz („Renas Geburtstagsquiz“):
   Neon-Pink und Türkis. Für die Plattform selbst — Startseite, Anmeldung,
   Einladung, Rechtliches — ist das falsch: sie soll nach sich aussehen und
   nicht nach dem Quiz, das gerade darauf läuft.

   Die Werte hier sind aus dem Seitenbanner AUSGEZÄHLT (ImageMagick-Histogramm),
   nicht geschätzt: #1D0658 als Grundfläche, #9732E4 als Signalfarbe,
   #C77BF5 als hellere Stufe. Der Banner führt weder Pink noch Türkis. */
body.marke-seite {
  --xxl-accent:        #9732E4;
  --xxl-accent-hover:  #7F1FCB;
  --xxl-accent-light:  #C77BF5;
  --xxl-accent-rgb:    151, 50, 228;
  --xxl-accent2:       #C77BF5;
  --xxl-accent2-rgb:   199, 123, 245;
  --xxl-card:          #241247;
  --xxl-card-hover:    #2F1A5C;
}

/* --- Logo und Banner eines Quiz im Editor --------------------------------- */
.quiz-marke {
  border: 1px dashed var(--xxl-border);
  border-radius: 0.4rem;
  padding: 0.65rem 0.75rem;
}
.quiz-marke-kopf { font-size: 0.9rem; color: var(--xxl-text-dim); }
.quiz-marke-hinweis {
  display: block;
  font-size: 0.78rem;
  color: var(--xxl-text-muted);
}
.quiz-marke-plaetze {
  display: flex; flex-wrap: wrap; gap: 1rem;
  margin-top: 0.6rem;
}
.quiz-markeplatz { flex: 1 1 15rem; min-width: 0; }
.quiz-markeplatz-titel { font-size: 0.9rem; color: var(--xxl-text); }
.quiz-markebild {
  max-width: 100%;
  max-height: 7rem;
  object-fit: contain;
  border-radius: 0.3rem;
  background: var(--xxl-sidebar);
  display: block;
}

/* ===========================================================================
   A.5.5.0 — Landingpage als Bühne
   Die Seite sieht aus wie das, was sie verkauft: eine Quizshow. Scheinwerfer
   von oben, die Beitrittskarte als Podium mit umlaufendem Lichtstrahl, laute
   Schrift für Titel, leise für Fließtext.
   Der Grundsatz aus A.5.3.0 gilt unverändert: Das Raumcode-Feld steht GANZ
   OBEN und ist fokussiert — wer beitreten will, scrollt nicht.
   =========================================================================== */

/* Schriften: selbst gehostet, weil die CSP nur font-src 'self' erlaubt.
   Beides Variable Fonts (eine Datei je Familie deckt alle Gewichte ab), OFL. */
@font-face {
  font-family: 'Baloo 2';
  src: url('/static/marke/fonts/baloo2.woff2') format('woff2');
  font-weight: 400 800;
  font-display: swap;
}
@font-face {
  font-family: 'Schibsted Grotesk';
  src: url('/static/marke/fonts/schibsted.woff2') format('woff2');
  font-weight: 400 900;
  font-display: swap;
}

.land-body {
  --land-schau: 'Baloo 2', ui-rounded, system-ui, sans-serif;
  --land-schrift: 'Schibsted Grotesk', system-ui, sans-serif;
  font-family: var(--land-schrift);
  /* Carbon-Gewebe als SEITENTEXTUR (Stefans Ansage 07.08.: nicht in die
     Kästchen, flächig und schwach): zwei gekreuzte Köperbindungen, so blass,
     dass man sie erst auf der Fläche wahrnimmt — Material, kein Muster. */
  background:
    repeating-linear-gradient(45deg, rgba(255, 255, 255, 0.014) 0 2px, transparent 2px 8px),
    repeating-linear-gradient(-45deg, rgba(0, 0, 0, 0.12) 0 2px, transparent 2px 8px),
    var(--xxl-bg);
  min-height: 100vh;
  margin: 0;
  color: var(--xxl-text);
}

/* Scheinwerfer: zwei Kegel von oben, ein Bodenglühen von unten. Fixiert,
   damit das Licht beim Scrollen steht wie im Studio. */
.land-body::before {
  content: '';
  position: fixed; inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(38rem 30rem at 12% -8%, rgba(var(--xxl-accent-rgb), 0.24), transparent 62%),
    radial-gradient(38rem 30rem at 88% -8%, rgba(var(--xxl-accent2-rgb), 0.16), transparent 62%),
    radial-gradient(70rem 42rem at 50% 118%, rgba(var(--xxl-accent-rgb), 0.14), transparent 65%);
}
/* Feines Korn über allem — nimmt den glatten Verläufen das Plastikhafte. */
.land-body::after {
  content: '';
  position: fixed; inset: 0;
  z-index: 5;
  pointer-events: none;
  opacity: 0.045;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 160px 160px;
}

/* --- Kopf ------------------------------------------------------------------ */
.land-hero {
  position: relative;
  overflow: hidden;
  background:
    radial-gradient(120% 80% at 50% 0%, rgba(var(--xxl-accent-rgb), 0.18), transparent 70%),
    linear-gradient(180deg, var(--xxl-header-base), var(--xxl-bg));
  padding: clamp(1.5rem, 5vw, 4rem) 1rem clamp(2.25rem, 6vw, 4rem);
  border-bottom: 1px solid var(--xxl-border-light);
}
/* Scheinwerferkegel wie auf dem Banner (Stefans Wunsch 07.08.): weiche,
   breit gestreute Lichtkegel von oben. Damit es nach Licht aussieht und
   nicht albern wirkt: starker Blur, wenig Deckkraft, Fuß deutlich breiter
   als Kopf, und die Kegel stehen STILL. Handy: ein zentraler Kegel;
   Rechner: zwei, die sich über Logo und Podium kreuzen. */
.land-hero::before,
.land-hero::after {
  content: '';
  position: absolute;
  top: -14rem;
  height: 46rem;
  width: 15rem;
  background: linear-gradient(to bottom,
    rgba(var(--xxl-accent2-rgb), 0.34),
    rgba(var(--xxl-accent-rgb), 0.10) 55%,
    transparent 82%);
  clip-path: polygon(44% 0, 56% 0, 100% 100%, 0 100%);
  filter: blur(16px);
  transform-origin: 50% 0;
  pointer-events: none;
}
.land-hero::before { left: 50%; margin-left: -7.5rem; }
.land-hero::after  { display: none; }
@media (min-width: 56rem) {
  .land-hero::before { left: 14%; margin-left: 0; transform: rotate(18deg); }
  .land-hero::after  { display: block; right: 16%; transform: rotate(-18deg); }
}
/* Der Inhalt liegt über den Kegeln. */
.land-hero-inner { position: relative; z-index: 1; }
/* Grid statt Flex, weil die Reihenfolge sich unterscheiden muss:
   Auf dem Handy kommt die Beitrittskarte ZWISCHEN Logo und Fließtext — wer
   einen Raumcode abtippen will, soll das Feld sehen, ohne an einem Absatz
   vorbeizuscrollen. Auf dem Rechner steht sie rechts daneben. */
.land-hero-inner {
  max-width: 62rem;
  margin: 0 auto;
  display: grid;
  gap: clamp(1.1rem, 3vw, 2.5rem);
  grid-template-columns: 1fr;
  grid-template-areas:
    "logo"
    "beitritt"
    "text";
  align-items: center;
}
.land-logo      { grid-area: logo; }
.land-beitritt  { grid-area: beitritt; }
.land-hero-text { grid-area: text; min-width: 0; }

@media (min-width: 56rem) {
  .land-hero-inner {
    /* 22rem hält die Karte lesbar; der Rest geht an Logo und Text. */
    grid-template-columns: minmax(0, 1fr) 22rem;
    grid-template-areas:
      "logo beitritt"
      "text beitritt";
    align-items: start;
  }
}

/* Auftritt beim Laden: Logo, Podium, Text — nacheinander, wie ein Vorhang. */
@keyframes land-auftritt {
  from { opacity: 0; transform: translateY(18px); }
  to   { opacity: 1; transform: none; }
}
.land-logo      { animation: land-auftritt 0.7s ease backwards; }
.land-beitritt  { animation: land-auftritt 0.7s ease 0.12s backwards; }
.land-hero-text { animation: land-auftritt 0.7s ease 0.24s backwards; }

.land-logo {
  display: block;
  width: 100%;
  max-width: 20rem;
  margin-inline: auto;
  border-radius: 0.9rem;
  border: 1px solid rgba(var(--xxl-accent2-rgb), 0.25);
  box-shadow:
    0 18px 50px rgba(0, 0, 0, 0.5),
    0 0 60px rgba(var(--xxl-accent-rgb), 0.3);
}
@media (min-width: 56rem) {
  .land-logo { max-width: 26rem; margin-inline: 0; }
}

/* „ON AIR"-Plakette: kleine Laut-Zeile über den Titeln. */
.land-kicker {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--xxl-accent-light);
  border: 1px solid rgba(var(--xxl-accent2-rgb), 0.35);
  border-radius: 99px;
  padding: 0.32rem 0.85rem;
  margin-bottom: 0.9rem;
  background: rgba(var(--xxl-accent-rgb), 0.08);
}
@keyframes land-puls {
  0%, 100% { box-shadow: 0 0 0 0 rgba(var(--xxl-accent2-rgb), 0.5); }
  50%      { box-shadow: 0 0 0 4px rgba(var(--xxl-accent2-rgb), 0); }
}
.land-kicker-punkt {
  width: 0.5rem; height: 0.5rem;
  border-radius: 50%;
  background: var(--xxl-accent-light);
  animation: land-puls 2.2s ease-in-out infinite;
}

.land-titel {
  font-family: var(--land-schau);
  font-size: clamp(2rem, 5vw, 3.4rem);
  font-weight: 800;
  line-height: 1.08;
  margin-bottom: 0.7rem;
  color: var(--xxl-text);
}
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .land-titel {
    background: linear-gradient(120deg, #ffffff 25%, var(--xxl-accent-light) 75%, var(--xxl-accent) 105%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }
}
.land-lead {
  font-size: clamp(1rem, 1.5vw, 1.15rem);
  line-height: 1.65;
  color: var(--xxl-text-muted);
  max-width: 34rem;
}

/* --- Das Podium: die Beitrittskarte ---------------------------------------- */
.land-beitritt {
  position: relative;
  border-radius: 1rem;
  padding: 1.5rem;
  background: linear-gradient(180deg, #2a1552, #1c0d3b);
  border: 1px solid rgba(var(--xxl-accent2-rgb), 0.22);
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.45);
}
/* Der Lichtstrahl läuft AUSSEN um die Karte: ein Kegelverlauf, per Maske auf
   den 2px-Rahmen beschnitten — er kreuzt also nie den Inhalt. Browser ohne
   @property zeigen einen stehenden Schimmer statt des Laufs. */
@property --land-winkel {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0turn;
}
@keyframes land-strahl { to { --land-winkel: 1turn; } }
.land-beitritt::before {
  content: '';
  position: absolute; inset: -2px;
  z-index: 0;
  border-radius: calc(1rem + 2px);
  padding: 2px;
  background: conic-gradient(from var(--land-winkel),
    transparent 0 62%,
    rgba(var(--xxl-accent2-rgb), 0.9) 78%,
    var(--xxl-accent) 86%,
    transparent 96%);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  mask-composite: exclude;
  animation: land-strahl 7s linear infinite;
  pointer-events: none;
}

.land-beitritt-titel {
  font-family: var(--land-schau);
  font-size: 1.35rem;
  font-weight: 700;
  color: var(--xxl-text);
  margin-bottom: 0.25rem;
}
.land-beitritt-hinweis {
  font-size: 0.9rem;
  line-height: 1.5;
  color: var(--xxl-text-muted);
  margin-bottom: 1.1rem;
}
.land-feld-label {
  display: block;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--xxl-text-dim);
  margin-bottom: 0.4rem;
}
/* Das Feld ist die Anzeigetafel: groß, zentriert, gesperrt gesetzt. */
.land-code-feld {
  width: 100%;
  box-sizing: border-box;
  font-family: var(--land-schau);
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: 0.3em;
  text-align: center;
  text-transform: uppercase;
  color: var(--xxl-text);
  caret-color: var(--xxl-accent-light);
  background: rgba(10, 4, 26, 0.65);
  border: 1px solid rgba(var(--xxl-accent2-rgb), 0.4);
  border-radius: 0.7rem;
  padding: 0.65rem 0.25rem 0.55rem;
  margin-bottom: 1rem;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.land-code-feld::placeholder {
  color: var(--xxl-text-dim);
  opacity: 0.55;
  letter-spacing: 0.3em;
}
.land-code-feld:focus {
  outline: none;
  border-color: var(--xxl-accent-light);
  box-shadow:
    0 0 0 3px rgba(var(--xxl-accent-rgb), 0.35),
    0 0 26px rgba(var(--xxl-accent-rgb), 0.35);
}

/* Chrom-Look (Stefans Ansage 07.08.): dunkles, lila getöntes Metall. Das
   Kennzeichen von Chrom ist die harte Spiegelkante auf halber Höhe — oben
   der „Himmel", unten der „Boden" mit leichtem Aufheller an der Unterkante.
   Dazu polierte Oberkante (inset-Licht) und ein leises Lila-Glühen. */
.land-los-knopf {
  display: block;
  width: 100%;
  box-sizing: border-box;
  position: relative;
  overflow: hidden;
  font-family: var(--land-schau);
  font-size: 1.15rem;
  font-weight: 700;
  color: #f3edff;
  text-align: center;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
  background: linear-gradient(180deg,
    #6f5c92 0%,
    #43305f 14%,
    #261741 46%,
    #170c2c 50%,
    #221338 56%,
    #35224f 86%,
    #4d3a6e 100%);
  border: 1px solid rgba(var(--xxl-accent2-rgb), 0.45);
  border-radius: 0.8rem;
  padding: 0.8rem 1.5rem;
  cursor: pointer;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.32),
    inset 0 -1px 0 rgba(255, 255, 255, 0.07),
    0 6px 18px rgba(0, 0, 0, 0.5),
    0 0 18px rgba(var(--xxl-accent-rgb), 0.22);
  transition: transform 0.15s ease, box-shadow 0.15s ease, filter 0.15s ease;
}
.land-los-knopf:hover {
  transform: translateY(-2px);
  filter: brightness(1.12);
  border-color: rgba(var(--xxl-accent2-rgb), 0.65);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.38),
    inset 0 -1px 0 rgba(255, 255, 255, 0.07),
    0 9px 24px rgba(0, 0, 0, 0.55),
    0 0 26px rgba(var(--xxl-accent-rgb), 0.4);
}
.land-los-knopf:active {
  transform: translateY(0);
  filter: brightness(0.95);
  box-shadow:
    inset 0 2px 6px rgba(0, 0, 0, 0.5),
    0 3px 10px rgba(0, 0, 0, 0.45),
    0 0 14px rgba(var(--xxl-accent-rgb), 0.25);
}
/* Glanz, der beim Überfahren einmal über den Knopf zieht. */
.land-los-knopf::after {
  content: '';
  position: absolute; inset: 0;
  background: linear-gradient(105deg, transparent 40%, rgba(255, 255, 255, 0.35) 50%, transparent 60%);
  transform: translateX(-130%);
}
.land-los-knopf:hover::after { transition: transform 0.55s ease; transform: translateX(130%); }

.land-fehler {
  display: none;
  font-size: 0.88rem;
  margin-top: 0.8rem;
  text-align: center;
  color: var(--xxl-danger);
}

/* --- Abschnitte ------------------------------------------------------------ */
.land-abschnitt {
  max-width: 62rem;
  margin: 0 auto;
  padding: clamp(2.25rem, 5vw, 3.75rem) 1rem;
}
.land-abschnitt-titel {
  font-family: var(--land-schau);
  font-size: clamp(1.6rem, 3vw, 2.3rem);
  font-weight: 800;
  line-height: 1.15;
  margin-bottom: 1.5rem;
  color: var(--xxl-text);
}

/* --- Drei Schritte --------------------------------------------------------- */
.land-schritte {
  list-style: none; margin: 0; padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 1.25rem;
}
.land-schritt {
  position: relative;
  overflow: hidden;
  background: linear-gradient(180deg, var(--xxl-content), rgba(0, 0, 0, 0.12));
  border: 1px solid var(--xxl-border-light);
  border-radius: 0.9rem;
  padding: 1.4rem 1.4rem 1.3rem;
  transition: transform 0.25s ease, border-color 0.25s ease;
}
.land-schritt:hover {
  transform: translateY(-3px);
  border-color: rgba(var(--xxl-accent2-rgb), 0.4);
}
/* Die Zahl steht riesig und nur umrissen hinter der Ecke — Bühnenanschrieb,
   kein Aufzählungspunkt. Die ol trägt die Reihenfolge schon selbst. */
.land-schritt-zahl {
  position: absolute;
  top: -0.9rem; right: 0.3rem;
  font-family: var(--land-schau);
  font-size: 5.5rem;
  font-weight: 800;
  line-height: 1;
  color: transparent;
  -webkit-text-stroke: 1.5px rgba(var(--xxl-accent2-rgb), 0.3);
  pointer-events: none;
}
.land-schritt-titel {
  font-family: var(--land-schau);
  font-size: 1.2rem;
  font-weight: 700;
  margin-bottom: 0.35rem;
  color: var(--xxl-text);
}
.land-schritt-text {
  color: var(--xxl-text-muted);
  line-height: 1.55;
  /* Platz für die Geisterzahl in der Ecke ist nicht nötig — sie liegt hinter
     dem Text; der Umriss ist blass genug, um nicht zu stören. */
  position: relative;
}

/* --- Was ein Quiz kann ----------------------------------------------------- */
.land-karten {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: 1.25rem;
}
.land-karte {
  position: relative;
  overflow: hidden;
  background: linear-gradient(180deg, var(--xxl-card), rgba(0, 0, 0, 0.18));
  border: 1px solid var(--xxl-border);
  border-radius: 0.9rem;
  padding: 1.4rem;
  transition: transform 0.25s ease, border-color 0.25s ease, box-shadow 0.25s ease;
}

/* KEIN Glanz in den Kästchen (Stefans Ansage 07.08., zweiter Anlauf: „noch
   immer in den Kästchen") — der Glanz gehört zum Carbon, und das liegt auf
   der FLÄCHE. Ein breites Lichtband zieht darum HINTER den Karten über den
   Seitenhintergrund (z-index -1; die Karten sind deckend und bleiben ruhig).
   Fixiert wie die Scheinwerfer, läuft nur auf transform. */
@keyframes land-lichtlauf {
  0%   { transform: translateX(-140%) skewX(-14deg); }
  26%  { transform: translateX(340%) skewX(-14deg); }
  100% { transform: translateX(340%) skewX(-14deg); }
}
.land-lichtlauf {
  position: fixed;
  top: 0; bottom: 0; left: 0;
  width: 36vw;
  z-index: -1;
  pointer-events: none;
  background: linear-gradient(105deg,
    transparent 0%,
    rgba(255, 255, 255, 0.025) 45%,
    rgba(var(--xxl-accent2-rgb), 0.06) 52%,
    transparent 100%);
  transform: translateX(-140%) skewX(-14deg);
  animation: land-lichtlauf 18s ease-in-out infinite;
}
.land-karte:hover {
  transform: translateY(-4px);
  border-color: rgba(var(--xxl-accent2-rgb), 0.45);
  box-shadow:
    0 14px 34px rgba(0, 0, 0, 0.45),
    0 0 24px rgba(var(--xxl-accent-rgb), 0.15);
}
/* Das Zeichen steht in einem kleinen Spot — rund, angeleuchtet. Eigene
   SVG-Strichzeichnungen in Akzent-Hell (Stefans Ansage 07.08.: die bunten
   Emojis passten nicht zum Design); Farbe kommt über currentColor. */
.land-karte-zeichen {
  display: flex;
  align-items: center; justify-content: center;
  width: 2.9rem; height: 2.9rem;
  color: var(--xxl-accent-light);
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, rgba(var(--xxl-accent2-rgb), 0.35), rgba(var(--xxl-accent-rgb), 0.12) 70%);
  border: 1px solid rgba(var(--xxl-accent2-rgb), 0.3);
  margin-bottom: 0.85rem;
  transition: transform 0.25s ease;
}
.land-karte-zeichen svg { width: 1.5rem; height: 1.5rem; display: block; }
.land-karte:hover .land-karte-zeichen { transform: scale(1.1) rotate(-5deg); }
.land-karte-titel {
  font-family: var(--land-schau);
  font-size: 1.15rem;
  font-weight: 700;
  margin-bottom: 0.4rem;
  color: var(--xxl-accent-light);
}
.land-karte-text { color: var(--xxl-text-muted); line-height: 1.55; }

/* --- Abschluss ------------------------------------------------------------- */
.land-abschluss { text-align: center; position: relative; }
.land-abschluss-text {
  color: var(--xxl-text-muted);
  max-width: 34rem;
  margin: 0 auto 1.4rem;
  line-height: 1.6;
}
.land-abschluss-knopf {
  display: inline-block;
  width: auto;
  padding: 0.8rem 2.4rem;
  text-decoration: none;
}
.land-abschluss-nachsatz {
  margin-top: 1.1rem;
  font-size: 0.9rem;
  color: var(--xxl-text-dim);
}
.land-abschluss-nachsatz a {
  color: var(--xxl-accent-light);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.land-fuss {
  border-top: 1px solid var(--xxl-border-light);
  padding: 1.75rem 1rem 2.5rem;
  text-align: center;
}
.land-fuss-marke {
  font-family: var(--land-schau);
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--xxl-text-dim);
  margin-bottom: 0.4rem;
}
.land-fuss .legal-nav { margin-top: 0; }

/* KEIN Scroll-Reveal: Zwei Anläufe (IntersectionObserver, dann
   animation-timeline) sind daran gescheitert, dass Ganzseiten-Aufnahmen
   nicht scrollen — die Prüfstand-Screenshots (das Abnahmemittel!) zeigten
   eine halb leere Seite. Ein Effekt, der Inhalt verstecken kann, hat auf
   einer Landingpage nichts verloren. Bewegung kommt von Lichtstrahl,
   Glanzläufen, Hero-Auftritt und Puls — nichts davon versteckt etwas. */

/* Wer weniger Bewegung wünscht, bekommt die Bühne ohne Lauflicht.
   (.land-lichtlauf bleibt ohne Animation auf -140 % — also außerhalb.) */
@media (prefers-reduced-motion: reduce) {
  .land-logo, .land-beitritt, .land-hero-text { animation: none; }
  .land-beitritt::before { animation: none; }
  .land-kicker-punkt { animation: none; }
  .land-lichtlauf { animation: none; }
  .land-los-knopf, .land-karte, .land-schritt,
  .land-karte-zeichen { transition: none; }
}

/* ===========================================================================
   A.5.6.0 — Vorschau-Leiste auf der Bühne
   Die Bühne darüber bleibt unangetastet: Was in der Vorschau zu sehen ist,
   muss Pixel für Pixel das sein, was am Abend am Beamer hängt. Deshalb liegt
   die Bedienung als eigene Leiste ÜBER der Bühne und ändert nichts an ihr.
   =========================================================================== */
.vorschau-leiste {
  position: fixed;
  left: 50%; bottom: 1rem;
  transform: translateX(-50%);
  z-index: 60;
  display: flex; flex-wrap: wrap;
  align-items: center; gap: 0.5rem;
  max-width: calc(100vw - 2rem);
  padding: 0.55rem 0.8rem;
  border-radius: 0.9rem;
  background: rgba(12, 5, 28, 0.92);
  border: 1px solid rgba(var(--xxl-accent2-rgb), 0.35);
  box-shadow: 0 12px 34px rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(6px);
}
.vorschau-marke {
  font-size: 0.7rem; font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--xxl-accent-light);
  padding-right: 0.35rem;
}
.vorschau-zaehler {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  color: var(--xxl-text);
  min-width: 3.5rem;
  text-align: center;
}
.vorschau-knopf {
  font: inherit;
  font-size: 0.9rem;
  color: var(--xxl-text);
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(var(--xxl-accent2-rgb), 0.3);
  border-radius: 0.55rem;
  padding: 0.4rem 0.75rem;
  cursor: pointer;
  text-decoration: none;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.vorschau-knopf:hover { background: rgba(255, 255, 255, 0.12); }
.vorschau-knopf:disabled { opacity: 0.4; cursor: default; }
.vorschau-knopf.ist-an {
  background: var(--xxl-accent);
  border-color: var(--xxl-accent);
  color: #fff;
}
.vorschau-pfeil { padding: 0.4rem 0.7rem; font-size: 1rem; }
.vorschau-leise { color: var(--xxl-text-dim); border-color: var(--xxl-border); }

/* In der Vorschau gibt es keinen Raum — der QR zeigte auf nichts. */
.ist-vorschau .stage-qr-ecke { display: none; }

/* ⚠️⚠️⚠️ PLATZ FÜR DIE LEISTE — ABER AM RAHMEN, NICHT IN DER BÜHNE
   (A.20.48.0, Befund 9, 06.09.2026).

   Bis A.20.47.2 stand hier `.ist-vorschau .stage-main { padding-bottom: 4.5rem }`.
   Das machte aus `?vorschau=` eine DRITTE Geometrie: `.stage-main` ist ein
   festes Feld von 1280×720 (`min-` und `max-height`), ein Innenabstand nimmt
   die Höhe also nicht von aussen, sondern vom INHALT — die Zonen bekamen rund
   7 % weniger als im Saal und im Editor. Man sah eine Bühne, die es nirgends
   gibt, und gestaltete gegen sie. Genau das sollte der ganze Zonen-Umbau
   beenden.

   ▶ Der Platz kommt jetzt aus dem SKALIERUNGSFAKTOR des Feldes. Das Feld wird
     als Ganzes kleiner gerechnet — 1280×720 bleiben 1280×720, jede Zone, jeder
     Schriftgrad und jede der 81 gesetzten Platzierungen behalten ihr Verhältnis
     Punkt für Punkt. Nur der Rahmen darum gibt die 4,5 rem her, in denen die
     Leiste steht.
   ⚠️ Die Rechnung ist dieselbe wie bei `.stage-body` oben, nur mit der Höhe
     abzüglich der Leiste. Wer die eine ändert, ändert die andere mit. */
.stage-body.ist-vorschau {
  transform: translate(-50%, -50%)
             scale(min(calc(100vw / 1280px), calc((100vh - 4.5rem) / 720px)));
}

/* Ziel des Rückwegs aus der Vorschau kurz aufleuchten lassen — in einer langen
   Fragenliste findet man die Stelle sonst nicht wieder, obwohl man dort steht. */
.quiz-question.ist-angesprungen {
  animation: frage-angesprungen 2s ease-out;
}
@keyframes frage-angesprungen {
  0%, 35% { box-shadow: 0 0 0 2px var(--xxl-accent), 0 0 22px rgba(var(--xxl-accent-rgb), 0.55); }
  100%    { box-shadow: 0 0 0 0 transparent, 0 0 0 transparent; }
}
@media (prefers-reduced-motion: reduce) {
  .quiz-question.ist-angesprungen { animation: none; box-shadow: 0 0 0 2px var(--xxl-accent); }
}

/* ===========================================================================
   Schätzen und Ordnen (A.10.0.0)
   =========================================================================== */

/* --- Handy: die Schätzung ------------------------------------------------- */

.zahl-feld-box {
  display: flex; flex-direction: column;
  gap: 0.6rem;
  width: 100%; max-width: 26rem;
}
.zahl-zeile {
  display: flex; align-items: stretch; gap: 0.5rem;
}
/* Groß und mittig: die Zahl ist das Einzige auf diesem Schirm, und sie wird
   im Halbdunkel mit dem Daumen eingetippt. */
.zahl-feld {
  flex: 1 1 auto; min-width: 0;
  min-height: 3.2rem;
  padding: 0.6rem 0.9rem;
  border: 2px solid var(--xxl-border);
  border-radius: 0.75rem;
  background: var(--xxl-card);
  color: var(--xxl-text);
  font-size: 1.5rem;
  font-weight: 700;
  text-align: center;
  /* 16px-Untergrenze verhindert, dass iOS beim Fokus in die Seite zoomt. */
  -webkit-appearance: none;
}
.zahl-feld:focus {
  outline: none;
  border-color: var(--xxl-accent);
}
.zahl-einheit {
  display: flex; align-items: center;
  padding: 0 0.6rem;
  color: var(--xxl-text-muted);
  font-size: 1.05rem;
  white-space: nowrap;
}
.zahl-knopf {
  min-height: 3.2rem;
  padding: 0 1rem;
  border: none;
  border-radius: 0.75rem;
  background: var(--xxl-accent);
  color: #2A0F62;
  font-weight: 800;
  touch-action: manipulation;
}
.zahl-knopf:disabled { opacity: 0.5; }
.zahl-feld-box.ist-gesperrt .zahl-feld { opacity: 0.6; }

/* --- Handy: die Reihenfolge ------------------------------------------------ */

.reihe-feld {
  display: flex; flex-direction: column;
  gap: 0.5rem;
  width: 100%; max-width: 26rem;
}
.reihe-zeile {
  display: flex; align-items: center; gap: 0.6rem;
  min-height: 3.2rem;
  padding: 0.4rem 0.6rem;
  border: 2px solid var(--xxl-border);
  border-radius: 0.75rem;
  background: var(--xxl-card);
  color: var(--xxl-text);
}
.reihe-zeile.ist-gesperrt { opacity: 0.65; }
.reihe-zeile.ist-richtig { border-color: var(--xxl-success); }
.reihe-zeile.ist-falsch  { border-color: var(--xxl-danger); }
.reihe-nummer {
  display: flex; align-items: center; justify-content: center;
  width: 2rem; height: 2rem; flex: 0 0 auto;
  border-radius: 50%;
  background: var(--xxl-content);
  color: var(--xxl-accent);
  font-weight: 800;
}
.reihe-text { flex: 1 1 auto; min-width: 0; word-break: break-word; }
.reihe-knoepfe { display: flex; gap: 0.35rem; flex: 0 0 auto; }
/* Zwei Knöpfe statt Ziehen und Fallenlassen: auf einem Handy konkurriert das
   mit dem Scrollen der Seite. Groß genug für einen Daumen. */
.reihe-knopf {
  width: 2.6rem; height: 2.6rem;
  border: 2px solid var(--xxl-border);
  border-radius: 0.6rem;
  background: var(--xxl-content);
  color: var(--xxl-text);
  font-size: 1.2rem;
  touch-action: manipulation;
}
.reihe-knopf:disabled { opacity: 0.3; }
.reihe-knopf:active:not(:disabled) { transform: scale(0.94); }

/* --- Bühne: beide Formen --------------------------------------------------- */

.stage-tipp {
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: calc(0.5 * 1.25 * var(--bw));
  width: 100%;
  min-height: 0;
}
.stage-zahl-hinweis {
  color: var(--xxl-text-muted);
  font-size: clamp(calc(1 * 1.25 * var(--bw)), calc(2.4 * var(--bh)), calc(1.6 * 1.25 * var(--bw)));
}
/* Die gesuchte Zahl beim Auflösen — der Moment, auf den alle warten. */
.stage-zahl-loesung {
  color: var(--xxl-success);
  font-size: clamp(calc(2.5 * 1.25 * var(--bw)), calc(9 * var(--bh)), calc(6 * 1.25 * var(--bw)));
  font-weight: 800;
  line-height: 1.1;
}
.stage-reihe-zeile {
  display: flex; align-items: center; gap: calc(0.9 * 1.25 * var(--bw));
  width: min(calc(48 * 1.25 * var(--bw)), 90%);
  padding: calc(0.35 * 1.25 * var(--bw)) calc(0.9 * 1.25 * var(--bw));
  border: 2px solid var(--xxl-border);
  border-radius: calc(0.6 * 1.25 * var(--bw));
  background: var(--xxl-card);
}
.stage-reihe-zeile.ist-richtig { border-color: var(--xxl-success); }
.stage-reihe-nummer {
  color: var(--xxl-accent);
  font-weight: 800;
  min-width: calc(2 * 1.25 * var(--bw));
  /* ch statt vw: auf einem 1920er Schirm sind 2vw fast 40 px, und die Nummer
     stünde weiter vom Text entfernt als der Text breit ist. */
  font-size: clamp(calc(1.1 * 1.25 * var(--bw)), calc(2.6 * var(--bh)), calc(2 * 1.25 * var(--bw)));
}
.stage-reihe-text {
  color: var(--xxl-text);
  font-size: clamp(calc(1.1 * 1.25 * var(--bw)), calc(3 * var(--bh)), calc(2.4 * 1.25 * var(--bw)));
}

/* ===========================================================================
   Paare zuordnen (A.11.0.0)
   =========================================================================== */

/* --- Handy ---------------------------------------------------------------- */

.paar-feld {
  display: flex; flex-direction: column;
  gap: 0.5rem;
  width: 100%; max-width: 26rem;
}
.paar-zeile {
  display: flex; flex-direction: column; gap: 0.3rem;
  padding: 0.5rem 0.6rem;
  border: 2px solid var(--xxl-border);
  border-radius: 0.75rem;
  background: var(--xxl-card);
}
.paar-zeile.ist-gesperrt { opacity: 0.65; }
.paar-zeile.ist-richtig { border-color: var(--xxl-success); }
.paar-zeile.ist-falsch  { border-color: var(--xxl-danger); }
.paar-begriff {
  color: var(--xxl-text);
  font-weight: 700;
  word-break: break-word;
}
/* Ein Auswahlfeld statt Ziehen: auf einem Handy ist eine Linie zwischen zwei
   Spalten mit dem Daumen kaum zu treffen, und beim Ziehen scrollt die Seite
   mit. Mindesthöhe wie bei den anderen Bedienelementen. */
.paar-wahl {
  min-height: 2.9rem;
  padding: 0.4rem 0.6rem;
  border: 2px solid var(--xxl-border);
  border-radius: 0.6rem;
  background: var(--xxl-content);
  color: var(--xxl-text);
  font-size: 1rem;
  width: 100%;
}
.paar-wahl:disabled { opacity: 0.6; }

/* --- Bühne ---------------------------------------------------------------- */

.stage-paar-spalten {
  display: flex; gap: clamp(calc(1 * 1.25 * var(--bw)), calc(4 * var(--bw)), calc(4 * 1.25 * var(--bw)));
  justify-content: center; align-items: flex-start;
  width: 100%;
}
.stage-paar-spalte {
  display: flex; flex-direction: column; gap: calc(0.4 * 1.25 * var(--bw));
  min-width: 0;
}
.stage-paar-karte {
  padding: calc(0.3 * 1.25 * var(--bw)) calc(0.9 * 1.25 * var(--bw));
  border: 2px solid var(--xxl-border);
  border-radius: calc(0.6 * 1.25 * var(--bw));
  background: var(--xxl-card);
  color: var(--xxl-text);
  font-size: clamp(calc(1 * 1.25 * var(--bw)), calc(2.6 * var(--bh)), calc(2 * 1.25 * var(--bw)));
  white-space: nowrap;
}
.stage-paar-zeile {
  display: flex; align-items: center; gap: calc(0.8 * 1.25 * var(--bw));
  width: min(calc(52 * 1.25 * var(--bw)), 92%);
  padding: calc(0.3 * 1.25 * var(--bw)) calc(0.9 * 1.25 * var(--bw));
  border: 2px solid var(--xxl-success);
  border-radius: calc(0.6 * 1.25 * var(--bw));
  background: var(--xxl-card);
}
.stage-paar-links, .stage-paar-rechts {
  flex: 1 1 0; min-width: 0;
  color: var(--xxl-text);
  font-size: clamp(calc(1 * 1.25 * var(--bw)), calc(2.6 * var(--bh)), calc(2 * 1.25 * var(--bw)));
}
.stage-paar-rechts { text-align: right; }
.stage-paar-pfeil { color: var(--xxl-accent); flex: 0 0 auto; }

/* --- Emoji-Rätsel ---------------------------------------------------------
   Kein eigener Fragetyp und keine Einstellung: besteht der Fragetext NUR aus
   Emojis, wird er groß gesetzt. Eine Handvoll Bildzeichen in Fließtextgröße
   ist auf einem Beamer aus zehn Metern nicht zu erkennen — und einen Schalter
   dafür müsste man erst finden. */
.stage-question.ist-emoji {
  font-size: clamp(calc(3 * 1.25 * var(--bw)), calc(14 * var(--bh)), calc(9 * 1.25 * var(--bw)));
  line-height: 1.15;
  letter-spacing: 0.1em;
}

/* ===========================================================================
   Modifikatoren (A.12.0.0)
   =========================================================================== */

/* --- Editor: der Wett-Schalter -------------------------------------------- */
.quiz-wette {
  display: flex; align-items: flex-start; gap: 0.6rem;
  padding: 0.6rem 0.8rem;
  border: 2px solid var(--xxl-border);
  border-radius: 0.6rem;
  background: var(--xxl-content);
  color: var(--xxl-text-muted);
  font-size: 0.9rem;
  cursor: pointer;
}
.quiz-wette input { margin-top: 0.2rem; flex: 0 0 auto; }
.quiz-wette strong { color: var(--xxl-accent); }

/* --- Handy: der Einsatz ---------------------------------------------------- */
.wette-box {
  display: flex; flex-direction: column; gap: 0.4rem;
  width: 100%; max-width: 26rem;
  padding: 0.6rem 0.8rem;
  border: 2px solid var(--xxl-accent);
  border-radius: 0.75rem;
  background: var(--xxl-card);
}
.wette-kopf { color: var(--xxl-accent); font-weight: 800; }
.wette-regler { display: flex; align-items: center; gap: 0.6rem; }
.wette-regler input[type="range"] { flex: 1 1 auto; min-width: 0; accent-color: var(--xxl-accent); }
.wette-wert {
  min-width: 3.5rem; text-align: right;
  color: var(--xxl-text); font-size: 1.4rem; font-weight: 800;
}
.wette-box.ist-gesperrt { opacity: 0.65; }
/* Die Regel und die mitlaufende Rechnung (A.14.1.0). Der Preis eines Einsatzes
   muss beim Schieben zu sehen sein — ein Merksatz darüber wird auf einem Handy
   im Halbdunkel nicht gelesen. */
.wette-regel { color: var(--xxl-text-muted); font-size: 0.85rem; line-height: 1.35; }
.wette-folge { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.wette-fall {
  padding: 0.15rem 0.5rem; border-radius: 0.5rem;
  font-size: 0.9rem; font-weight: 700;
  background: rgba(255,255,255,0.06);
}
.wette-fall.ist-gut      { color: var(--xxl-success); }
.wette-fall.ist-schlecht { color: var(--xxl-danger); }
.wette-fall.ist-neutral  { color: var(--xxl-text-muted); font-weight: 600; }

/* --- Pult: der Ansagetext zur Wettfrage (A.14.1.0) ------------------------- */
.pult-wette {
  border-left: 3px solid var(--xxl-accent);
  padding: 0.5rem 0.75rem;
  background: rgba(255,255,255,0.04);
  border-radius: 0.5rem;
}
.pult-wette-kopf { color: var(--xxl-accent); font-weight: 800; font-size: 0.9rem; }
.pult-wette-ansage { color: var(--xxl-text); font-size: 0.95rem; line-height: 1.4; margin: 0.25rem 0; }
.pult-wette-stand { color: var(--xxl-text-muted); font-size: 0.85rem; }

/* --- Bühne: Joker, Duell, Ausgeschiedene ---------------------------------- */
.stage-joker {
  display: flex; flex-direction: column; gap: calc(0.35 * 1.25 * var(--bw));
  width: min(calc(40 * 1.25 * var(--bw)), 88%);
  margin-top: calc(0.4 * 1.25 * var(--bw));
}
.stage-joker-zeile { display: flex; align-items: center; gap: calc(0.6 * 1.25 * var(--bw)); }
.stage-joker-marke {
  color: var(--xxl-accent); font-weight: 800;
  min-width: calc(1.5 * 1.25 * var(--bw));
}
/* Der Balken zeigt VERHÄLTNISSE, nicht Namen: wer was getippt hat, steht
   nirgends im Datenstrom. */
.stage-joker-balken {
  flex: 1 1 auto; height: calc(1.1 * 1.25 * var(--bw));
  border-radius: calc(0.55 * 1.25 * var(--bw));
  background: var(--xxl-content);
  overflow: hidden;
}
.stage-joker-fuell {
  height: 100%;
  background: var(--xxl-accent2);
  transition: width 0.4s ease-out;
}
.stage-joker-zahl { color: var(--xxl-text-muted); min-width: calc(2 * 1.25 * var(--bw)); text-align: right; }

/* --- Bühne: der große Zwischenstand (A.14.2.0) -----------------------------
   Liegt ÜBER der Bühne, deckt sie aber vollständig ab: halbdurchsichtig wäre
   die Frage darunter noch zu ahnen, und aus zehn Metern ist das Gewühl statt
   eines Punktestands. Der Aufbau darunter bleibt trotzdem stehen — er wird
   nur verdeckt, nicht abgeräumt (siehe buehne-stand.js). */
.stage-stand {
  position: fixed; inset: 0; z-index: 60;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: clamp(calc(0.8 * 1.25 * var(--bw)), calc(2.5 * var(--bh)), calc(2 * 1.25 * var(--bw)));
  padding: clamp(calc(1 * 1.25 * var(--bw)), calc(3 * var(--bh)), calc(3 * 1.25 * var(--bw)));
  background: var(--xxl-bg);
  /* Der Beamer bekommt einen weichen Anlauf, damit der Wechsel nicht wie ein
     Bildfehler wirkt. */
  animation: stand-auf 0.35s ease-out;
}
@keyframes stand-auf {
  from { opacity: 0; transform: scale(0.98); }
  to   { opacity: 1; transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .stage-stand { animation: none; }
}
.stand-titel {
  color: var(--xxl-accent); font-weight: 800;
  font-size: clamp(1.6rem, 6vh, 4rem);
  letter-spacing: 0.02em;
}
.stand-liste {
  display: flex; flex-direction: column;
  gap: clamp(0.3rem, 1.2vh, 1rem);
  width: min(60rem, 92%);
  /* Die Bühne scrollt nicht: was nicht passt, muss kleiner werden — dafür
     sorgen die Klassen ist-voll/ist-klein am Kasten. */
  min-height: 0;
  list-style: none; margin: 0; padding: 0;
}
.stand-zeile {
  display: flex; align-items: center;
  gap: clamp(0.5rem, 1.5vw, 1.2rem);
  padding: clamp(0.25rem, 1vh, 0.8rem) clamp(0.6rem, 1.5vw, 1.2rem);
  border-radius: 0.75rem;
  background: var(--xxl-card);
  font-size: clamp(1rem, 3.4vh, 2.4rem);
}
/* Der Erste bekommt den Rahmen, nicht eine andere Schriftgröße: eine größere
   Zeile verschiebt alle anderen und lässt die Liste unruhig wirken. */
.stand-zeile.ist-erster {
  border: 2px solid var(--xxl-accent);
  background: rgba(var(--xxl-accent-rgb), 0.12);
}
.stand-zeile.ist-raus { opacity: 0.45; }
.stand-rang { flex: 0 0 auto; min-width: 2.2em; font-weight: 800; color: var(--xxl-accent); }
.stand-name {
  flex: 1 1 auto; min-width: 0;
  display: flex; align-items: center; gap: 0.6em;
  color: var(--xxl-text); font-weight: 700;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.stand-punkte {
  flex: 0 0 auto; min-width: 3em; text-align: right;
  color: var(--xxl-accent2); font-weight: 800;
}
.stand-leer { color: var(--xxl-text-muted); font-size: clamp(1rem, 2.5vh, 1.6rem); }
/* Viele Spieler: ZWEI SPALTEN statt kleinerer Schrift. Gemessen: vierzehn
   einspaltige Zeilen brauchen 938 px und hatten 720 — schuld ist nicht die
   Schrift, sondern der Avatar, der die Zeilenhöhe setzt. Ein 16:9-Schirm hat
   die Breite ohnehin übrig, und Schrumpfen macht die hinteren Plätze
   unleserlich, statt sie ins Bild zu holen.
   `columns` und nicht `grid`: es füllt SPALTENWEISE auf, die Rangfolge läuft
   also 1–7 links und 8–14 rechts. Ein Grid liefe zeilenweise (1,2 / 3,4) und
   damit quer durch die Rangliste. */
.stage-stand.ist-voll .stand-liste {
  display: block;
  columns: 2;
  column-gap: clamp(calc(0.6 * 1.25 * var(--bw)), calc(2 * var(--bw)), calc(1.6 * 1.25 * var(--bw)));
  width: min(calc(80 * 1.25 * var(--bw)), 96%);
}
.stage-stand.ist-voll .stand-zeile {
  font-size: clamp(calc(0.85 * 1.25 * var(--bw)), calc(2.4 * var(--bh)), calc(1.5 * 1.25 * var(--bw)));
  /* Eine Zeile darf nicht zwischen den Spalten zerrissen werden. */
  break-inside: avoid;
  margin-bottom: clamp(calc(0.25 * 1.25 * var(--bw)), calc(1 * var(--bh)), calc(0.7 * 1.25 * var(--bw)));
}
.stage-stand.ist-voll .stand-titel  { font-size: clamp(calc(1.3 * 1.25 * var(--bw)), calc(4.5 * var(--bh)), calc(2.8 * 1.25 * var(--bw))); }
.stage-stand.ist-klein .stand-zeile { font-size: clamp(calc(1.2 * 1.25 * var(--bw)), calc(5 * var(--bh)), calc(3.2 * 1.25 * var(--bw))); }

/* --- Bühne: die Wettregel (A.14.1.0) --------------------------------------
   Sie ist der einzige Ort, an dem ALLE im Raum dieselbe Erklärung sehen — das
   Handy erreicht nur den Einzelnen, das Pult nur den Spielleiter. Maße in vh,
   nicht in rem: die Bühne hängt an der Schirmhöhe und scrollt nicht. */
.stage-wette-regel {
  display: flex; flex-direction: column; align-items: center; gap: calc(0.2 * var(--bh));
  width: min(calc(52 * 1.25 * var(--bw)), 92%);
  padding: calc(0.5 * var(--bh)) calc(1 * 1.25 * var(--bw));
  border: 2px solid var(--xxl-accent);
  border-radius: calc(0.75 * 1.25 * var(--bw));
  background: rgba(255,255,255,0.04);
  text-align: center;
}
/* Kopf und Stand teilen sich eine Zeile — die Bühne scrollt nicht. */
.stage-wette-kopfzeile {
  display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: center; gap: calc(0.3 * 1.25 * var(--bw)) calc(0.9 * 1.25 * var(--bw));
}
.stage-wette-kopf {
  color: var(--xxl-accent); font-weight: 800;
  font-size: clamp(calc(1 * 1.25 * var(--bw)), calc(2.2 * var(--bh)), calc(1.6 * 1.25 * var(--bw)));
}
.stage-wette-zeilen {
  display: flex; flex-wrap: wrap; justify-content: center; gap: calc(0.4 * 1.25 * var(--bw)) calc(1.2 * 1.25 * var(--bw));
}
.stage-wette-fall {
  font-weight: 700;
  font-size: clamp(calc(0.85 * 1.25 * var(--bw)), calc(1.9 * var(--bh)), calc(1.3 * 1.25 * var(--bw)));
}
.stage-wette-fall.ist-gut      { color: var(--xxl-success); }
.stage-wette-fall.ist-schlecht { color: var(--xxl-danger); }
.stage-wette-fall.ist-neutral  { color: var(--xxl-text-muted); }
.stage-wette-stand {
  color: var(--xxl-text-muted);
  font-size: clamp(calc(0.8 * 1.25 * var(--bw)), calc(1.7 * var(--bh)), calc(1.1 * 1.25 * var(--bw)));
}
/* ⚠️ Der Erklärkasten braucht Höhe, und die Bühne SCROLLT NICHT (A.14.1.0).
   Gemessen auf 1366×768 an einer echten Frage aus Renas Quiz: ohne Kasten
   778 px Inhalt, mit Kasten 839 px — die unteren Reihen („Antworten", „Punkte")
   standen unter dem Rand. Frage und Optionen geben den Platz deshalb ab,
   solange eine Wettfrage läuft. Dasselbe Muster wie `.zeigt-loesung`, wo die
   Lösung ihren Platz vom Bild bekommt.
   Nur bei Wettfragen: an jeder anderen Frage ändert sich NICHTS.

   ⚠️⚠️⚠️ UND SIE GILT SEIT A.20.48.0 AUCH IM EDITOR (Befund 6, 06.09.). Bis
   A.20.47.2 setzte `buehne.js` die drei Zustandsklassen (`zeigt-wette`,
   `zeigt-hinweise`, `zeigt-loesung`) NUR an `#stage`; der Editor kannte sie
   nicht. Folge: eine Wettfrage stand im Editor mit voller Fragengrösse da und
   im Saal ~25 % kleiner — man gestaltete gegen eine Bühne, die es nicht gibt.
   Genau das ist sein Satz „trotz korrekter Einstellung im Editor sieht es im
   Spiel so aus".
   ▶ `:is(…)` fasst beide Träger und beide Textelemente zusammen. Die
     Spezifität bleibt dabei GLEICH: `:is()` zählt wie sein schwerstes
     Argument, hier je eine Klasse — die Reihenfolge gegenüber allen anderen
     Regeln ändert sich also nicht. Zwei getrennte Regeln wären zwei
     Gelegenheiten, nur eine nachzuziehen. */
:is(.stage-main, .quiz-vorschau-schirm).zeigt-wette :is(.stage-question, .quiz-fragevorschau-text) { --frage-grund: clamp(calc(1.4 * 1.25 * var(--bw)), calc(4 * var(--bw)), calc(3.6 * 1.25 * var(--bw))); }
:is(.stage-main, .quiz-vorschau-schirm).zeigt-wette .stage-options  { font-size: clamp(calc(1 * 1.25 * var(--bw)), calc(2.4 * var(--bw)), calc(2.1 * 1.25 * var(--bw))); }

/* ⚠️⚠️⚠️ HIER STANDEN ZWEI `@media (max-height: …)`-BLÖCKE — SIE SIND AM
   06.09.2026 GELÖSCHT (A.20.48.0, Befund 9), UND DAS IST KEINE AUFRÄUMARBEIT.

   Sie stammen aus A.14.1.0, als die Bühne noch das FENSTER war: „auf einem
   flachen Beamer wird es eng, dort setzen wir kompakter". Seit A.20.47.0
   stimmt diese Voraussetzung nicht mehr. Die Bühne ist ein FESTES Feld von
   1280×720, das als Ganzes skaliert wird (`--bw: 12.8px`, konstant) — die
   Medienabfrage fragt aber weiter das Browserfenster. Damit machte sie aus
   EINER Bühne zwei verschiedene:
     · auf 1600×900 oder am Beamer war sie nie aktiv — der Fall, für den sie
       gebaut war, erreichte sie also gar nicht mehr;
     · in einem 720-px-Fenster (der Prüfstand, ein halbierter Bildschirm) sass
       plötzlich eine andere Bühne da als im Saal.
   ▶ GEMESSEN (`befund-vorschau-buehne.js`, neue Lage „Buzzer mit 4 Hinweisen",
     06.09.): dieselbe Frage, dieselbe Bühne — die Fragenschrift stand bei
     einem 1280×720-Fenster auf **2,48 %** der Fläche, bei 1920×930 auf
     **2,33 %**. 6,4 % auseinander, allein aus der Fenstergrösse. Die
     Editor-Vorschau lag in beiden Fenstern auf 2,33 % und war damit die
     ehrlichere von beiden.
   ▶ Was sie leisten sollten, leistet seit A.20.47.0 die Zonen-Tabelle (jede
     Zone bekommt ihren Anteil) und `passeEin` (was dann noch nicht passt, wird
     gemessen verkleinert). Beides rechnet in BÜHNENMASSEN und gilt überall
     gleich. Eine dritte Geometrie daneben kann nur widersprechen.
   ⚠️ Wer hier wieder eine Medienabfrage einsetzen will: die Bühne kennt ihre
     eigene Grösse nicht über `@media`, sondern über `--bw`/`--bh`. Was vom
     Zustand abhängt, gehört an eine Klasse (`zeigt-wette`, `hinweise-eng`),
     was von der Frageform abhängt, in die Zonen-Tabelle. */

/* Was der Einsatz gebracht hat — die Regel am eigenen Fall. */
.stage-wette-delta {
  min-width: calc(3.5 * 1.25 * var(--bw)); text-align: right;
  font-weight: 800; font-size: clamp(calc(0.9 * 1.25 * var(--bw)), calc(2 * var(--bh)), calc(1.4 * 1.25 * var(--bw)));
}
.stage-wette-delta.ist-gut      { color: var(--xxl-success); }
.stage-wette-delta.ist-schlecht { color: var(--xxl-danger); }
.stage-wette-delta.ist-neutral  { color: var(--xxl-text-muted); }

.stage-duell {
  color: var(--xxl-warning);
  font-weight: 800;
  font-size: clamp(calc(1 * 1.25 * var(--bw)), calc(2.6 * var(--bh)), calc(1.8 * 1.25 * var(--bw)));
}
/* Wer raus ist, bleibt sichtbar — er ist noch im Raum und behält seine
   Punkte. Nur gedämpft, damit klar ist, dass er nicht mehr mitspielt. */
/* ⚠️ `.stage-scoreitem.ist-raus` ist am 06.09.2026 aus diesen zwei Zeilen
   herausgenommen (A.20.48.0, Befund 14): die alte Punkteliste gibt es seit
   A.20.47.0 nicht mehr, `buehne-listen.js` baut Kacheln. Für die Kachel steht
   die Regel schon da (`.stage-spieler.ist-raus`, weiter oben, samt
   Durchstreichung des Namens). `.stage-lobbyspieler.ist-raus` BLEIBT — den
   Wartesaal baut `buehne-wartesaal.js`, und er ist keine Kachelreihe. */
.stage-lobbyspieler.ist-raus { opacity: 0.4; }

/* --- Pult: die Modifikatoren ---------------------------------------------- */
.pult-modi {
  padding: 0.6rem 0.7rem;
  border: 1px solid var(--xxl-border-light);
  border-radius: 0.6rem;
  background: var(--xxl-content);
}
.pult-spielerzeilen { display: flex; flex-direction: column; gap: 0.3rem; }
.pult-spielerzeile {
  display: flex; align-items: center; gap: 0.4rem;
  padding: 0.2rem 0.4rem;
  border-radius: 0.4rem;
  background: var(--xxl-card);
  color: var(--xxl-text);
}
.pult-spielerzeile.ist-raus { opacity: 0.5; }
.pult-spielerzeile.ist-raus > span:first-child { text-decoration: line-through; }

/* ===========================================================================
   Der Admin-Bereich auf dem Handy (A.12.1.0)
   ===========================================================================

   Gemessen, nicht geschätzt: `npm run test:mobil` geht den Bedienweg auf einem
   390×844-Gerät ab und prüft drei Dinge — läuft etwas seitlich heraus, ist
   jedes Bedienelement mindestens 40 px hoch, steht irgendwo Text unter 13 px.

   Die Wandprobe war von Anfang an grün: die Verwaltung lief nirgends aus dem
   Schirm. Rot waren die Maße — alle Knöpfe 37 px, etliche Texte bei 11 bis
   12,8 px.

   ⚠️ ALLES HIER STEHT IN `pointer: coarse` UND ÄNDERT DEN DESKTOP NICHT.
   Am Schreibtisch ist ein 37-px-Knopf mit der Maus bequem zu treffen und eine
   kompakte Liste ein Vorteil; erst der Daumen braucht mehr. Eine Umstellung
   für alle hätte die Verwaltung am großen Schirm aufgebläht, um ein Problem zu
   lösen, das es dort nicht gibt. `coarse` trifft Touchscreens und nicht die
   Fenstergröße — ein schmal gezogenes Desktop-Fenster bleibt also, wie es ist,
   und ein Tablet in voller Breite bekommt die großen Ziele trotzdem. */

@media (pointer: coarse) {

  /* Daumenmaß: 44 px sind die verbreitete Untergrenze, unter der man auf einem
     Handy danebentrifft. Über `min-height`, damit die vorhandenen Abstände
     erhalten bleiben und nur zu flache Knöpfe wachsen. */
  .xxl-btn-accent,
  .xxl-btn-ghost,
  .xxl-btn-danger,
  .xxl-input,
  select,
  textarea.xxl-input {
    min-height: 44px;
  }

  /* ⚠️ Ein Schieberegler ist von Haus aus 16 px hoch und trägt KEIN
     `.xxl-input` — die Regel darüber greift also nicht, und `mobil` hat ihn
     jahrelang als einziges Bedienelement unter dem Daumenmaß gemeldet.
     Die 44 px sind reine TREFFERFLÄCHE: Schiene und Griff bleiben, wo sie
     sind, sie stehen nur mittig in einem höheren Kasten.
     ⚠️ Diese Regel greift über den TYP, nicht über eine Klasse — und genau
     deshalb gilt sie seit A.20.26.0 auch für die Lautstärke-Regler an Medien
     und Intros, ohne dass dort etwas nachgetragen werden musste. Wer je auf
     eine Klasse umstellt, muss alle Regler einzeln nachziehen. */
  input[type="range"] {
    min-height: 44px;
  }

  /* Ein Eingabefeld unter 16 px lässt iOS beim Antippen in die Seite zoomen —
     danach steht die halbe Verwaltung außerhalb des Schirms, und man muss von
     Hand zurückwischen. */
  .xxl-input, select, textarea.xxl-input { font-size: 16px; }

  /* Lesbarkeit: nichts unter 13 px. Die betroffenen Stellen sind Hinweise und
     Schildchen — sie dürfen kleiner sein als der Fließtext, aber nicht so
     klein, dass man sie auf einem Handy überliest. */
  .quiz-typmarke,
  .quiz-medienplatz-hinweis,
  .quiz-marke-hinweis,
  .quiz-mediendatei,
  /* ⚠️ Die beiden Marken aus A.20.7.0 gehören hier dazu — 0,72 rem sind
     11,5 px, und `mobil` hat sie beim ersten Lauf sofort gemeldet. Sie sind
     die ÜBERSCHRIFTEN der Stationen und Gruppen: ausgerechnet die darf man am
     Handy nicht überlesen. */
  .quiz-stationsmarke,
  .quiz-gruppenkopf,
  /* Der Satz neben den Schriftwerkzeugen (A.20.8.0) — 0,8 rem sind 12,8 px,
     und `mobil` hat ihn beim ersten Lauf gemeldet. Er erklärt, was die Knöpfe
     tun; wer ihn nicht lesen kann, drückt sie blind. */
  .quiz-schriftwerkzeug-hinweis,
  /* Die Marken der beiden Werkzeuggruppen (A.20.9.0) — „ZEILE" und „WORT".
     0,68 rem sind 10,88 px, und `mobil` hat sie beim ersten Lauf gemeldet.
     Sie sind der einzige Hinweis darauf, dass die zehn Knöpfe zwei
     verschiedene Dinge tun; wer sie nicht lesen kann, sucht bei jedem Knopf
     neu. */
  .quiz-werkzeugmarke,
  .text-xs {
    font-size: 0.8125rem;   /* 13 px */
  }

  /* Der Haken an einer Antwortmöglichkeit ist das kleinste Ziel im ganzen
     Editor — und das folgenreichste: er entscheidet, was richtig ist. */
  .quiz-option-haken {
    min-height: 44px;
    min-width: 44px;
  }

  /* „A−" ist mit 0,72 rem = 11,5 px unter die Grenze gerutscht (A.20.10.0).
     Der Knopf bleibt kleiner als „A+" — das ist ja seine Aussage —, aber
     lesbar muss er sein. ⚠️ Mit Elternteil geschrieben: die Grundregel steht
     ebenfalls als `.quiz-schriftwerkzeug .quiz-werkzeug-kleiner`, und eine
     Regel mit weniger Klassen käme hier nicht durch. */
  .quiz-schriftwerkzeug .quiz-werkzeug-kleiner {
    font-size: 0.8125rem;
  }

  /* ⚠️ DIE FARBPUNKTE (A.20.10.0): 44 px TREFFERFLÄCHE, aber der Punkt bleibt
     klein. Derselbe Kniff wie beim Schieberegler in A.20.7.1 — ein 44 px
     großer Farbkreis wäre auf 390 px ein Klecks, und neun davon nebeneinander
     füllten die halbe Zeile. `background-clip: content-box` lässt die Farbe
     nur im Inneren stehen; das Polster ringsum ist unsichtbar und trotzdem
     zu treffen.
     ⚠️ Die Farbe selbst kommt aus einem `:style` am Element — deshalb darf
     hier KEIN `background` gesetzt werden, nur der Zuschnitt. */
  .quiz-farbpunkt {
    width: 44px;
    height: 44px;
    padding: 13px;
    background-clip: content-box;
    border-width: 0;
    box-shadow: none;
  }
  .quiz-farbpunkt:hover,
  .quiz-farbpunkt:focus-visible {
    outline: 2px solid var(--xxl-text);
    outline-offset: -13px;
  }
  .quiz-farbwahl {
    width: 44px;
    height: 44px;
    padding: 11px;
  }

  /* Ein Kästchen bleibt ein Kästchen; 44 px wären ein Kasten. 24 px sind mit
     dem Daumen zu treffen, und der eigentliche Klickbereich ist ohnehin das
     Label drumherum — das ist so hoch wie sein Text. */
  input[type="checkbox"] {
    width: 24px;
    height: 24px;
  }

  /* Die Adressen von Bühne und Pult sind LINKS, die man auf dem Handy antippt,
     um sie zu öffnen oder zu teilen. Als 22-px-Textzeile trifft man daneben. */
  .pult-stagelink {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }

  /* Die beiden Zeilen mit ▲▼ am Runden- und Fragenkopf stehen sonst als
     schmale Streifen nebeneinander und sind mit dem Daumen kaum einzeln zu
     treffen. */
  .quiz-question .xxl-btn-ghost,
  .quiz-round-head .xxl-btn-ghost {
    min-width: 44px;
  }
}

/* Auf schmalen Schirmen bekommt jede Antwortmöglichkeit ihre eigene Zeile.
   Nebeneinander bleiben von 390 px nach Haken, Buchstabe, ↑↓ und ✕ noch rund
   120 px für den Text übrig — man tippt dann in ein Feld, in dem drei Wörter
   Platz haben, und sieht beim Schreiben nie den Anfang.

   An der BREITE festgemacht und nicht am Zeigergerät: ein schmal gezogenes
   Fenster am Schreibtisch hat dasselbe Platzproblem. */
/* Breit genug: die Zeile bleibt eine Zeile. */
.quiz-option-eingabe { flex: 1 1 auto; min-width: 8rem; }

@media (max-width: 520px) {
  .quiz-option-zeile { flex-wrap: wrap; }
  /* Erst die Steuerung in einer Reihe — Buchstabe, Haken, Pfeile, Entfernen —,
     dann das Textfeld darunter über die ganze Breite. */
  .quiz-option-zeile .quiz-option-marke { order: 0; }
  .quiz-option-zeile .quiz-option-haken { order: 1; }
  .quiz-option-zeile > button:not(.quiz-option-haken) { order: 2; }
  .quiz-option-zeile .quiz-option-eingabe {
    order: 9;
    flex: 1 1 100%;
    min-width: 100%;
  }
}

/* --- Hinweise, nach und nach (A.15.0.0) ------------------------------------
   Stefans Befund vom 17.08.: „Wen suchen wir?" und dann alle paar Sekunden ein
   Hinweis mehr. Die gefallenen bleiben stehen — nur zusammen ergeben sie das
   Rätsel. */

/* Auf der Bühne. Linksbündig und untereinander, nicht zentriert: man liest sie
   als Liste, und eine zentrierte Liste mit unterschiedlich langen Zeilen hat
   keine Kante, an der das Auge zurückspringt. */
.stage-hinweise {
  display: flex; flex-direction: column;
  gap: clamp(calc(0.25 * 1.25 * var(--bw)), calc(0.9 * var(--bh)), calc(0.6 * 1.25 * var(--bw)));
  align-items: flex-start;
  text-align: left;
  width: 100%;
  /* ⚠️ NICHT in `ch`. Eine Breite in `ch` wandert mit dem Schriftgrad mit:
     beim Einpassen wird der Text kleiner UND die Spalte schmaler, die Zahl der
     Umbrüche bleibt gleich, und das Verkleinern bringt fast nichts (gemessen:
     919 → 856 px, gebraucht wurden 720). In `rem`/`vw` bleibt die Spalte
     stehen, es passt mehr pro Zeile, und die Zeilenzahl fällt wirklich. */
  max-width: min(calc(58 * 1.25 * var(--bw)), calc(72 * var(--bw)));
  margin: 0 auto;
}
.stage-hinweis {
  display: flex; align-items: baseline; gap: calc(0.6 * 1.25 * var(--bw));
  color: var(--xxl-text);
  /* ⚠️ `--hinweis-skala` setzt buehne-hinweise.js beim EINPASSEN. Acht lange
     Hinweise ergaben ungebremst 919 px auf einer 720-px-Bühne. Die Höhe macht
     nicht die Anzahl, sondern die Textlänge — die kann kein fester Schriftgrad
     vorhersehen, deshalb wird gemessen und verkleinert, bis es passt. */
  /* So groß wie die Antwortmöglichkeiten (`.stage-options`, 2.4vw): die
     Hinweise SIND der Inhalt dieser Frageform. Zuerst standen sie bei 15,8 px
     gegen 30 px bei den Optionen — auf einem Beamer quer durch den Raum ist das
     der Unterschied zwischen lesbar und nicht. Dass acht lange dann nicht mehr
     von allein passen, fängt das Einpassen ab. */
  font-size: calc(clamp(calc(1.05 * 1.25 * var(--bw)), calc(2.2 * var(--bw)), calc(1.9 * 1.25 * var(--bw))) * var(--hinweis-skala, 1));
  line-height: 1.25;
}
.stage-hinweis-marke {
  color: var(--xxl-accent);
  font-weight: 800;
  flex: 0 0 auto;
  min-width: 1.4em;
}
/* Der neue Hinweis blendet einmal auf. Kurz und ohne Bewegung der Zeile: ein
   Hereinschieben verrückte alle darüberstehenden, und wer gerade liest,
   verlöre die Stelle. */
.stage-hinweis.ist-neu { animation: hinweis-auf 420ms ease-out; }
@keyframes hinweis-auf {
  from { opacity: 0; }
  to   { opacity: 1; }
}
/* ⚠️ Der Platzhalter für die noch ausstehenden Hinweise hat RESERVIERTEN Platz.
   Ohne ihn springt die ganze Tafel bei jedem Hinweis um eine Zeilenhöhe nach
   oben — und das mitten im Lesen. */
.stage-hinweis.ist-offen {
  color: var(--xxl-text-dim);
  font-style: italic;
  font-size: clamp(calc(0.85 * 1.25 * var(--bw)), calc(2 * var(--bh)), calc(1.4 * 1.25 * var(--bw)));
}
/* Was die Frage GERADE wert ist. Nur bei eingeschaltetem Abschlag im DOM. */
.stage-hinweis-wert {
  margin: calc(0.4 * 1.25 * var(--bw)) auto 0;
  font-weight: 800;
  font-size: clamp(calc(1 * 1.25 * var(--bw)), calc(2.4 * var(--bh)), calc(1.7 * 1.25 * var(--bw)));
  color: var(--xxl-text-muted);
}
.stage-hinweis-wert.ist-gefallen { color: var(--xxl-warning); }

/* ⚠️ DIE BÜHNE SCROLLT NICHT, und acht Hinweise sind echte Höhe. Dasselbe
   Muster wie `.zeigt-wette` (A.14.1.0) und `.zeigt-loesung`: Frage und Medium
   geben den Platz ab, solange Hinweise stehen. Nur dann — an jeder anderen
   Frage ändert sich nichts.
   Gemessen wird bei 1280×720; auf 1600×900 zeigt sich der Fehler nie.
   ⚠️ Auch diese Regel gilt seit A.20.48.0 für BEIDE (Befund 6) — die
   Begründung steht oben bei `.zeigt-wette`. */
:is(.stage-main, .quiz-vorschau-schirm).zeigt-hinweise :is(.stage-question, .quiz-fragevorschau-text) { --frage-grund: clamp(calc(1.3 * 1.25 * var(--bw)), calc(3.4 * var(--bw)), calc(3 * 1.25 * var(--bw))); }
/* ⚠️⚠️⚠️ AUCH HIER STANDEN ZWEI `@media (max-height: …)`-BLÖCKE — gelöscht am
   06.09.2026 (A.20.48.0, Befund 9), aus genau dem Grund, der oben bei
   `.zeigt-wette` ausgeschrieben steht: sie fragen das FENSTER, die Bühne ist
   aber ein festes Feld. An dieser Lage ist der Unterschied auch gemessen
   worden (2,48 % gegen 2,33 % der Fläche, allein aus der Fenstergrösse).
   ⚠️ Die HINWEIS-SKALA ist damit NICHT weg: `--hinweis-skala` steht weiterhin
     an `.stage-hinweis` (siehe oben bei `.stage-hinweise`), und
     `buehne-hinweise.js` schreibt sie — der Einpasser ist der richtige Ort für
     „es passt nicht", nicht die Schirmhöhe. */

/* Ab der Mitte des Einpassens gibt auch die FRAGE Platz ab — vorher nicht: sie
   ist der Anker, auf den der Saal schaut, und sie soll so lange wie möglich
   groß bleiben. Gesetzt wird die Klasse von buehne-hinweise.js, nicht per
   Medienabfrage: es hängt an der Textlänge, nicht an der Schirmhöhe. */
/* ⚠️ Für BEIDE (Befund 6): die Klassen setzt `buehneHinweise.passeEin`, und
   das läuft seit A.20.48.0 auch über die Editor-Vorschau. */
:is(.stage-main, .quiz-vorschau-schirm).hinweise-eng :is(.stage-question, .quiz-fragevorschau-text) { --frage-grund: clamp(calc(1.1 * 1.25 * var(--bw)), calc(2.4 * var(--bw)), calc(2 * 1.25 * var(--bw))); }
:is(.stage-main, .quiz-vorschau-schirm).hinweise-eng .stage-hinweise { gap: calc(0.1 * 1.25 * var(--bw)); }
:is(.stage-main, .quiz-vorschau-schirm).hinweise-eng .stage-hinweis  { line-height: 1.15; }

/* ⚠️ DIE ZWEITE STUFE DER FRAGE (A.16.0.0). Bei einer Tipp-Frage mit vielen
   Hinweisen ist die Frage der größte einzelne Block, der nichts mehr tut: sie
   wird einmal gelesen und steht dann nur noch da. Gibt sie hier nicht deutlich
   ab, muss der Platz von den Hinweisen kommen — und die sind bei dieser
   Frageform gerade das, was gelesen werden soll. Im ersten Anlauf war es genau
   andersherum, und die Hinweise standen als kleinstes auf der Bühne. */
:is(.stage-main, .quiz-vorschau-schirm).hinweise-sehr-eng :is(.stage-question, .quiz-fragevorschau-text) {
  --frage-grund: clamp(calc(0.95 * 1.25 * var(--bw)), calc(1.8 * var(--bw)), calc(1.45 * 1.25 * var(--bw)));
  line-height: 1.15;
}
/* ⚠️ Der Wrap auf der Bühne und die Fläche im Editor sind dasselbe Rechteck —
   sie stehen im ganzen Stylesheet paarweise (siehe `.stage-question-wrap,
   .quiz-vorschau-flaeche` weiter unten). */
:is(.stage-main, .quiz-vorschau-schirm).hinweise-sehr-eng :is(.stage-question-wrap, .quiz-vorschau-flaeche) { gap: calc(0.25 * 1.25 * var(--bw)); }

/* ⚠️⚠️ SEIT A.16.0.0 STEHEN UNTER DEN HINWEISEN AUCH OPTIONEN UND KARTEN.
   Bis A.15.0.0 gab es Hinweise nur bei der Buzzerfrage, und dort steht unter
   ihnen nichts als die Buzzer-Zeile. Bei einer Wahlfrage kommen bis zu sechs
   Optionen dazu — das ist mehr Höhe als die Hinweise selbst. Frage und Hinweise
   allein kleiner zu machen holt das nicht herein: gemessen lief der engste Fall
   mit 831 px über die 720-px-Bühne, und das Einpassen stand schon am Anschlag.

   ⚠️⚠️ WARUM NICHT EINFACH DIE OPTIONEN KLEINER: `.stage-options` hat sein
   Zeilenmaß in `ch` (46 Zeichen, siehe oben — das ist Absicht und soll so
   bleiben). `ch` rechnet gegen die Schrift DESSELBEN Elements. Wer dort die
   Schrift verkleinert, verkleinert die Breite im selben Verhältnis mit: die
   Umbrüche bleiben Zeichen für Zeichen dieselben, die Zeilenzahl fällt nicht,
   und es wird nur alles kleiner UND schmaler. Genau diese Falle hat beim Bau
   von A.15.0.0 schon einmal 199 px gekostet.

   Die Lösung: der BEHÄLTER behält seine volle Breite und damit sein Zeilenmaß,
   verkleinert wird nur der Text DARIN. Dann brechen die Zeilen wirklich um, und
   die Höhe fällt tatsächlich. */
:is(.stage-main, .quiz-vorschau-schirm).zeigt-hinweise .stage-option {
  font-size: calc(1em * var(--tipp-skala, 1));
  padding: calc(clamp(calc(0.35 * 1.25 * var(--bw)), calc(1 * var(--bh)), calc(0.7 * 1.25 * var(--bw))) * var(--tipp-skala, 1))
           clamp(0.6rem, 1.4vw, 1.1rem);
}
:is(.stage-main, .quiz-vorschau-schirm).zeigt-hinweise .stage-options {
  gap: calc(clamp(calc(0.4 * 1.25 * var(--bw)), calc(1.2 * var(--bh)), calc(0.9 * 1.25 * var(--bw))) * var(--tipp-skala, 1));
}

/* Dasselbe für Ordnen und Paare. ⚠️ Die Klassennamen sind NACHGESEHEN, nicht
   geraten: `stage-reihe-zeile`, `stage-paar-karte`, `stage-paar-zeile`. Ein
   erfundener Name fällt hier nicht auf — die Regel greift schlicht nicht, das
   Einpassen schrumpft statt dessen die Hinweise bis an den Anschlag, und
   niemand sieht, warum es nicht reicht. */
:is(.stage-main, .quiz-vorschau-schirm).zeigt-hinweise :is(.stage-reihe-zeile, .stage-paar-karte, .stage-paar-zeile) {
  font-size: calc(1em * var(--tipp-skala, 1));
}
:is(.stage-main, .quiz-vorschau-schirm).zeigt-hinweise .stage-tipp {
  gap: calc(clamp(calc(0.4 * 1.25 * var(--bw)), calc(1.2 * var(--bh)), calc(0.9 * 1.25 * var(--bw))) * var(--tipp-skala, 1));
}

/* Am Pult — beide Pulte benutzen dieselben Klassen. Die schon gefallenen
   Hinweise stehen hell, die kommenden gedämpft: der Spielleiter muss auf einen
   Blick sehen, was der Raum schon weiß und was er noch in der Hand hat. */
.pult-hinweise {
  border: 1px solid var(--xxl-border);
  border-radius: 0.5rem;
  padding: 0.6rem 0.7rem;
}
.pult-hinweis {
  display: flex; align-items: baseline; gap: 0.6rem;
  padding: 0.3rem 0.1rem;
  color: var(--xxl-text-dim);
}
.pult-hinweis.ist-draussen {
  color: var(--xxl-text);
  font-weight: 600;
}
.pult-hinweis.ist-draussen .pult-option-marke { color: var(--xxl-accent); }

/* Im Editor: derselbe Zeilenbau wie bei den Antwortmöglichkeiten — es sind
   dieselben Handgriffe (tippen, schieben, entfernen), und zwei verschiedene
   Bauformen für dieselbe Bedienung wären nur Lernaufwand. */
/* Der Hinweisblock ist seit A.20.7.0 eine `.quiz-feldgruppe` wie „Die Frage"
   und „Punkte und Zeit" — er hatte als einziger Block schon eine Kante, und
   genau die ist jetzt die Regel für alle. Die eigene `.quiz-hinweise`-Regel
   ist damit weg: zwei Fassungen derselben Kante wären zwei Orte für eine
   Formel. */

/* --- Wasserzeichen und Introvideo (A.18.0.0) --------------------------------
   Stefans Bestellung: „ein Introvideo für das Quiz einspielen und eine Art
   Wasserzeichen-Hintergrundbild definieren". Beides gilt NUR auf der Bühne —
   auf dem Handy läge das Wasserzeichen hinter dem Buzzer, genau dort, wo im
   Halbdunkel getroffen werden muss. */

/* Die Wasserzeichen-Schicht liegt HINTER allem, was im Fluss steht, aber vor
   dem Seitenhintergrund. `fixed` statt `absolute`: die Bühne scrollt nicht
   (body: overflow hidden), und ein mitwanderndes Hintergrundbild wäre bei
   einem Beamer ohnehin sinnlos.
   Die Deckkraft setzt buehne-wasserzeichen.js aus dem Wert am Quiz. */
/* ⚠️ `z-index: 0`, NICHT -1. Der erste Anlauf hatte -1 (dasselbe Muster wie
   beim Seitenhintergrund weiter oben) — und das Wasserzeichen war auf dem
   Beamer schlicht NICHT DA, obwohl das Element sichtbar war, das Bild mit
   HTTP 200 lud und die Deckkraft stimmte. Der Prüfpunkt war grün, der
   Screenshot hat es gefunden.
   Grund: `html` UND `.stage-body` tragen beide `var(--xxl-bg)`. Weil das
   Wurzelelement einen eigenen Hintergrund hat, wird der des body als ganz
   normaler Elementhintergrund gemalt — und der liegt über allem mit negativem
   z-index. Die Schicht lag also hinter einer deckenden Fläche.
   Deshalb: Schicht auf 0, und die beiden Ansichten darüber auf 1. */
.stage-wasserzeichen {
  position: fixed;
  inset: 0;
  z-index: 0;
  background-position: center;
  background-repeat: no-repeat;
  /* `cover` statt `contain`: ein Wasserzeichen soll die Fläche tragen, nicht
     mit Rändern darin schweben. Was seitlich abgeschnitten wird, ist bei einem
     Hintergrund der richtige Verlust. */
  background-size: cover;
  pointer-events: none;
}

/* Die beiden Ansichten liegen ÜBER dem Wasserzeichen. Ohne diese zwei Zeilen
   verdeckt es die Frage, statt hinter ihr zu liegen. Alles Weitere auf der
   Bühne (Zwischenstand, Intro, QR-Ecke, Tonfreigabe) ist ohnehin positioniert
   und liegt darüber. */
.stage-lobby,
.stage-main {
  position: relative;
  z-index: 1;
}

/* Das Intro liegt über ALLEM — auch über dem großen Zwischenstand (z-index 60)
   —, aber unter der Tonfreigabe: wer die Bühne gerade erst öffnet, muss zuerst
   starten können, sonst liefe das Intro stumm. */
/* Der ÜBERGABE-SCHIRM (A.20.0.0) — seine Bestellung vom 24.08.
   z-index 70: über der Frage und über dem Zwischenstand (60), aber UNTER dem
   Intro (80). Läuft noch ein Video, hat es Vorrang; der Schirm wartet darunter.
   Der Hintergrund ist SEIN Bühnenbild (public/uploads/buehne-web.jpg) — es hat
   einen Lichtkegel in der Mitte und ruhige, dunkle Flächen an den Rändern.
   Genau dort steht das Logo. */
.stage-uebergabe {
  position: fixed; inset: 0; z-index: 70;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: clamp(calc(0.6 * 1.25 * var(--bw)), calc(2 * var(--bh)), calc(1.6 * 1.25 * var(--bw)));
  background: var(--xxl-bg) var(--xxl-buehne-bild) center/cover no-repeat;
  animation: stand-auf 0.35s ease-out;
}
.stage-uebergabe-logo {
  /* ⚠️ Höhe UND Breite deckeln: ein hochkantes Logo würde sonst über den
     Lichtkegel hinauswachsen, ein breites bis an die Bokeh-Ränder laufen. */
  max-width: min(calc(46 * var(--bw)), 720px);
  max-height: calc(46 * var(--bh));
  width: auto; height: auto;
  object-fit: contain;
  /* Der Schein hebt das Logo aus dem Nebel heraus, ohne einen Kasten darum zu
     ziehen — ein Rahmen sähe auf dem Bühnenbild wie ein Aufkleber aus. */
  filter: drop-shadow(0 0 calc(2.5 * 1.25 * var(--bw)) rgba(var(--xxl-accent-rgb), 0.55))
          drop-shadow(0 1.2rem 2rem rgba(0, 0, 0, 0.65));
}
.stage-uebergabe-titel {
  font-size: clamp(calc(1.4 * 1.25 * var(--bw)), calc(4.2 * var(--bh)), calc(3 * 1.25 * var(--bw)));
  font-weight: 800;
  letter-spacing: 0.01em;
  color: var(--xxl-text);
  text-align: center;
  max-width: calc(80 * var(--bw));
  text-shadow: 0 calc(0.15 * 1.25 * var(--bw)) calc(1.2 * 1.25 * var(--bw)) rgba(0, 0, 0, 0.8);
}
/* Ohne Logo trägt der Titel den Schirm allein — dann darf er größer sein. */
.stage-uebergabe.ist-ohne-logo .stage-uebergabe-titel {
  font-size: clamp(calc(2 * 1.25 * var(--bw)), calc(8 * var(--bh)), calc(5.5 * 1.25 * var(--bw)));
}

.stage-intro {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: flex;
  align-items: center;
  justify-content: center;
  /* ⚠️ KEIN schwarzer Kasten mehr (A.20.0.0, sein Befund): ein Video im
     Seitenverhältnis der Bühne füllt sie ohnehin, und bei jedem anderen sah man
     bisher breite schwarze Balken. Jetzt scheint SEIN Bühnenbild durch — der
     Rand sieht dann aus wie ein Saal, nicht wie ein Fehler.
     Ein leichter Schleier bleibt, damit ein helles Video nicht in den
     Hintergrund ausfranst. */
  background: rgba(0, 0, 0, 0.45);
}

/* Das Video füllt den Schirm, ohne beschnitten zu werden. `contain` ist hier
   das Gegenteil der Entscheidung beim Wasserzeichen und aus demselben Grund
   richtig: ein Intro ist gestaltet, und was am Rand abgeschnitten würde, hat
   jemand dorthin gesetzt. */
/* ⚠️ Das Video FÜLLT den Schirm, es wird nicht in seiner eigenen Auflösung
   stehen gelassen. Der erste Anlauf hatte `width/height: auto` mit
   `max-…: 100%` — ein 320er Prüfvideo stand damit als Briefmarke mitten im
   schwarzen Bild, und ein echtes 720p-Intro sähe auf einem 4K-Beamer genauso
   verloren aus. `contain` hält dabei das Seitenverhältnis: bei einem Intro ist
   gestaltet, was am Rand stünde, und darf nicht abgeschnitten werden. */
.stage-intro-video {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

/* Intro und Wasserzeichen im Editor (A.18.0.0). Eingeklappt, solange nichts
   hinterlegt ist: sonst trüge jede Runde und jede Frage zwei leere Kacheln
   mehr, und der Editor wird bei dreißig Fragen unlesbar. */
.quiz-rundenmedien {
  border: 1px solid var(--xxl-border);
  border-radius: 10px;
  padding: 0.5rem 0.75rem;
  background: rgba(255, 255, 255, 0.02);
}
/* ⚠️ Das Dreieck wird SELBST gesetzt. `display: flex` auf einem <summary>
   entfernt den eingebauten Marker — die Zeile sah aus wie eine Beschriftung
   und nicht wie etwas, das man aufklappen kann. Gesehen hat es der Screenshot,
   kein Check: ein Klickweg, den niemand als solchen erkennt, ist keiner. */
.quiz-rundenmedien-kopf {
  cursor: pointer;
  font-size: 0.9rem;
  color: var(--xxl-text-dim);
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  flex-wrap: wrap;
  list-style: none;
}
.quiz-rundenmedien-kopf::-webkit-details-marker { display: none; }
.quiz-rundenmedien-kopf::before {
  content: '▸';
  color: var(--xxl-accent);
  /* Der Pfeil dreht sich beim Aufklappen. Ohne feste Breite rückt die
     Beschriftung dabei um ein paar Pixel — das fällt genau dann auf, wenn man
     mehrmals hintereinander klappt. */
  display: inline-block;
  width: 0.9rem;
  transition: transform 0.15s ease-out;
}
.quiz-rundenmedien[open] > .quiz-rundenmedien-kopf::before {
  transform: rotate(90deg);
}

/* Der Deckkraft-Regler. Die Zahl daneben ist breit genug für „100 %"
   festgelegt — sonst wandert der Hinweistext beim Schieben mit. */
.quiz-deckkraft {
  flex: 1 1 12rem;
  max-width: 22rem;
  accent-color: var(--xxl-accent);
}
.quiz-deckkraft-wert {
  min-width: 3.5rem;
  font-variant-numeric: tabular-nums;
  color: var(--xxl-text);
  font-weight: 700;
}

/* Loop und Lautstärke an einem Medium (A.20.26.0). Sein Befund vom 26.08.:
   „Bei eingefügten Videos kann ich derzeit nicht beeinflussen ob die im Loop
   laufen sollen, oder wie laut die laufen sollen."

   Eigene Klassen statt `.quiz-deckkraft` mitzubenutzen: die Deckkraft ist EIN
   Regler am Quizkopf, diese hier stehen an jedem Medium und an drei
   Intro-Plätzen. Ein gemeinsamer Name hätte beim nächsten Griff an die
   Deckkraft still alle Lautstärke-Regler mitverändert.

   ⚠️ Die 44 px Trefferfläche kommen aus der Regel über `input[type="range"]`
   weiter oben und sind hier NICHT wiederholt — sie stünden sonst an zwei
   Stellen und liefen auseinander. */
.quiz-ton {
  border-top: 1px solid var(--xxl-border);
  padding-top: 0.5rem;
}
.quiz-ton-label {
  font-size: 0.875rem;
  color: var(--xxl-text-dim);
}
.quiz-tonregler {
  flex: 1 1 10rem;
  max-width: 18rem;
  accent-color: var(--xxl-accent);
}
.quiz-tonwert {
  min-width: 4rem;
  font-variant-numeric: tabular-nums;
  color: var(--xxl-text);
  font-weight: 700;
}

/* „▶ Quiz-Intro läuft" am Pult (A.18.0.0). Keine Schaltfläche, sondern eine
   Zustandsmarke — sie soll sich vom Weiter-Knopf daneben deutlich
   unterscheiden, damit im Halbdunkel niemand danach greift. */
.pult-intro-marke {
  display: inline-flex;
  align-items: center;
  padding: 0.35rem 0.7rem;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid var(--xxl-border);
  color: var(--xxl-accent);
  font-weight: 700;
  font-size: 0.9rem;
}

/* ==== DIE ENDKARTE (A.20.33.0) ==============================================
   Seine Ansage vom 26.08.: „eine Ansprechende Endkarte wäre super."

   ⚠️ SIE IST DAS GEGENSTÜCK ZUM ÜBERGABE-SCHIRM und benutzt bewusst dieselbe
   Fläche: sein Bühnenbild, sein Logo. Der Abend steht dann zwischen zwei
   Bildern derselben Handschrift statt zwischen einem Bühnenbild und einer
   Tabelle.

   ⚠️ ALLE MASSE IN --bw/--bh. Die Bühne ist seit A.20.27.0 ein festes Feld von
   1280×720, das als Ganzes skaliert wird; `vw`/`vh` würden hier den FENSTER-
   rand messen statt der Bühne, und im Saal stünde etwas anderes als in der
   Vorschau. Genau das war sein Befund vom 26.08.

   ⚠️ z-index 65: ÜBER dem Zwischenstand (60), UNTER Übergabe-Schirm (70) und
   Intro (80). Ein laufendes Video muss die Karte überdecken können — der
   Spielleiter kann nach der Siegerehrung noch ein Abspann-Video anstossen. */
.stage-endkarte {
  position: fixed; inset: 0; z-index: 65;
  display: flex; flex-direction: column;
  align-items: center; justify-content: flex-start;
  gap: calc(1.6 * var(--bh));
  padding: calc(5 * var(--bh)) calc(3 * 1.25 * var(--bw)) calc(4 * var(--bh));
  background: var(--xxl-bg) var(--xxl-buehne-bild) center/cover no-repeat;
  animation: stand-auf 0.35s ease-out;
  overflow: hidden;
}
/* ⚠️ GEMESSEN, NICHT GESCHÄTZT (26.08.): mittig gesetzt standen oben UND unten
   je 108 px leere Bühne, und das Podest schwebte über dem Boden des
   Bühnenbildes statt darauf. Der Titel sitzt jetzt oben, das Podest wächst in
   den Raum dazwischen und steht mit `align-items: flex-end` unten auf — die
   Standlinie ist damit dieselbe, egal wie hoch die Köpfe darüber bauen und wie
   lang die Liste darunter wird. */

/* Klein und oben: hier trägt nicht das Logo den Schirm, sondern das Podest.
   Auf dem Übergabe-Schirm ist es umgekehrt — dort steht es im Lichtkegel. */
.ek-logo {
  max-height: calc(9 * var(--bh));
  max-width: calc(22 * 1.25 * var(--bw));
  width: auto; height: auto; object-fit: contain;
  filter: drop-shadow(0 0 calc(1.2 * 1.25 * var(--bw)) rgba(var(--xxl-accent-rgb), 0.45));
}

.ek-titel {
  color: var(--xxl-accent);
  font-weight: 800;
  font-size: calc(6.5 * var(--bh));
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-shadow: 0 calc(0.3 * var(--bh)) calc(1.5 * var(--bh)) rgba(0, 0, 0, 0.85);
}

/* --- Das Podest ----------------------------------------------------------
   ⚠️ `align-items: flex-end` ist der ganze Trick: die drei Spalten sind
   verschieden hoch, stehen aber auf EINER Linie. Ohne das schwebten die
   Sockel auf verschiedenen Höhen und es sähe aus wie ein Stapelfehler. */
.ek-podest {
  display: flex; align-items: flex-end; justify-content: center;
  gap: calc(1.5 * 1.25 * var(--bw));
  /* Nimmt den Platz zwischen Titel und Liste — und stellt sein Podest unten
     hinein. Wird die Liste lang, schrumpft dieser Raum, und die Standlinie
     wandert mit nach oben statt aus der Bühne zu laufen. */
  flex: 1 1 auto;
  min-height: 0;
}
.ek-stufe {
  display: flex; flex-direction: column; align-items: center;
  gap: calc(0.8 * var(--bh));
  /* Jede Stufe steigt einzeln auf, von unten nach oben und von hinten nach
     vorn: erst Bronze, dann Silber, zuletzt Gold. Das ist die Reihenfolge
     einer Siegerehrung, und sie kostet zusammen 0,9 s — kurz genug, dass
     niemand wartet, lang genug, dass es ein Moment ist. */
  animation: ek-auf 0.45s ease-out backwards;
}
.ek-stufe.ist-platz3 { animation-delay: 0.05s; }
.ek-stufe.ist-platz2 { animation-delay: 0.30s; }
.ek-stufe.ist-platz1 { animation-delay: 0.55s; }
@keyframes ek-auf {
  from { opacity: 0; transform: translateY(calc(4 * var(--bh))); }
  to   { opacity: 1; transform: translateY(0); }
}

.ek-krone {
  font-size: calc(5 * var(--bh));
  line-height: 1;
  filter: drop-shadow(0 0 calc(1 * var(--bh)) rgba(255, 210, 90, 0.75));
  animation: ek-krone 2.4s ease-in-out 1.1s infinite;
}
@keyframes ek-krone {
  0%, 100% { transform: translateY(0) rotate(-4deg); }
  50%      { transform: translateY(calc(-0.6 * var(--bh))) rotate(4deg); }
}

/* Bei Gleichstand stehen mehrere nebeneinander AUF derselben Stufe — nicht
   untereinander: eine Stufe, die in die Höhe wächst, hebt zwei Gleichplatzierte
   optisch über die anderen. */
.ek-koepfe { display: flex; align-items: flex-end; gap: calc(1 * 1.25 * var(--bw)); }
.ek-kopf {
  display: flex; flex-direction: column; align-items: center;
  gap: calc(0.4 * var(--bh));
  text-align: center;
  max-width: calc(16 * 1.25 * var(--bw));
}
.ek-kopf.ist-raus { opacity: 0.5; }
/* Die Avatare rechnen in `em` (siehe .avatar) — die Schriftgrösse des Kopfes
   ist damit ihr Mass. */
.ek-kopf { font-size: calc(2.6 * var(--bh)); }
.ek-stufe.ist-platz1 .ek-kopf { font-size: calc(3.4 * var(--bh)); }
.ek-stufe.ist-geteilt .ek-kopf { font-size: calc(2.2 * var(--bh)); }

.ek-name {
  font-weight: 700; color: var(--xxl-text);
  font-size: calc(2.6 * var(--bh));
  line-height: 1.1;
  text-shadow: 0 calc(0.15 * var(--bh)) calc(0.8 * var(--bh)) rgba(0, 0, 0, 0.9);
  /* Ein langer Name darf die Stufe nicht breiter machen als das Podest. */
  overflow-wrap: anywhere;
}
.ek-stufe.ist-platz1 .ek-name { font-size: calc(3.4 * var(--bh)); }
.ek-punkte {
  font-weight: 800; color: var(--xxl-accent);
  font-size: calc(3.2 * var(--bh));
  font-variant-numeric: tabular-nums;
  line-height: 1;
  text-shadow: 0 calc(0.15 * var(--bh)) calc(0.8 * var(--bh)) rgba(0, 0, 0, 0.9);
}
.ek-stufe.ist-platz1 .ek-punkte { font-size: calc(4.4 * var(--bh)); }

/* ⚠️ DIE HÖHEN SIND DIE AUSSAGE. Gold höher als Silber höher als Bronze — wer
   sie gleich hoch machte, hätte drei Kästen statt eines Podests. */
/* ⚠️ EIN KÖRPER, KEIN GLAS. Der erste Anlauf war ein halbdurchsichtiger
   Farbverlauf — auf dem hellen, unruhigen Bühnenbild verschwand er fast ganz,
   und drei Scheiben in verschiedenen Höhen sehen nicht aus wie ein Podest,
   sondern wie ein Anzeigefehler. Dunkler Körper, heller Deckel: die Kante oben
   ist das, worauf jemand steht, und sie trägt die Farbe. */
.ek-sockel {
  display: flex; align-items: center; justify-content: center;
  width: calc(14 * 1.25 * var(--bw));
  border-radius: calc(0.4 * 1.25 * var(--bw)) calc(0.4 * 1.25 * var(--bw)) 0 0;
  background: linear-gradient(180deg,
    rgba(28, 12, 48, 0.92), rgba(12, 5, 24, 0.96));
  border-top: calc(0.55 * var(--bh)) solid rgba(var(--xxl-accent-rgb), 0.95);
  border-left: 1px solid rgba(255, 255, 255, 0.10);
  border-right: 1px solid rgba(255, 255, 255, 0.10);
  box-shadow: 0 calc(-0.4 * var(--bh)) calc(2 * var(--bh)) rgba(var(--xxl-accent-rgb), 0.30),
              inset 0 calc(1 * var(--bh)) calc(2 * var(--bh)) rgba(255, 255, 255, 0.06);
}
.ek-stufe.ist-platz1 .ek-sockel { height: calc(17 * var(--bh)); }
.ek-stufe.ist-platz2 .ek-sockel { height: calc(11 * var(--bh)); }
.ek-stufe.ist-platz3 .ek-sockel { height: calc(7 * var(--bh)); }
/* Der Erste bekommt den Schein, nicht ein anderes Material: das Podest soll aus
   EINEM Stück sein. */
.ek-stufe.ist-platz1 .ek-sockel {
  box-shadow: 0 calc(-0.4 * var(--bh)) calc(4 * var(--bh)) rgba(var(--xxl-accent-rgb), 0.55),
              inset 0 calc(1 * var(--bh)) calc(3 * var(--bh)) rgba(255, 255, 255, 0.10);
}
/* ⚠️ JEDE MEDAILLE PASST ZU IHREM SOCKEL. Eine Größe für alle drei stiess auf
   der niedrigsten Stufe unten an (gemessen: Medaille 43 px in einem 50 px hohen
   Sockel — und ein Emoji baut über seine Schriftgröße hinaus). */
.ek-marke { line-height: 1; }
.ek-stufe.ist-platz1 .ek-marke { font-size: calc(8 * var(--bh)); }
.ek-stufe.ist-platz2 .ek-marke { font-size: calc(5.5 * var(--bh)); }
.ek-stufe.ist-platz3 .ek-marke { font-size: calc(4 * var(--bh)); }

/* --- Wer nicht aufs Podest passt ----------------------------------------- */
/* ⚠️ SCHMAL. Der erste Anlauf lief über 928 px der 1280 breiten Bühne und
   schnitt als dunkler Balken quer durchs Bild — gemessen, nicht geahnt. Sie ist
   der Nachsatz zum Podest, nicht sein Gegengewicht. */
.ek-rest {
  display: flex; flex-direction: column;
  gap: calc(0.4 * var(--bh));
  list-style: none; margin: 0; padding: 0;
  width: min(calc(42 * 1.25 * var(--bw)), 62%);
  /* ⚠️ Luft zum Podest. Gemessen klebte sie mit 8 px daran — und zwei Dinge,
     die sich berühren, liest das Auge als eines. */
  margin-top: calc(1.5 * var(--bh));
  /* Kommt NACH dem Podest — es soll zuerst dastehen. */
  animation: stand-auf 0.4s ease-out 0.9s backwards;
}
.ek-rest.ist-voll {
  display: block; columns: 2;
  column-gap: calc(1.5 * 1.25 * var(--bw));
  width: min(calc(66 * 1.25 * var(--bw)), 88%);
}
.ek-restzeile {
  display: flex; align-items: center;
  gap: calc(0.8 * 1.25 * var(--bw));
  padding: calc(0.35 * var(--bh)) calc(1 * 1.25 * var(--bw));
  border-radius: calc(0.4 * 1.25 * var(--bw));
  background: rgba(18, 8, 34, 0.72);
  border: 1px solid rgba(255, 255, 255, 0.08);
  font-size: calc(2.4 * var(--bh));
  color: var(--xxl-text);
  /* Eine Zeile darf nicht zwischen den Spalten zerrissen werden. */
  break-inside: avoid;
  margin-bottom: calc(0.5 * var(--bh));
}
.ek-restzeile.ist-raus { opacity: 0.5; }
.ek-restplatz {
  color: var(--xxl-accent); font-weight: 800;
  min-width: calc(3 * 1.25 * var(--bw));
  font-variant-numeric: tabular-nums;
}
.ek-restname { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.ek-restpunkte {
  font-weight: 800; color: var(--xxl-accent);
  font-variant-numeric: tabular-nums;
}

.ek-leer {
  font-size: calc(3 * var(--bh));
  color: var(--xxl-text-muted);
  text-shadow: 0 calc(0.15 * var(--bh)) calc(0.8 * var(--bh)) rgba(0, 0, 0, 0.9);
}

/* Ohne Logo hat die Karte oben Platz frei — dann darf die Überschrift grösser
   sein. Dieselbe Regel wie beim Übergabe-Schirm. */
.stage-endkarte.ist-ohne-logo .ek-titel { font-size: calc(8 * var(--bh)); }

/* Ohne Podest — ein Abend, an dem keine Punkte gefallen sind — steht die Karte
   mittig. Der Aufbau „Titel oben, Podest auf den Boden" braucht das Podest;
   ohne es hinge die Liste unter der Überschrift und darunter läge die halbe
   Bühne leer. Und die Liste darf dann breiter sein, sie ist alles, was da ist. */
.stage-endkarte.ist-ohne-podest { justify-content: center; }
.stage-endkarte.ist-ohne-podest .ek-rest { margin-top: 0; }

@media (prefers-reduced-motion: reduce) {
  .stage-endkarte, .ek-stufe, .ek-rest { animation: none; }
  .ek-krone { animation: none; }
}

/* ---- „Wer hat geantwortet?" (A.20.34.0) -----------------------------------
   Sein Befund vom 26.08.: ohne Buzz gab es keinen Weg, jemandem die Punkte der
   Frage zu geben. Diese Knöpfe setzen, WER dran ist; gewertet wird danach mit
   den bekannten ✓/✗.

   ⚠️ Grosse Daumenziele: das Pult liegt im Halbdunkel auf dem Tisch, und ein
   Fehlgriff vergibt die Punkte an die falsche Person. Mindestens 44 px hoch —
   dieselbe Zahl wie bei `.pult-big`, aus demselben Grund. */
/* ⚠️⚠️ EINE ZEILE JE SPIELER, MIT DEM URTEIL DARAN (A.20.34.1). Der erste
   Anlauf war eine Reihe von NAMEN: wer einen drückte, machte ihn dran und
   musste danach weiter unten werten. Auf seinem Handy sah das aus wie ein
   Kasten mit einem Namen darin — und ein Kasten mit einem Namen darin sieht
   nicht aus wie ein Knopf. Seine Frage dazu: „Warum kann ich hier nun nicht
   sagen, Spieler hat richtig oder falsch geantwortet?"
   ▶ Ein Bedienschritt, der nirgends auf dem Schirm steht, existiert nicht. */
.pult-dranwahl-liste { display: flex; flex-direction: column; gap: 0.5rem; }
.pult-dranzeile {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: 0.5rem;
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--xxl-border);
  border-radius: 0.6rem;
}
/* Die beiden Urteile bleiben beieinander und wandern gemeinsam nach rechts —
   oder gemeinsam in die nächste Zeile, wenn der Name lang ist. */
.pult-dranurteil {
  display: inline-flex; align-items: center; gap: 0.5rem;
  margin-left: auto;
}
.pult-dranname {
  display: inline-flex; align-items: center; gap: 0.45rem;
  font-weight: 700; color: var(--xxl-text);
  /* Ein langer Name schrumpft, statt die Knöpfe hinauszuschieben — dieselbe
     Regel wie in der Spielerzeile (A.20.32.0). */
  flex: 1 1 auto; min-width: 0;
  overflow-wrap: anywhere;
}
/* ⚠️ 44 px: das Pult liegt im Halbdunkel auf dem Tisch, und ein Fehlgriff
   vergibt die Punkte an die falsche Person. Dieselbe Zahl wie bei `.pult-big`. */
.pult-dranknopf {
  min-height: 44px;
  padding: 0.35rem 0.9rem;
  border: 1px solid var(--xxl-border);
  border-radius: 0.5rem;
  background: transparent;
  font-weight: 700;
  cursor: pointer;
  white-space: nowrap;
}
/* Die beiden tragen dieselben Farben wie die grossen ✓/✗ darunter — es ist
   derselbe Handgriff, nur an der Zeile. */
.pult-dranknopf.ist-richtig { color: var(--xxl-accent); border-color: var(--xxl-accent); }
.pult-dranknopf.ist-falsch  { color: var(--xxl-danger); border-color: var(--xxl-danger); }
.pult-dranknopf.ist-richtig:hover { background: rgba(var(--xxl-accent-rgb), 0.16); }
.pult-dranknopf.ist-falsch:hover  { background: rgba(255, 90, 120, 0.14); }

/* ═══════════════════════════════════════════════════════════════════════════
   FESTE ZONEN (A.20.47.0)
   ═══════════════════════════════════════════════════════════════════════════

   Seine Ansage vom 26.08.2026: „Ich würde vorschlagen das wir feste Zonen
   einführen. Eine für Frage + Bild/Video, eine für Spieler, einer für
   Antwortmöglichkeiten und eine für alles andere (Buzzer Status usw)."

   ▶ WARUM. Bis hierher war die Bühne EINE Flex-Spalte, in der alles um
   denselben Platz konkurrierte. Daraus kam eine ganze Fehlerklasse:
   A.20.44.0 (die Spielerliste frass die Frage) · A.20.45.0 (das Bild lag über
   den Antworten) · A.20.46.0 (die Antwortenliste schnitt sechs Namen ab) — und
   zuletzt der Balken, der 29 px über den Antworten lag, weil `.stage-options`
   schrumpfen darf und seine Optionen nicht. Wer seinen eigenen Kasten hat,
   kann keinem anderen etwas wegnehmen.

   ▶ DIE ANTEILE STEHEN HIER UND NUR HIER. Sie sind GEMESSEN, nicht geraten
   (`scripts/befund-zonen.js`, zwölf Lagen mit zehn Leuten im Raum):
     Kopf   4,9 % in JEDER Lage      → als eigene Zeile abgeschafft, siehe unten
     Spieler 21,4 % in JEDER Lage    → auf 17 gesenkt, seine Ansage „kleiner"
     Balken  8,9 % Sockel            → 7,2
     Antworten 19,6 % ungeschrumpft  → 20,8, jetzt über die volle Breite
     Frage+Medium der Rest           → 55

   ⚠️ ALS `fr`, NICHT IN `--bh`. `.stage-main` trägt eine Polsterung; feste
   Bühnenprozente ergäben zusammen mehr als der Innenraum hoch ist. Als
   Bruchteile bleibt das VERHÄLTNIS exakt und die Polsterung darf sich ändern.

   ⚠️⚠️ KEINE ZONE BEKOMMT `position`. Der Bezugsrahmen für alles von Hand
   Gesetzte ist und bleibt `.stage-question-wrap`. */

/* ⚠️⚠️⚠️ DIESE REGEL GILT FÜR BEIDE — DIE BÜHNE UND DIE EDITOR-VORSCHAU.
   Der Schirm im Editor IST `.stage-main`, die Fläche darin IST der Wrap; das
   stand schon immer so im Kommentar von `.quiz-vorschau-schirm`, aber die
   Aufteilung war dort NACHGEMESSEN statt geteilt (`padding: 6.33% 2.5%`).
   ▶ Eine nachgemessene Zahl stimmt bis zur nächsten Änderung. Genau daran ist
     sie am 25.08. schon einmal gescheitert: die 13,05 % unten stammten aus
     einem ZUSTAND statt aus dem Layout und waren um mehr als das Doppelte
     falsch — die Vorschau zeigte weniger Gedränge, als der Beamer hat.
   ▶ Mit den Zonen gibt es nichts mehr nachzumessen: beide rechnen dieselben
     Anteile, und der Editor bekommt für die Spielerzone einen leeren
     Platzhalter (`.quiz-vorschau-spielerplatz`). */
.stage-main,
.quiz-vorschau-schirm {
  /* ▶▶ DIE VIER ZAHLEN DER BÜHNE. Sie stehen HIER und nur hier — wer eine Zone
     je Frageform anders will, überschreibt genau diese Variablen
     (z. B. `.stage-main.zeigt-wette { --zone-antworten: 28fr }`). Das ist die
     „kleine Tabelle je Frageform" aus der Übergabe; eine Formel wäre sie nicht.
     ⚠️ Die Einheit gehört MIT in die Variable: `calc()` kann kein `fr`. */
  --zone-inhalt: 83fr;
  --zone-spieler: 17fr;
  --zone-oben: 51fr;
  --zone-antworten: 25fr;
  --zone-status: 7fr;

  /* ⚠️⚠️ WIE VIEL PLATZ DIE QR-ECKE RECHTS UNTEN WEGNIMMT — EINE ZAHL, EIN ORT.
     Sie stand bisher nur im `padding-right` des Unterbaus. Seit die Kopfzeile
     abgeschafft ist, steht dort unten aber noch etwas: „Runde · Punkte" sind
     aus dem Kopf in die Statuszone gewandert, also genau unter den QR.
     Gemessen am 27.08.: der QR deckte „20 Punkte" auf **147 px** — und zwar
     nicht das QR-Bild allein, sondern die Ecke samt Knopf „QR ausblenden".
     ▶ Wer die Ecke ändert, ändert diese Zahl — und beide Zonen gehen mit.

     ⚠️⚠️⚠️ OHNE px-BODEN SEIT A.20.48.0 (Befund 13, 06.09.2026). Hier stand
     `clamp(70px, calc(8 * var(--bw)), 130px)` — dieselbe Rechnung wie an der
     QR-Grafik selbst (`.stage-qr-ecke`, Zeile ~2896), und dort ist sie
     RICHTIG: ein QR-Code muss abzuscannen sein, das ist eine Angabe in echten
     Pixeln. HIER geht es aber nicht um die Grafik, sondern um den PLATZ, den
     sie in der Bühne wegnimmt — und der ist ein ANTEIL.
     Auf der Bühne fiel das nie auf: `--bw` ist dort konstant 12,8 px, 8 · bw
     sind 102,4 px, der Deckel greift nie. In der EDITOR-Vorschau ist `--bw`
     ein Prozent der Vorschaubreite (rund 7 px) — 8 · bw sind dort 56 px, und
     der Boden zog sie auf 70. Der Balken stand damit rund einen Prozentpunkt
     weiter links als im Saal, gemessen in `befund-vorschau-buehne` an
     „die Zustandszeile steht auch seitlich richtig".
     ⚠️ Und in der Vorschau ist der QR gar nicht da (`.ist-vorschau
     .stage-qr-ecke { display: none }`) — reserviert wird der Platz trotzdem,
     denn im Saal ist er weg. Genau deshalb muss es ein Anteil sein. */
  --qr-platz: calc(calc(8 * var(--bw))
                 + clamp(calc(0.8 * 1.25 * var(--bw)), calc(2 * var(--bw)), calc(2 * 1.25 * var(--bw))));

  display: grid;
  grid-template-rows: var(--zone-inhalt) var(--zone-spieler);
  /* ⚠️⚠️ EINE SPALTE, UND SIE DARF SCHRUMPFEN. Ohne `minmax(0, …)` nimmt die
     implizite Spalte die Inhaltsbreite ihres breitesten Kindes — die
     Spielerreihe mit zehn Kacheln — und die Bühne ragte seitlich heraus. */
  grid-template-columns: minmax(0, 1fr);

  /* ⚠️⚠️ POLSTERUNG UND ZWISCHENRÄUME SIND NICHT UMSONST. Der erste Bau rechnete
     die Anteile gegen die vollen 720 px — tatsächlich fressen Polsterung und
     die drei `gap` zusammen 118 px, und die Antwortzone kam 13 px zu klein
     heraus: die untere Antwortreihe wurde abgeschnitten und der Zustandsbalken
     lag darüber. Gemessen, auf dem Bild gesehen.
     ▶ Deshalb hier knapper als vorher. Die Zonen trennen sich schon durch ihre
       Anordnung; sie brauchen dafür keinen breiten Zwischenraum. */
  /* ⚠️⚠️⚠️ ZWEI AUSDRÜCKE FÜR DIESELBE GRÖSSE — und das ist kein Versehen,
     sondern die einzige Stelle, an der Bühne und Vorschau sich unterscheiden
     MÜSSEN. `--bw` ist auf der Bühne eine feste Länge (12,8 px) und in der
     Vorschau ein `cqw`-Wert. Ein `cqw` wird immer gegen den Container
     AUSSERHALB des Elements aufgelöst — für die KINDER des Schirms ist das der
     Schirm, für den Schirm selbst aber der Kasten darüber. `--bw` bedeutet an
     diesen beiden Orten deshalb nicht dasselbe, und Polsterung und Fuge stünden
     mit ihm um 3,3 % daneben.
     ▶ Die beiden Zahlen stehen unten bei `.stage-main` und
       `.quiz-vorschau-schirm`, jede in der Einheit, die an ihrem Ort stimmt.
       Was sie bedeuten, ist dasselbe: 1,6 % der Bühnenbreite und 1 % ihrer
       Höhe. */
  padding: var(--zone-polster);
  gap: var(--zone-fuge);
}
/* Die Bühne ist ein festes Feld — hier ist `--bw` eine Länge. */
.stage-main {
  --zone-polster: clamp(calc(0.8 * 1.25 * var(--bw)), calc(1.6 * var(--bw)), calc(1.6 * 1.25 * var(--bw)));
  --zone-fuge: calc(1 * var(--bh));
}
/* Im Editor rechnet der Schirm gegen SEINE eigene Breite: `1.6cqw` sind 1,6 %
   davon, und weil er 16:9 ist, sind 0,5625cqw genau 1 % seiner Höhe.
   ⚠️ Das setzt voraus, dass `.quiz-fragevorschau-buehne` ein Container ist —
   ohne ihn fiele `cqw` hier auf die Fensterbreite zurück. */
.quiz-vorschau-schirm {
  --zone-polster: 1.6cqw;
  --zone-fuge: 0.5625cqw;
}

/* ⚠️ Der Wrap behält `position: relative` (weiter oben) — er IST der
   Bezugsrahmen. Neu ist nur, dass er innen ein Raster statt einer Flex-Spalte
   ist. `overflow: hidden` und `min-height: 0` bleiben ebenfalls stehen. */
/* ⚠️ Auch diese Regel gilt für BEIDE — der Wrap auf der Bühne und die Fläche im
   Editor sind dasselbe Rechteck, und seit A.20.47.0 auch dasselbe Raster.
   Die 81 von Hand gesetzten Platzierungen rechnen gegen genau dieses Rechteck,
   in Vorschau wie im Saal. */
.stage-question-wrap,
.quiz-vorschau-flaeche {
  display: grid;
  /* ⚠️⚠️ DIE STATUSZONE HAT EINEN HARTEN BODEN, DIE ANDEREN BEIDEN NICHT.
     Ihr Inhalt ist eine Pille mit fester Schrift („Antworten offen — 0 von
     10"): sie ist auf 1280×720 **64 px** hoch, in JEDER der zwölf gemessenen
     Lagen, und sie kann nicht schrumpfen. 7fr ergaben 46 px — gemessen ragte
     sie 9 px oben und 9 px unten aus ihrer eigenen Zone.
     ▶ `minmax(min-content, …)` gibt ihr genau so viel, wie sie WIRKLICH
       braucht, und den Rest weiterhin nach der Tabelle. Das ist EINE Regel;
       die Alternative wäre gewesen, in allen fünf Frageform-Zeilen unten die
       Zahlen nachzuziehen — und beim nächsten Wort im Balken wieder.
     ⚠️ Frage und Antworten bekommen KEINEN Boden: die sollen ja schrumpfen
       dürfen, genau dafür gibt es das Einpassen. */
  /* ⚠️⚠️⚠️ DIE REGEL, NACH DER DIE BÖDEN VERGEBEN SIND: eine Zone bekommt
     einen Boden, wenn ihr Inhalt NICHT einpassen kann.
       Frage + Medium → KEIN Boden. Beide haben einen Einpasser
                        (`buehnenbild.passeEin`, `buehne-medienplatz.js`), sie
                        geben ab, wenn es eng wird — dafür sind sie gebaut.
       Antworten      → Boden. Der Kasten darf schrumpfen, seine Karten nicht
                        (`.stage-options`, `.stage-reihe-zeile` und die Paare
                        stehen in festen Maßen). Gemessen am 27.08.: fünf
                        Ordnen-Karten ragten 6 px oben und 6 px unten aus der
                        Zone, bei Paaren und der Wette 3 bis 7 px — und die
                        Zone schneidet ab.
       Status         → Boden, siehe unten.
     ▶ Ohne diese Regel bliebe nur, die Anteile je Frageform nachzuziehen —
       und die stimmen dann bis zur sechsten Karte. */
  grid-template-rows: var(--zone-oben)
                      minmax(min-content, var(--zone-antworten))
                      minmax(min-content, var(--zone-status));
  grid-template-columns: minmax(0, 1fr);
  /* Enger als die alte Flex-Lücke: siehe die Polsterungs-Notiz oben. */
  gap: calc(1 * var(--bh));

  /* ⚠️⚠️⚠️ DIESE VIER ZEILEN SIND DER GRUND, WARUM DIE ZONEN NICHT WIRKTEN
     (27.08.). Der Wrap ist seit A.20.9.0 eine zentrierte Flex-Spalte, und die
     alte Regel weiter oben setzt `align-items: center` und
     `justify-content: center`. Wer nur `display` auf `grid` umstellt, NIMMT
     BEIDE MIT — und im Raster bedeuten sie etwas anderes:
       `align-items: center`     → das Kind wird NICHT auf seine Zeile
                                   gestreckt, sondern darin zentriert. Es
                                   behält also seine Inhaltshöhe, und genau
                                   das sollten die Zonen ja abschaffen.
       `justify-content: center` → die Spalte wird auf Inhaltsbreite gezogen
                                   und zentriert, statt die Breite zu füllen.
     Gemessen am 27.08.: die Zeilen standen richtig auf 368 / 180 / 51 px, die
     Zonen darin aber auf 422 / 141 / 64 — die obere ragte 27 px über ihre
     Zeile, und weil sie ZENTRIERT war, oben genauso weit wie unten. Auf dem
     Beweisbild fehlte die erste Zeile der Frage.
     ⚠️⚠️ UND KEIN PRÜFPUNKT KONNTE DAS SEHEN: eine Zone, die mit ihrem Inhalt
     mitwächst, meldet keinen Überlauf — wortgleich die Lehre aus A.20.9.0
     („ein Behälter, der mitwächst, kann seinen eigenen Überlauf nicht
     messen"). Auch das Einpassen des Textes hörte dadurch lautlos auf zu
     wirken: `passeEin` misst `scrollHeight > clientHeight`, und beides stand
     auf 422.
     ▶ Wer `display` umstellt, schreibt die Ausrichtung neu hin — sie erbt
       sonst eine Bedeutung aus der alten Bauweise. */
  align-items: stretch;
  justify-items: stretch;
  align-content: stretch;
  justify-content: stretch;
}

/* ── ZONE 1+2: Frage und Medium NEBENEINANDER ─────────────────────────────
   `1fr auto`: die Medienspalte bemisst sich aus ihrer eigenen Höhe mal ihrem
   Seitenverhältnis, die Frage bekommt den Rest. Fehlt das Medium, steht es auf
   `display: none` und die Spalte fällt ersatzlos weg — dann nimmt die Frage
   die ganze Breite. */
.stage-zone-oben {
  display: grid;
  grid-template-columns: 1fr auto;
  /* ⚠️⚠️⚠️ DIE ZEILE MUSS AUSGESCHRIEBEN WERDEN, SONST IST SIE `auto` — und
     eine `auto`-Zeile ist so hoch wie ihr Inhalt. Das Medium trägt darin
     `height: 100%`, und 100 % von „so hoch wie du selbst bist" ist eine
     Rechnung im Kreis: der Browser fällt auf die Inhaltshöhe zurück, das Bild
     wächst über die Zone hinaus, und `overflow: hidden` schneidet es ab.
     Gemessen am 27.08.: das Bild ragte **63 px** (1280×720) bzw. **79 px**
     (1600×900) aus seinem Kasten, bei 66,7 % Bühnenhöhe in einer Zone von
     44,7 %. ▶ Gefunden hat es `test:medienplatz`, NICHT `befund-zonen.js` —
     der fragte die obere Zone gar nicht ab (inzwischen nachgezogen).
     `minmax(0, 1fr)` gibt der Zeile die Höhe, die das Raster ihr zuweist, und
     erlaubt ihr zugleich, unter ihre Inhaltsgröße zu schrumpfen. */
  grid-template-rows: minmax(0, 1fr);
  /* ⚠️⚠️⚠️ SPALTENFLUSS, WEIL DIE ZONE SEIT A.20.48.0 DREI KINDER HAT (Befund 5):
     Frage, Fragen-Medium und Lösungsbild. Mit dem Vorgabefluss `row` wäre das
     dritte Kind in eine ZWEITE Rasterzeile gebrochen — und die Zone hat nur
     eine, `overflow: hidden` hätte es lautlos abgeschnitten.
     ⚠️ Für zwei Kinder ändert sich dadurch nichts: bei einer Rasterzeile legt
     der Spaltenfluss jedes Kind in seine eigene Spalte, genau wie vorher.
     ⚠️ Und für das dritte gilt `grid-auto-columns: auto` — es bemisst sich
     aus seiner eigenen Breite (`--zone-medien`), wie die zweite Spalte. */
  grid-auto-flow: column;
  grid-auto-columns: auto;
  gap: inherit;
  min-height: 0;
  min-width: 0;
  /* ⚠️⚠️ OHNE DAS SPRENGT DAS MEDIUM DIE ZONE. Der erste Bau gab ihm
     `height: 100%` plus `aspect-ratio` — daraus RECHNET der Browser eine
     Breite, und die kann größer sein als die Spalte. Gemessen kamen Summen
     von 103 bis 110 % heraus: das Bild ragte in die Antwortzone. */
  overflow: hidden;
}
.stage-zone-frage {
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: inherit;
  min-height: 0; min-width: 0;
  overflow: hidden;
}
/* ⚠️⚠️ WENN ES AUCH BEI 45 % NICHT PASST, wird HINTEN abgeschnitten, nicht
   vorne — wer eine Frage vorliest, fängt vorne an. Die Klasse steht weiterhin
   am Wrap (`buehnenbild.js` setzt sie dort), wirken muss sie jetzt aber auf die
   Frage-Zone: der Wrap selbst ist ein Raster und kennt kein `justify-content`
   mehr, das den Text bewegen würde. */
/* ⚠️ Die Klasse sitzt am TRÄGER des Einpassens, und der ist seit A.20.47.0
   die Zone selbst — `buehnenbild.passeEin` setzt sie dort, wo es misst. */
.stage-zone-frage.laeuft-ueber { justify-content: flex-start; }

/* ── ZONE 3: die Antworten, über die volle Breite ────────────────────────── */
.stage-zone-antworten {
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: inherit;
  min-height: 0; min-width: 0;
  overflow: hidden;
}

/* ── ZONE 4: alles andere ────────────────────────────────────────────────── */
.stage-zone-status {
  display: flex; align-items: center; justify-content: space-between;
  gap: clamp(calc(0.75 * 1.25 * var(--bw)), calc(2 * var(--bw)), calc(2 * 1.25 * var(--bw)));
  min-height: 0; min-width: 0;
  overflow: hidden;
}
/* Der Balken in der Mitte, Runde und Punkte an den Rändern. `flex: 1` an der
   Mitte hält ihn zentriert, auch wenn links und rechts ungleich lang sind. */
.stage-zone-status > .stage-buzz-state { flex: 0 1 auto; }
.stage-zone-status > .stage-round,
.stage-zone-status > .stage-points {
  flex: 1 1 0; min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  font-size: clamp(calc(0.95 * 1.25 * var(--bw)), calc(1.8 * var(--bw)), calc(1.5 * 1.25 * var(--bw)));
}
/* ⚠️ Die Punktzahl steht ganz rechts — dort, wo die QR-Ecke liegt. Ohne diesen
   Platzhalter deckt der QR sie zu (gemessen 147 px von „20 Punkte"). Er sitzt
   an der PUNKTZAHL und nicht an der Zone: an der Zone würde er den
   Zustandsbalken aus der Bühnenmitte nach links schieben. */
.stage-zone-status > .stage-points { text-align: right; padding-right: var(--qr-platz); }

/* ── DAS MEDIUM ALS SPALTE ────────────────────────────────────────────────
   ⚠️⚠️ HIER STEHT DER GANZE UNTERSCHIED ZU VORHER. Bis A.20.46.0 bemass sich
   das Medium über seine RESTHÖHE in einer Spalte — gemessen kamen dabei 8 % der
   Bühnenbreite heraus, 102 px bei einem Video. Jetzt bekommt es eine eigene
   Spalte, deren Breite aus der Zonenhöhe mal dem Seitenverhältnis folgt.
   `--medien-seiten` setzt `buehne-medienplatz.js` aus dem geladenen Bild oder
   Video; ohne Angabe gilt 16/9. */
/* ⚠️⚠️ DIE SPALTENBREITE IST EINE ZAHL, KEINE RECHNUNG AUS DEM
   SEITENVERHÄLTNIS. Der erste Bau leitete sie über `aspect-ratio` aus der
   Zonenhöhe ab — elegant, aber sie kann dabei breiter werden als die Spalte,
   und dann ragt das Bild in die Antworten (gemessen: Summen bis 110 %).
   Jetzt bekommt die Spalte eine feste Breite in BÜHNENPROZENT, und das
   Seitenverhältnis hält `object-fit: contain` am Bild selbst. Damit kann
   nichts mehr sprengen, und die Zahl ist je Frageform einstellbar.
   ⚠️ Steht kein Medium da, trägt der Kasten `display: none` — dann ist die
   Spalte 0 und die Frage nimmt die ganze Breite. */
/* ⚠️ Und dasselbe für die Vorschau: dort heißt der Medienkasten
   `.quiz-vorschau-medien`. Zwei Namen, EINE Regel — mit einer eigenen Regel
   daneben liefen die Spaltenbreiten auseinander, und dann stünde das Bild im
   Editor breiter als im Saal. */
.stage-zone-oben > .stage-media,
.stage-zone-oben > .quiz-vorschau-medien {
  width: var(--zone-medien, calc(50 * var(--bw)));
  max-width: 100%;
  height: 100%;
  max-height: 100%;
  flex: 0 0 auto;
  align-self: stretch;
  /* ⚠️⚠️ ZWEI BILDER IM FLUSS BRECHEN NICHT MEHR UM (A.20.48.0, Befund 12,
     gemessen am 06.09.). `.stage-media` trägt `flex-wrap: wrap` — das ist in
     der freien Reihe richtig, in einer festen Spalte aber falsch: die Spalte
     ist 640 px breit, zwei Bilder wollten je 471 px, das zweite brach in eine
     zweite Zeile und wurde von der Zone abgeschnitten. Gemessen stand es mit
     **0 % sichtbar** da und ragte 372 px aus seiner Zone.
     ▶ `nowrap` genügt: die Kinder tragen über `.stage-media > *` schon
     `min-width: 0` und mit dem Vorgabewert `flex-shrink: 1` teilen sie sich
     die Spalte von selbst (471 + 471 → 320 + 320). Ein EINZELNES Bild bleibt
     dabei unverändert — es schrumpft nur, wenn es muss. */
  flex-wrap: nowrap;
}
/* ⚠️⚠️⚠️ STEHT NUR FREI GESETZTES IM KASTEN, GIBT ES KEINE SPALTE (A.20.47.2,
   06.09.). Die Regel darüber gab dem Kasten IMMER seine 50 % — auch mit
   `nur-frei`. Die Frage bekam die linke Hälfte, rechts stand Luft, und
   `passeEin` verkleinerte sie still. Das ist SEIN Normalfall: 29 von 36 Medien
   in der Datenbank stehen frei. Gemessen (`befund-zonen.js`, „Buzzer + FREIES
   Bild", Bühne UND Editor gleich, deshalb war `befund-vorschau-buehne` grün):
   Frage-Spalte 47,8 % der Zone, Medien-Spalte 51,7 %, die Frage auf vier
   Zeilen statt zwei — bei `art-buzzer ohne-medium`, die Zeile stimmte also.
   ⚠️ `.stage-media.nur-frei { height: 0 }` weiter oben (A.20.9.0) half nicht:
     gleiche Spezifität, die spätere Regel gewinnt — und die spätere war die
     Spaltenbreite. Diese hier steht deshalb DAHINTER und ist eine Klasse
     schwerer.
   ⚠️ Der Kasten bleibt im DOM: die freien Kinder MÜSSEN Kinder von
     `#stage-media` bleiben, `buehne-effekt.js` sucht sie dort. Sie rechnen
     absolut gegen den Wrap — die Breite ihres Kastens ist ihnen gleich
     (`.stage-media > .ist-frei { max-width: none }`).
   ⚠️⚠️ `display: contents` STATT `width: 0` (A.20.48.0). Bis A.20.47.2 stand
     hier `width: 0`, und dazu brauchte es eine zweite Regel mit `:has()`, die
     der Zone die Spaltenfuge wegnahm — eine 0-px-Spalte lässt die Lücke sonst
     stehen (gemessen 7 px). Mit `display: contents` erzeugt der Kasten gar
     keine Rasterspalte mehr: die Fuge fällt von selbst weg, und mit der
     dritten Spalte (Lösungsbild, Befund 5) wäre die `:has()`-Regel ohnehin
     nicht mehr eindeutig gewesen — sie hätte die Fuge auch dort genommen, wo
     noch zwei echte Spalten stehen.
   ⚠️ Die freien Kinder sind `position: absolute` gegen den WRAP; sie sind
     damit ohnehin keine Rasterkinder, und `display: contents` am Kasten
     ändert an ihrem Bezugsrahmen nichts.
   ⚠️⚠️ DAFÜR DARF NIEMAND MEHR `display: flex` INS style-ATTRIBUT SCHREIBEN.
     `buehne-medien.js` tat das (`behaelter.style.display = 'flex'`) — eine
     Inline-Angabe schlägt jede Regel hier. Es schreibt jetzt nur noch `''`
     oder `'none'`; die Klasse `.stage-media` trägt das `display: flex`. */
.stage-zone-oben > .stage-media.nur-frei,
.stage-zone-oben > .quiz-vorschau-medien.nur-frei {
  display: contents;
}
/* ▶ DER PLATZ DER SPIELER IM EDITOR (A.20.47.0) — eine leere Rasterzeile.
   Sie hält den Platz, den im Saal die Kachelreihe braucht. Sichtbar ist nichts:
   wer hier Namen zeigte, zeigte eine Besetzung, die zur Frage nicht gehört.
   ⚠️ Sie braucht keine Höhe: die kommt aus `--zone-spieler`, derselben Zahl,
   nach der sich im Saal die Kacheln richten. */
.quiz-vorschau-spielerplatz { min-height: 0; }

/* ⚠️⚠️⚠️ DIE HÜLLE ZWISCHEN KASTEN UND BILD BRAUCHT DIE HÖHE AUCH — sonst löst
   sich das `max-height: 100%` am Bild nicht auf. Zwischen `.stage-media` und
   dem `<img>` steht seit A.5.8.0 ein `.stage-media-buehne` je Medium (dort
   hängen die Enthüllungen dran). Ohne definite Höhe DARAN bezieht sich das
   Prozent des Bildes auf einen Kasten, der selbst erst aus dem Bild entsteht —
   die Angabe fällt ersatzlos aus, und zwar still.
   ▶ Gemessen am 27.08.: das Bild ragte **63 px** (1280×720) und **79 px**
     (1600×900) aus seinem Kasten. Der Kasten selbst sass richtig in seiner
     Zone — `befund-zonen.js` meldete deshalb „nichts ragt heraus", weil er
     Kinder und Enkel prüft und das Bild hier der URENKEL ist.
   ⚠️ Genau davor warnt der lange Kommentar bei `.stage-media-image`: „das
     Prozent löst sich nicht auf, weil `.stage-media` keine definite Höhe hat".
     Mit den Zonen hat der Kasten sie endlich — die Kette darf nur nicht in der
     Hülle dazwischen abreissen. */
/* ⚠️⚠️⚠️ `:not(.ist-frei)` IST PFLICHT, UND ZWAR AM 27.08. LIVE AUFGEFALLEN.
   Ein FREI GESETZTES Medium ist absolut positioniert und rechnet gegen den
   Wrap — `height: 100%` heisst dort „die ganze Fläche hoch". Sein Bild wurde
   damit auf **100 % der Flächenhöhe** aufgeblasen statt auf die 42 %, die
   seine gespeicherte Breite ergibt. Gemeldet hat es `befund-vorschau-buehne`
   („das Bild ist gleich hoch — 42 % → 100 %, 58 Punkte daneben").
   ▶ UND WARUM ES ERST LIVE AUFFIEL: dieser Lauf war 30/30 grün, BEVOR die
     Regel darüber dazukam — sie entstand beim Umstellen von
     `test:medienplatz`, und danach ist er nicht noch einmal gefahren worden.
     **Wer eine Regel ergänzt, wiederholt die Läufe, die den geänderten Bereich
     messen** — nicht nur den, der die Ergänzung verlangt hat.
   ▶ Die Regel gilt nur für Medien IM FLUSS. Wer von Hand setzt, bekommt genau
     die Grösse, die er gesetzt hat — dieselbe Entscheidung wie bei `text-frei`
     seit A.20.11.0. */
/* ⚠️⚠️ UND `:not(.stage-media-audio)` EBENSO (27.08., zweiter Nachtrag). Ein Ton
   hat kein Bild — er steht als KREIS im Kasten (`.stage-media-audio`, feste
   Breite und Höhe, `border-radius: 50%`). `height: 100%` machte aus dem Kreis
   ein Oval von Zonenhöhe. Kein Prüfstand misst eine Form; am Code gefunden. */
.stage-zone-oben > .stage-media > *:not(.ist-frei):not(.stage-media-audio) {
  height: 100%;
  max-height: 100%;
  min-height: 0;
}
/* ⚠️ Eine TONFRAGE braucht keine halbe Bühne für einen Kreis. Trägt der Kasten
   nur Ton (Klasse `nur-ton`, gesetzt in `buehne.js` aus
   `buehnenbild.nurTonImFluss`), ist die Spalte so breit wie der Kreis — die
   Frage bekommt den Rest. Vorher stand der Kreis UNTER der Frage; neben ihr ist
   er dasselbe in der Sprache der Zonen. */
.stage-zone-oben > .stage-media.nur-ton { width: auto; }
.stage-zone-oben > .stage-media img,
.stage-zone-oben > .stage-media video {
  width: 100%; height: 100%;
  max-width: 100%; max-height: 100%;
  object-fit: contain;
}
/* ⚠️⚠️ UND DIESELBE HÖHENKETTE IN DER VORSCHAU (A.20.47.2, 06.09.). Das Bild im
   Editor trug eine feste Kappe von 25,9 cqw (das alte 46vh der Reihe,
   umgerechnet, bei `.quiz-vorschau-medium img`) — die Bühne kennt seit
   A.20.47.0 keine Kappe mehr, dort füllt das Bild die Zonenhöhe. Gemessen
   (`befund-vorschau-buehne.js`, Lagen „im Fluss"): bei der Ordnen-Frage stand
   das Bild im Editor 57,6 % der Fläche hoch, im Saal 37,5 % (20 Punkte, oben
   und unten beschnitten); bei Schätzen umgekehrt 57,6 % gegen 63,3 %. Zwei
   Namen, EINE Regel — wie bei der Spaltenbreite darüber.
   ⚠️ `:not(.ist-frei)` wie auf der Bühne: ein frei gesetztes Bild bekommt
     genau die Grösse, die er gesetzt hat (A.20.47.1). */
.stage-zone-oben > .quiz-vorschau-medien > *:not(.ist-frei) {
  height: 100%;
  max-height: 100%;
  min-height: 0;
}
.stage-zone-oben > .quiz-vorschau-medien > :not(.ist-frei) img,
.stage-zone-oben > .quiz-vorschau-medien > :not(.ist-frei) video {
  width: 100%; height: 100%;
  max-width: 100%; max-height: 100%;
  object-fit: contain;
}

/* ── DIE SPIELERZONE ──────────────────────────────────────────────────────
   Sie war bis A.20.46.0 ein Flex-Kind mit `flex: 0 0 auto` und einem Deckel
   von 26 % (A.20.44.0). Als Rasterzeile hat sie eine feste Höhe, und der
   Deckel ist damit gegenstandslos — die Zone IST der Deckel.
   ⚠️⚠️ 17 STATT 21,4 % ist seine Ansage („die 3 anderen Felder kleiner").
   Gemessen stand der Unterbau in JEDER Lage bei 21,4 %; ob zehn Namen auch in
   17 % noch ganz zu sehen sind, misst `test:vollersaal` — der Prüfpunkt dafür
   ist seit A.20.46.0 da und war schon einmal rot. */
.stage-main > .stage-bottom {
  max-height: 100%;
  height: 100%;
  min-height: 0;
  align-items: flex-start;
}

/* ── DIE SPIELERREIHE (A.20.47.0) ─────────────────────────────────────────
   Sein Befund beim ersten Zonen-Bild: „Die Darstellung der spieler ist
   grausam." Vorher: zwei Restlisten mit Überschriften, die linke bei einer
   Wahlfrage LEER auf der halben Bühnenbreite.

   ⚠️ `grid-auto-flow: column` mit `minmax(0, 1fr)`: alle Kacheln gleich breit,
   die Zahl der Spalten ergibt sich aus der Zahl der Leute. Es gibt keine
   Schwelle, die jemand pflegen müsste — bei zwanzig wird es nur schmaler.
   Die `0` in `minmax` ist Pflicht, sonst nimmt jede Spalte ihre Inhaltsbreite
   und die Reihe ragt seitlich aus dem Bild (die Falle von A.20.44.0). */
.stage-spielerreihe {
  list-style: none; margin: 0; padding: 0;
  width: 100%; height: 100%;
  display: grid;
  grid-auto-flow: column;
  /* ⚠️⚠️ DIE OBERGRENZE IST BEIM ANSEHEN DAZUGEKOMMEN (27.08.). Ohne sie teilen
     sich die Kacheln die Bühne IMMER vollständig — bei einem einzigen Spieler
     war seine Kachel 1240 px breit, der Name stand mittig und sein Gesicht ganz
     links am anderen Ende. Bei zehn Leuten fiel das nie auf, weil dort 1fr
     ohnehin kleiner ist als der Deckel.
     ▶ 15 % ist die Breite, die eine Kachel bei sieben Leuten hätte — ab da
       greift wieder `1fr`. Darunter bleiben die Kacheln gleich gross und die
       Reihe rückt in die Mitte, statt sich auseinanderziehen zu lassen. */
  grid-auto-columns: minmax(0, 1fr);
  gap: clamp(calc(0.3 * 1.25 * var(--bw)), calc(0.7 * var(--bw)), calc(0.7 * 1.25 * var(--bw)));
  align-items: stretch;
}
.stage-spieler {
  /* ⚠️ Die Obergrenze steht an der KACHEL, nicht an der Spalte: `fr` lässt sich
     nicht in ein `min()` schreiben, und eine feste Spaltenbreite würde bei zehn
     Leuten seitlich aus der Bühne laufen. So bleibt die Spalte `1fr`, und die
     Kachel darin bleibt bei wenigen Leuten schmal und steht mittig. */
  /* ⚠️ `width: 100%` MUSS DAZU: `justify-self: center` allein nimmt einem
     Raster-Kind die Streckung, es fällt auf seine Inhaltsbreite zurück — die
     Kachel war danach schmaler als der Name darin („Testspieler" stand als
     „Test" da). Beide Zeilen zusammen heissen: so breit wie die Spalte, aber
     höchstens 15 %, und dann mittig. */
  width: 100%;
  max-width: calc(15 * var(--bw));
  justify-self: center;
  display: grid;
  grid-template-rows: auto auto;
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: 0 clamp(calc(0.25 * 1.25 * var(--bw)), calc(0.6 * var(--bw)), calc(0.6 * 1.25 * var(--bw)));
  min-width: 0; min-height: 0;
  padding: clamp(calc(0.3 * 1.25 * var(--bw)), calc(0.8 * var(--bh)), calc(0.7 * 1.25 * var(--bw)))
           clamp(calc(0.35 * 1.25 * var(--bw)), calc(0.9 * var(--bw)), calc(0.8 * 1.25 * var(--bw)));
  border-radius: 0.6em;
  /* ⚠️⚠️ DIE KACHEL IST IHR EIGENES MASS. Ihre Breite hängt an der Zahl der
     Leute im Raum (`minmax(0, 1fr)` mal zehn oder mal vier) — eine feste
     Schriftgröße darin kann deshalb nur für EINE Besetzung stimmen. Der Tipp
     unten rechnet gegen diese Breite (`cqw`), nicht gegen die Bühne. */
  container-type: inline-size;
  /* Ein eigener Träger, damit die Reihe auf JEDEM Bühnenbild lesbar bleibt —
     vorher schwebten die Namen frei über dem Motiv. */
  background: rgba(15, 6, 45, 0.42);
  border: 1px solid rgba(255, 255, 255, 0.07);
  overflow: hidden;
}
/* ⚠️⚠️ DER NAME BEKOMMT EINE EIGENE ZEILE ÜBER DIE GANZE KACHEL (27.08.).
   Vorher stand er neben dem Gesicht, und bei zehn Leuten blieben ihm gemessen
   **18 px** — NEUN von zehn Namen standen als „A…" da, „Anna" eingeschlossen.
   Das ist keine Schriftgröße zum Nachdrehen: eine Kachel ist bei zehn Leuten
   103 px breit, davon nehmen Gesicht, Punktzahl und Marke nebeneinander fast
   alles. ▶ Übereinander statt nebeneinander: der Name hat jetzt die volle
   Kachelbreite (81 px), darunter stehen Gesicht, Punktzahl und Marke in einer
   Zeile. Die Kachel wird dadurch höher, nicht breiter — und Höhe hat die
   Spielerzone (114 px bei 17 %), Breite hat sie nicht. */
.stage-spieler > .avatar { grid-row: 2; grid-column: 1; }
.stage-spieler-name {
  grid-row: 1; grid-column: 1 / -1;
  text-align: center;
  font-size: clamp(calc(0.7 * 1.25 * var(--bw)), calc(1.15 * var(--bw)), calc(1 * 1.25 * var(--bw)));
  color: var(--xxl-text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;
}
.stage-spieler-punkte {
  grid-row: 2; grid-column: 2;
  font-size: clamp(calc(0.95 * 1.25 * var(--bw)), calc(1.7 * var(--bw)), calc(1.5 * 1.25 * var(--bw)));
  font-weight: 700; color: var(--xxl-accent);
  line-height: 1.05;
}
/* ⚠️ SEIN TIPP, unter Gesicht und Punktzahl — nur bei Schätzen, Ordnen und
   Paaren, und nur nach dem Auflösen. Er steht in einer eigenen Zeile über die
   ganze Kachel, aus demselben Grund wie der Name: „420 (± 20)" passt in keine
   Spalte von 18 px. Das Element gibt es gar nicht erst, solange nichts
   abzugeben war — eine leere dritte Zeile nähme jeder Kachel Höhe weg. */
.stage-spieler-tipp {
  grid-row: 3; grid-column: 1 / -1;
  text-align: center;
  /* ⚠️⚠️ `cqw` — DIE KACHELBREITE, NICHT DIE BÜHNENBREITE. Der längste Text,
     den es hier gibt, ist eine vierstellige Schätzung samt Abstand:
     „1.500 (± 1.390)", sechzehn Zeichen. Gegen die Bühne gerechnet stand er
     bei zehn Leuten auf 12,2 px und wurde zu „1.500 (± 1…" gekürzt — gemessen,
     nicht geschätzt. Gegen die KACHEL gerechnet schrumpft er mit ihr: bei
     vier Leuten ist sie doppelt so breit und die Schrift entsprechend größer.
     ▶ Die Grenzen bleiben in Bühnenmaß, damit die Schrift auf einem Beamer
       nicht unter das Lesbare fällt und bei zwei Spielern nicht ausufert. */
  font-size: clamp(calc(0.5 * 1.25 * var(--bw)), 11cqw, calc(0.85 * 1.25 * var(--bw)));
  color: var(--xxl-text-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;
  line-height: 1.2;
}

/* Die Marke sitzt oben rechts in der Kachel und ist leer, solange nichts zu
   melden ist — dann nimmt sie auch keinen Platz. */
.stage-spieler-marke {
  grid-row: 1 / span 2; grid-column: 3;
  align-self: start; justify-self: end;
  min-width: 1.5em; text-align: center;
  font-size: clamp(calc(0.65 * 1.25 * var(--bw)), calc(1 * var(--bw)), calc(0.9 * 1.25 * var(--bw)));
  font-weight: 700;
  color: var(--xxl-text-muted);
}
.stage-spieler-marke.ist-leer { display: none; }
.stage-spieler-marke.ist-buzz {
  color: #2a0038; background: var(--xxl-accent);
  border-radius: 999px; padding: 0.05em 0.45em;
}
.stage-spieler-marke.ist-still { opacity: 0.5; }

/* ⚠️ DIE ZUSTÄNDE. Was vorher die linke Spalte war, steht jetzt hier —
   deshalb müssen sie auf dem Beamer aus zehn Metern zu unterscheiden sein.
   Farbe UND Rand, nie Farbe allein. */
.stage-spieler.is-active {
  border-color: var(--xxl-accent);
  box-shadow: 0 0 0 2px var(--xxl-accent) inset;
}
.stage-spieler.is-correct { border-color: #97C459; background: rgba(60, 110, 20, 0.45); }
.stage-spieler.is-wrong   { border-color: #F09595; background: rgba(120, 30, 30, 0.42); }
.stage-spieler.is-away    { opacity: 0.45; }
.stage-spieler.ist-raus   { opacity: 0.35; }
.stage-spieler.ist-raus .stage-spieler-name { text-decoration: line-through; }

/* Der QR-Code liegt fixiert über der rechten unteren Ecke — die letzte Kachel
   darf nicht darunter verschwinden. Dieselbe Rechnung wie beim alten Unterbau. */
.stage-main > .stage-bottom {
  padding-right: var(--qr-platz);
}


/* ── DIE TABELLE JE FRAGEFORM (A.20.47.0) ────────────────────────────────
   Aus der Übergabe: „vermutlich je Frageform eine kleine Tabelle statt einer
   Formel — eine Buzzerfrage hat keine Antwortzone, und die soll dann nicht
   leer herumstehen."

   ⚠️ DIE ZAHLEN SIND GEMESSEN (`scripts/befund-zonen.js`, acht Leute im Raum),
   nicht geraten. Der erste Bau gab allen Formen dieselben Anteile — Ordnen lief
   dabei um 55 px über, Paare um 33: fünf Karten brauchen mehr als 25 %.
   ⚠️ Die Klasse setzt `buehne.js` an `#stage`. Ohne sie gelten die Vorgaben
   oben, und das ist der Wahlfall. */

.stage-main.art-buzzer, .quiz-vorschau-schirm.art-buzzer { --zone-oben: 56fr; --zone-antworten: 20fr; }
.stage-main.art-choice, .quiz-vorschau-schirm.art-choice { --zone-oben: 51fr; --zone-antworten: 25fr; }
.stage-main.art-number, .quiz-vorschau-schirm.art-number { --zone-oben: 56fr; --zone-antworten: 20fr; }
/* Fünf Karten untereinander — die grösste Antwortzone von allen. */
.stage-main.art-order,  .quiz-vorschau-schirm.art-order  { --zone-oben: 35fr; --zone-antworten: 41fr; }
/* Fünf Paare in zwei Spalten. */
.stage-main.art-match,  .quiz-vorschau-schirm.art-match  { --zone-oben: 40fr; --zone-antworten: 36fr; }

/* ⚠️ Ohne Medium braucht die obere Zone kaum Höhe — die Frage allein füllt sie
   nicht. Die Klasse setzt `buehne.js` mit, damit die Antworten den Rest
   bekommen, statt dass oben Luft steht. */
.stage-main.ohne-medium.art-buzzer, .quiz-vorschau-schirm.ohne-medium.art-buzzer { --zone-oben: 46fr; --zone-antworten: 30fr; }
.stage-main.ohne-medium.art-choice, .quiz-vorschau-schirm.ohne-medium.art-choice { --zone-oben: 41fr; --zone-antworten: 35fr; }

/* ⚠️ Und der Name auf der Kachel darf nicht auf „…" fallen, nur weil ein ✓
   danebensteht: gemessen waren nach dem Auflösen VIER von acht verkürzt.
   ⚠️⚠️ DAS HAT NICHT GEREICHT — am 27.08. waren es NEUN von zehn, auch ohne
   jede Marke. Die Marke war nur der Tropfen; der Name hatte neben Gesicht und
   Punktzahl von vornherein zu wenig Platz. Er steht deshalb jetzt in einer
   EIGENEN ZEILE über die ganze Kachel (siehe `.stage-spieler-name` oben), und
   die Marke sitzt neben der Punktzahl. */
.stage-spieler { grid-template-columns: auto 1fr auto; }
.stage-spieler-marke { grid-row: 2; grid-column: 3; align-self: center; }
