:root {
  --schrift: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Inter, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;

  /* ── DAS BEWEGUNGSVOKABULAR (23.09.2026) ──────────────────────────────
     Marco: „butterweich". Vorher standen hier zwoelf verschiedene Dauern
     (.14 bis .35 s) und vier Kurven — jede fuer sich vertretbar, zusammen
     nicht aus einem Guss. Jetzt DREI Dauern und EINE Kurve fuers Ankommen:
       kurz  — Rueckmeldung auf Hover, Fokus, Farbe; man soll sie kaum sehen
       mittel — ein Element tritt auf oder schaltet um
       lang  — etwas bewegt sich ueber eine Strecke (Karten, Nachruecken)
     Die Ankunftskurve startet schnell und setzt weich auf: sie antwortet
     sofort und kommt trotzdem ruhig an. `ease`/`ease-in` bleiben, wo etwas
     geht oder nur die Farbe wechselt.
     `hud.js` liest dieselben Werte (`bewegung()`), statt eigene zu fuehren.
     NICHT HIER: die Choreografien — Systemstart (`boot-*`, `kern-hoch`,
     `.boot.geht`, von `BOOT_*` in hud.js getaktet), Diagrammaufbau
     (`dia-*`) und Pulse. Ihre Werte sind aufeinander
     abgestimmte Takte; ein Token daraus verschoebe die Takte gegeneinander. */
  --dauer-kurz: 160ms;
  --dauer: 240ms;
  --dauer-lang: 360ms;
  --kurve-raus: cubic-bezier(.22, 1, .36, 1);
}

/* Der Wechsel Hell/Dunkel als Ueberblendung (`startViewTransition` in hud.js).
   Lang und weich: es aendert sich JEDE Flaeche zugleich, und ein schneller
   Schnitt liest sich dabei wie ein Blitz. */
::view-transition-old(root), ::view-transition-new(root) {
  animation-duration: var(--dauer-lang); animation-timing-function: ease;
}

/* ══ FARBTHEMEN ═══════════════════════════════════════════════════════════
   Zwei Themes, gesteuert über Klasse am <html>. Die bisherigen Werte sind
   das Dark-Theme; sie bleiben Default, wenn JavaScript nicht läuft oder
   noch nicht umgeschaltet hat. */
:root, html[data-herold-theme="dark"] {
  --grund: #0a0a0b;
  --flaeche: #141416;
  --rand: #2a2a2e;
  --text: #fafafa;
  --text-leise: #a1a1aa;
  --akzent: #3b82f6;
  --hoert: #60a5fa;
  --denkt: #f59e0b;
  --fehler: #f87171;
  --schimmer-akzent: rgba(59, 130, 246, .08);
  --schimmer-akzent-stark: rgba(59, 130, 246, .07);
  --schatten: rgba(0, 0, 0, .45);
  --schatten-stark: rgba(0, 0, 0, .55);
  --dialog-hintergrund: rgba(10, 10, 11, .92);
  --boot-scanlinien: rgba(250, 250, 250, .03);
  --flaeche-92: rgba(20, 20, 22, .92);
  --text-alpha-schwach: rgba(250, 250, 250, .06);
  --akzent-alpha: rgba(59, 130, 246, .12);
  --hoert-alpha: rgba(96, 165, 250, .12);
  --fehler-alpha: rgba(248, 113, 113, .14);
  /* Das getoente Gegenstueck zu `--denkt`. Es gab bisher nur den Vollton; das
     Kopfband der Karte braucht die Flaeche dazu (siehe `.widget[data-rang]`). */
  --denkt-alpha: rgba(245, 158, 11, .14);
  --hoert-glow: rgba(96, 165, 250, .45);
  --akzent-glow: rgba(59, 130, 246, .45);
  --akzent-glow-schwach: rgba(59, 130, 246, .25);
}

html[data-herold-theme="light"] {
  --grund: #fafafa;
  --flaeche: #ffffff;
  --rand: #e4e4e7;
  --text: #09090b;
  --text-leise: #52525b;
  --akzent: #2563eb;
  --hoert: #3b82f6;
  --denkt: #d97706;
  --fehler: #dc2626;
  --schimmer-akzent: rgba(37, 99, 235, .10);
  --schimmer-akzent-stark: rgba(37, 99, 235, .08);
  --schatten: rgba(0, 0, 0, .10);
  --schatten-stark: rgba(0, 0, 0, .15);
  --dialog-hintergrund: rgba(250, 250, 250, .96);
  --boot-scanlinien: rgba(9, 9, 11, .03);
  --flaeche-92: rgba(255, 255, 255, .92);
  --text-alpha-schwach: rgba(9, 9, 11, .06);
  --akzent-alpha: rgba(37, 99, 235, .12);
  --hoert-alpha: rgba(59, 130, 246, .12);
  --fehler-alpha: rgba(220, 38, 38, .14);
  --denkt-alpha: rgba(217, 119, 6, .14);
  --hoert-glow: rgba(59, 130, 246, .45);
  --akzent-glow: rgba(37, 99, 235, .45);
  --akzent-glow-schwach: rgba(37, 99, 235, .25);
}

/* Umgelegt wird der Schalter in hud.js (`kugelAusweichen`), sobald das erste
   Fenster da ist und wenn das letzte gegangen ist. */

* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body {
  background: radial-gradient(ellipse at 50% 40%, var(--flaeche) 0%, var(--grund) 60%);
  color: var(--text);
  font-family: var(--schrift);
  overflow: hidden;
  -webkit-user-select: none; user-select: none;
  -webkit-tap-highlight-color: transparent;
  display: grid;
  grid-template-rows: auto 1fr auto;
  padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
}

.kopf {
  display: flex; justify-content: space-between; align-items: center;
  padding: 14px 20px; font-size: 13px; color: var(--text-leise);
  letter-spacing: .04em; text-transform: uppercase;
}
.kopf-links { display: flex; align-items: center; gap: 10px; }
.kopf-rechts { display: flex; align-items: center; gap: 8px; }
.punkt { width: 8px; height: 8px; border-radius: 50%; background: var(--fehler); box-shadow: 0 0 8px var(--fehler); transition: background var(--dauer-lang), box-shadow var(--dauer-lang); }
.punkt.an { background: var(--akzent); box-shadow: 0 0 8px var(--akzent); }
#uhr { font-variant-numeric: tabular-nums; }

/* DIE BUEHNE HAT DREI SPALTEN, UND DIE MITTLERE IST DIE MITTE.
   Bis zum 18.09.2026 stand hier `1fr minmax(0, 380px)` — die Fenster als
   feste Leiste NEBEN der Kugel. Das verschob die obere Haelfte gegen die
   untere (gemessen bei 1440 px: Kugel bei x=522, Eingabefeld bei x=720, also
   198 px Versatz). Danach schwebten sie DARUEBER, dann lagen sie als Zeile
   DARUNTER — und seit dem 22.09.2026 stehen sie wieder daneben, rechts.
   Marco: „Analog zur Themenliste, die jetzt links neben dem Avatar liegt,
   sollen Widgets rechts neben Avatar und Antworttext erscheinen."

   DER FEHLER VON DAMALS WAR NICHT DIE SPALTE, SONDERN IHRE EINSEITIGKEIT.
   Das Mikrofon im Fuss ist ueber die VOLLE Breite zentriert; nimmt nur die
   rechte Seite Platz weg, wandert die Kugel nach links und die Achse kippt.
   Also reserviert die LINKE Seite dieselbe Breite noch einmal — eine
   Phantomgasse, die nichts traegt und genau deshalb alles haelt. Sie ist
   ohnehin der Platz, in dem Schlange und Themenliste `fixed` liegen: die
   beiden bekommen damit nebenbei den Rand, den sie sich bisher mit dem
   Antwortsatz teilen mussten.
   Ohne offenes Fenster ist die Gasse 0 — dann steht hier exakt das Bild von
   vorher, eine Spalte ueber die volle Breite. */
/* `body.fenster-offen` setzt `hud.js` (`kugelSchrumpfen`), sobald das erste
   Fenster da ist. Die Variable `--fensterspalte` dahinter ist mit dem Umbau
   vom 22.09.2026 entfallen: die Gassen sind jetzt fest und gleich breit
   (`--randspalte`), die Karten stehen in der Mitte. Die Klasse selbst bleibt
   — auf dem Telefon entscheidet sie weiter, ob der Avatar Hoehe abgibt. */

/* DIE RAENDER SIND FEST UND GLEICH BREIT — AUCH LEER.
   Marcos zweite Invariante: „Eine Form, die mit dem Inhalt wechselt, gewoehnt
   sich niemand an. 'Nichts offen' ist eine Auskunft — sie sagt, dass
   nachgesehen wurde." Deshalb EINE Zahl fuer beide Gassen und keine, die mit
   dem Inhalt kommt und geht: links die Themen, rechts die Schlange.
   Und weil sie gleich breit sind, sitzt das Mikrofon im Fuss ohne
   Phantomgasse genau in der Mitte — die Rechnung, die frueher zweimal
   aufgetragen werden musste, faellt damit weg. */
:root { --randspalte: 246px; }
.buehne {
  display: grid; padding: 0 20px; min-height: 0;
  grid-template-columns: var(--randspalte) minmax(0, 1fr) var(--randspalte);
}

/* DIE MITTE GEHOERT DEN KARTEN, DAS GESPROCHENE LAEUFT ALS BAND DARUNTER.
   Marco am 22.09.2026, gemessen bei 1600x900: „Die Mitte nimmt 848x360 px in
   grosser Schrift fuer EINEN Satz, der in zwei Minuten weg ist; die
   Fensterspalte ist 356 px breit; die Themenliste 210 px bei Deckkraft 0,55.
   Das Fluechtige hat die Buehne, das Bleibende fluestert."

   ZWEI ZEILEN STATT DREI, UND DIE OBERE IST DIE GROSSE: oben die Karten
   (`minmax(0, 1fr)`, alles, was da ist), darunter das Band mit Avatar und
   Antwortsatz (`auto`, so hoch wie sein Inhalt). Die Leserichtung von oben
   nach unten IST damit die Rangfolge — was vorliegt, dann was gerade gesagt
   wird, dann mein Mikrofon im Fuss.

   DIE DRITTE ZEILE VON VORGESTERN FAELLT WEG. Sie war die untere Luft, die
   die Mitte senkrecht zentriert hat; ein Band, das unten steht, braucht sie
   nicht. `minmax(0, 1fr)` und nicht `1fr`: Letzteres ist `minmax(auto, 1fr)`
   und waechst ueber seinen Anteil hinaus, sobald der Inhalt hoeher ist —
   dann schoebe eine volle Kartenbahn das Band aus dem Bild.

   EIN HORIZONT (Invariante 1): alle drei Zonen der oberen Zeile stehen im
   SELBEN Raster — Themen, Karten und die laufende Arbeit sind Rasterzellen
   und richten sich an der Buehne aus, nicht am Sichtfeld. */
.buehne { grid-template-rows: minmax(0, 1fr) auto; }
.fensterzeile { grid-column: 1 / 4; grid-row: 1; min-height: 0; }
/* DIE BAHN STEHT SYMMETRISCH ZUR ACHSE (24.09.2026). Sie lag auf Spalte 1-2
   und damit um eine halbe Gasse links der Mitte — gemessen 99 px bei 1113 px
   Fensterbreite. Marco: „wenn es nur eins gibt, dann ist das halt in der
   Mitte." Kugel, Band und Mikrofon stehen auf der Achse; die Karten jetzt
   auch.
   Steht die Schlange in der rechten Gasse, gibt die Bahn BEIDE Gassen ab und
   nicht nur die eine — sonst rutschte ihre Mitte wieder zur Seite.
   Erst ab 901 px: darunter blendet `.schlange` per CSS aus (ohne `hidden`),
   und ab 820 abwaerts ist die Buehne eine Spalte ohne Gassen. */
@media (min-width: 901px) {
  .buehne:has(.schlange:not([hidden])) .fensterzeile { grid-column: 2; }
}
/* DIE LINKE GASSE GEHOERT SEIT DEM 23.09.2026 DER BAHN. Dort stand die
   Themenliste; die gibt es nicht mehr, jedes offene Thema ist eine Karte.
   Die Gasse selbst bleibt (`--randspalte`) — sie haelt das Mikrofon im Fuss
   und den Antwortsatz auf der Mittelachse —, nur traegt sie jetzt Karten. */
.mitte { grid-column: 2; grid-row: 2; }
.laeuft-gerade {
  grid-column: 3; grid-row: 1; min-height: 0; min-width: 0;
  display: flex; flex-direction: column; gap: 10px;
  align-self: center; padding-left: 16px;
  max-height: 100%; overflow: hidden;
}

/* DIE KUGEL WEICHT AUS, STATT SICH VERDECKEN ZU LASSEN.
   Die Fenster schweben (`position: fixed`) und reservieren keine Breite — die
   Kugel blieb deshalb exakt mittig und lag bei engem Browserfenster unter dem
   ersten Fenster (gemessen bei 900x900: 158 px Kugel unter dem Fenster, bei
   1100x800 noch 30 px). Jetzt gibt `.mitte` die Fensterspalte als Polsterung
   ab; der Flex-Algorithmus zentriert Kugel UND Dialogzeilen in der Restflaeche
   und schiebt damit beides nach links.
   Polsterung und nicht `transform: translateX()`: ein starrer Versatz haette
   bei 900 px Breite die linke Kante des 720 px breiten Dialogblocks auf -116 px
   geschoben, also aus dem Bild. Neu zentrieren kann nicht ueberstehen. */
.mitte {
  position: relative; display: flex; flex-direction: column;
  align-items: center; justify-content: flex-end; min-height: 0;
  padding-bottom: 2px;
}
/* DIE KUGEL IST DAS ELEMENT, DAS PLATZ ABGIBT.
   Bis zum 18.09.2026 stand hier nichts weiter als `height: auto` — und weil
   `flex-shrink` in `.mitte` auf dem Standardwert 1 steht, hat der Flex-Algorithmus
   Kugel UND Dialogblock gemeinsam zusammengedrückt. Der Dialog hatte aber nur ein
   `min-height` als Boden, keinen reservierten Platz: gemessen bei 1280×720 wurde er
   auf 88 px gestaucht, obwohl sein Inhalt 115 px brauchte, und der Überstand wurde
   bei `overflow: visible` quer über das Eingabefeld im Fuß gemalt.
   Jetzt schrumpft nur noch die Kugel. Sie verliert dabei nichts außer Durchmesser;
   der Text verliert Worte.
   Bemessen wird sie dabei über die HÖHE, nicht mehr über die Breite: in einer
   Flex-Spalte greift das Schrumpfen an der Höhe an: bliebe die Breite gesetzt, würde
   die Fläche beim Schrumpfen verzerrt. So folgt die Breite über `aspect-ratio`
   der Höhe und das Seitenverhältnis bleibt in jeder Größe dasselbe.
   DER MITTLERE DECKEL MISST DIE MITTE, NICHT DEN SCHIRM. `32vw` sind 80 % der
   vollen Breite — sobald rechts die Fensterspalte steht und links ihre
   Phantomgasse, ist die volle Breite aber nicht mehr das, was die Kugel hat.
   Also `(100vw - 40px Buehnenpolster - 2x Fensterspalte) * .32`: dieselben
   80 % der Restflaeche. Ohne Fenster ist die Gasse 0 und es stehen wieder
   32vw da. Wer das weglaesst, schiebt die 5:2-Flaeche bei 1100 px seitlich
   aus ihrer Spalte — gemessen 550 px Kugel in 456 px Mitte.

   SEIT DEN SOUNDLINIEN IST DIE FLAECHE EIN BAND (5:2) UND KEIN QUADRAT.
   Eine Tonkurve in einem Quadrat laesst oben und unten je ein Drittel leer
   und wirkt trotzdem gedraengt. Der Hoehen-Deckel faellt dabei von 56vh auf
   26vh, WEIL die Breite ueber `aspect-ratio` daran haengt: 26vh mal 2,5 sind
   dieselben 65vh Diagonale wie vorher, aber quer. Und `32vw` statt `80vw`
   haelt die alte Regel "hoechstens 80 % der Breite" ein — 32 mal 2,5 sind 80.
   Wer hier die Hoehe anhebt, ohne den vw-Deckel mitzuziehen, schiebt die
   Linien seitlich aus dem Bild. */
/* DER AVATAR IST DAS LEBENSZEICHEN, NICHT DER INHALT (Marco, 22.09.2026).
   Er stand bei 550x220 px mitten auf der Buehne und war damit das groesste
   Element eines Schirms, auf dem er am wenigsten zu sagen hat: er zeigt, DASS
   Herold da ist, nicht WORUM es geht. Als schmale Linie ueber dem Band sagt
   er dasselbe und nimmt den Karten nichts weg.
   `aspect-ratio` faellt dabei: bei 44 px Hoehe waere die Linie ueber 5:2 noch
   110 px breit — eine Briefkarte statt einer Tonspur. Die Breite folgt jetzt
   dem Band darunter, gedeckelt wie es. */
#kern {
  height: 44px; width: min(440px, 100%);
  display: block; flex: none;
  /* DER AVATAR DARF DAS BAND NICHT UEBERDECKEN, AUCH NICHT HALB — er ist das
     Lebenszeichen, der Satz ist der Inhalt. Beide stehen im Fluss, koennen
     sich also nicht ueberlagern; der reservierte Abstand steht am Band
     (`.gespraech`, margin-top) und nicht hier, damit er auch dann gilt, wenn
     die Welle aus dem Bild genommen wird (`body.verlauf-offen`). */
}
/* DIE KUGEL GIBT AUF DEM GROSSEN SCHIRM NICHTS MEHR AB.
   Hier stand `height: min(14vh, 120px)`, damit die Fenster weiter oben
   beginnen. Gemessen war das unnoetig: die Fensterzeile passt in die Luft
   unter dem Text, ohne dass jemand Platz abgibt (siehe `.buehne`). Die Kugel
   schrumpfte also um 100 px fuer nichts — und sie IST die Anzeige, an der
   man sieht, was Herold gerade tut.
   Auf dem Telefon bleibt das Schrumpfen: dort stehen die Fenster
   untereinander im Fluss und brauchen die Hoehe wirklich (820-px-Query). */

/* `flex: none` ist der eigentliche Fix: der Dialogblock nimmt sich die Höhe, die
   sein Inhalt braucht, statt sie sich zuteilen zu lassen. `min-height` bleibt — es
   hält die Zeilen ruhig, solange noch nichts gesagt wurde, damit die Kugel nicht bei
   jedem Satz springt. `padding-bottom` entfiel mit dem Vorlese-Knopf; die
   Antwortzeile selbst ist klickbar. */

/* DAS BAND — ein Untertitel, kein Absatz.
   `min-height` faellt von 5,5em auf 0: die Hoehe hielt die Zeilen ruhig,
   solange die Kugel darueber bei jedem Satz sprang. Die Linie springt nicht
   mehr (feste 44 px), und ein Band, das leer 5,5 Zeilen hoch ist, schiebt die
   Karten grundlos nach oben.
   Breiter als vorher (960 statt 720): ein Untertitel darf lang und flach
   sein — er soll in EINER Zeile stehen, nicht in dreien. */
.gespraech {
  width: min(960px, 100%); text-align: center; margin: 14px 0 2px;
  min-height: 0; flex: none; position: relative;
  /* Spalte, damit der Lauf im aufgeklappten Zustand (siehe unten) den Platz
     nehmen kann, den die Kugel freigibt. Im Normalzustand aendert es nichts:
     das einzige Flex-Kind ist der Lauf, und der waechst nicht von selbst. */
  display: flex; flex-direction: column;
}

/* DER LAUF — was gesagt wurde, von oben nach unten, und man kann zurück.
   Bis zum 19.09.2026 hielt die Bühne genau EINEN Zug: zwei Absätze, die der
   nächste Zug überschrieb. Der Verlauf lag dabei die ganze Zeit bereit — der
   Dienst schreibt ihn nach `verlauf.jsonl` und schickt ihn beim Verbinden mit
   — nur nahm das HUD daraus den letzten Nutzer- und den letzten Herold-Satz
   und warf den Rest weg.

   GEDECKELT WIRD JETZT DIE FLÄCHE, NICHT DER TEXT. Vorher trugen beide Zeilen
   eine `-webkit-line-clamp`, weil `.gespraech` sonst bis in den Fuß wachsen
   und sich über das Eingabefeld malen konnte. Die Klammer kostete aber genau
   das, was hier gebraucht wird: eine lange Antwort brach mit „…" ab, und das
   Abgeschnittene war weg. Ein Deckel plus `overflow-y: auto` hält den Fuß
   genauso frei und macht den Rest erreichbar, statt ihn wegzuschneiden.

   IM RUHEZUSTAND IST NUR DER LETZTE ZUG ZU SEHEN — sonst nichts. Die HOEHE
   des Laufs setzt deshalb `hud.js` auf die Hoehe des lebenden Paares; was
   darueber liegt, steht ueber der Kante und nicht halb im Bild. Ein fester
   Wert kann das nicht leisten: eine einzeilige Antwort und ein Absatz
   brauchen nicht dieselbe Flaeche, und wo die Flaeche zu gross ist, lugt der
   Verlauf hervor.

   DER DECKEL DARUEBER IST NICHT FREI GEWAEHLT: er ist genau das, was der alte
   geklammerte Block schon beanspruchen durfte — drei Nutzer-Zeilen (3 x 21 px
   plus 20 px Abstand) und vier Herold-Zeilen (4 x 26 px) sind 187 px. Damit
   kostet der Lauf die Kugel auch im schlimmsten Fall keinen Pixel, den eine
   lange Antwort sie vorher nicht auch gekostet hat (sie ist das Element mit
   `flex-shrink`, und gemessen bei 1440x900 stand sie vorher bei 324 px). Bei
   einem kurzen Zug bleibt sie groesser als vorher.

   KEIN BALKEN IM RUHEZUSTAND. Er zeigte etwas an, das gerade niemanden
   angeht, und verriet nebenbei, dass da oben noch etwas liegt — die Buehne
   soll ruhig aussehen, nicht nach Fenster. Gescrollt werden kann trotzdem:
   der Inhalt ragt hinaus, das Rad greift, und genau dieser erste Zug nach
   oben klappt den Verlauf auf (siehe unten). Erst dann gibt es einen Balken,
   und dann gehoert er auch hin. */
.lauf {
  /* DER DECKEL WAR 62 PX, UND DAS WAR ZU WENIG — Marco am 22.09.2026 mit
     Bild: „Und das hier sieht jetzt auch scheisse aus." Eine dreizeilige
     Antwort passte nicht mehr, und weil `laufAnpassen` die Hoehe des lebenden
     Paares als `height` setzt, schnitt der Deckel die OBERSTE Zeile
     waagerecht durch — direkt unter der Avatar-Welle, die 4 px darueber lag.
     Ein angeschnittener Satz ist schlimmer als ein gekuerzter.
     124 px sind die Nutzerzeile samt ihrem Abstand und drei Herold-Zeilen.
     Damit bleibt es ein Band (vorher waren es 190), traegt aber die Antworten,
     die wirklich vorkommen. Was darueber hinausgeht, ROLLT weiter — das Rad
     greift, und der erste Zug nach oben klappt den Verlauf auf. */
  /* DER DECKEL LAESST EINE GANZE ANTWORT DURCH.
     124 px waren die Nutzerzeile plus DREI Herold-Zeilen. Marco am 23.09.2026
     mit Bild: „Herold hat vier Zeilen gesagt, aber ich sehe nur drei" — die
     vierte stand halb angeschnitten da. Die Zahl stammte aus einer Zeit, in
     der das Band ein schmaler Streifen bleiben sollte; seit Marco am selben
     Tag gesagt hat, die Kugel duerfe fuer die Nachrichten nach oben gehen,
     ist der Platz nicht mehr knapp, sondern verhandelbar. 260 px tragen die
     Nutzerzeile und rund neun Antwortzeilen, und die Kugel gleitet mit
     (siehe die Hoehen-Transition weiter unten). Was darueber hinausgeht,
     rollt weiter — der Zug nach oben klappt dann den Verlauf auf. */
  /* DER DECKEL IST DIE HAELFTE DER BUEHNE, NICHT EIN STREIFEN.
     Marco am 24.09.2026: „Es muss dynamisch Platz fuer mein Gesagtes und fuer
     sein eigenes machen, egal wie lang der Text ist." 30vh waren bei 860 px
     Fensterhoehe rund zwoelf Zeilen fuer BEIDE Saetze zusammen — ein langes
     Diktat plus Antwort rollte. Den Platz gibt die Kartenzeile darueber her
     (`minmax(0, 1fr)`), der Fuss mit dem Mikrofon bleibt frei. Ganz ohne
     Deckel schoebe ein sehr langer Satz den Fuss aus dem Bild. */
  max-height: min(55vh, 520px); overflow-y: auto;
  /* Das Rad soll am Ende des Laufs nicht die Seite darunter mitnehmen. */
  overscroll-behavior: contain;
  /* `position: relative`, damit `offsetTop` der Zeilen sich auf den Lauf
     bezieht — daran misst `hud.js` die Hoehe des lebenden Paares. */
  position: relative;
  /* DIE RINNE STEHT IMMER, AUF BEIDEN SEITEN.
     Marco am 23.09.2026: „Es springt jetzt nur ein stück zur seite wegen dem
     scrollbalken." Zu ist die Rollleiste 0 px breit, aufgeklappt 6 — der
     zentrierte Text ruecke beim Oeffnen also um die halbe Leiste zur Seite.
     `both-edges` und nicht das blanke `stable`: reserviert wird sonst nur
     rechts, und der mittig gesetzte Satz sitzt dauerhaft 3 px links von der
     Mitte. Zwei Rinnen kosten zwoelf Pixel Breite und halten die Mitte. */
  scrollbar-gutter: stable both-edges;
  /* `thin` UND NICHT `none`: bei `none` haette die Rinne nichts zu
     reservieren, und der Sprung waere zurueck. Unsichtbar wird sie ueber die
     FARBE — zu ist Griff wie Bahn durchsichtig, aufgeklappt bekommt der Griff
     seine Kontur. */
  scrollbar-width: thin;
  scrollbar-color: transparent transparent;
}
/* DIE AUSBLEND-MASKE AM OBEREN RAND IST WEG (23.09.2026, "Nimm die Maske ganz
   raus"). Sie sollte sagen „da ist noch etwas", ohne eine Schnittkante zu
   zeigen — und war zweimal genau das Gegenteil: erst 24 px breit ueber der
   ganzen Nutzerzeile, dann 12 px ueber Herolds Antwort, die ohnehin schon
   gekuerzt war. Beide Male sah sie aus wie ein Defekt statt wie ein Hinweis.
   Was sie sagen sollte, sagt inzwischen etwas anderes: Herolds Satz steht
   ungekuerzt unten, Marcos leise darueber, und der Zug nach oben klappt den
   Verlauf auf. Ein Hinweis auf etwas, das man ohnehin findet, ist keiner. */
/* Aufgeklappt bekommt der Griff seine Kontur. Nur die FARBE aendert sich,
   nie die Breite — die Breite ist es, die den Text verschoebe.
   Bewusst ohne `::-webkit-scrollbar`: wo `scrollbar-width` gilt, ignoriert
   Chrome die Pseudo-Elemente, und zwei Fassungen derselben Leiste sind eine
   zu viel. */
body.verlauf-offen .lauf { scrollbar-color: var(--rand) transparent; }

/* ══ DER SYSTEMSTART ══════════════════════════════════════════════════════

   Ein Startprotokoll, wie es ein Terminal schreibt — aber in Herolds Haut:
   dieselbe Karte wie ein Fenster (`.widget`: Flaechenfarbe, --rand, 14 px
   Radius, `backdrop-filter`, derselbe Schatten), derselbe Kopf in --akzent und
   Versalien, dieselben Statusfarben. Neu ist nur, was ein Terminal ausmacht:
   Festbreitenschrift, ein Marker je Zeile, Zeichen fuer Zeichen getippt und
   ein blinkender Cursor darunter.

   WAS DASTEHT, IST GEMESSEN. Die Zeilen nennen die Werte aus dem `hallo` und
   die Einstellungen dieses Geraets. Ein Fortschrittsbalken waere hier eine
   Luege: das HUD ist fertig, sobald das `hallo` da ist. Die zwei Sekunden
   kaufen nichts ausser der Ansage — und genau deshalb raeumt jede Nutzeraktion
   und jeder Zug des Gehirns sie sofort weg (`bootAbbrechen` in hud.js).

   Er SCHWEBT (wie die Fenster, eine Ebene darunter) und liegt nicht mehr im
   Gespraechskasten. Im Fluss nahm er dem einzigen Element Platz weg, das in
   `.mitte` nachgibt: gemessen am 19.09.2026 schrumpfte die Kugel bei 820x640
   auf 35 px. Schwebend kostet er keine Hoehe. */
.boot {
  position: fixed; inset: 0; z-index: 4;
  display: flex; align-items: center; justify-content: center;
  padding: 20px;
  /* Dieselbe Spalte, die auch die Kugel abgibt — sonst laege die Karte bei
     engem Fenster unter dem ersten Fenster, und die Fenster sind das, was
     waehrend des Starts einlaeuft. Unter 821 px ist die Variable 0, dort
     stapeln die Fenster ohnehin unter der Kugel. */
  padding-right: 20px;
  pointer-events: none;
}
.boot.geht { opacity: 0; transform: scale(.985); transition: opacity .32s ease, transform .32s ease; }
/* OHNE DIESE ZEILE GEHT DIE KARTE NIE WEG.
   `[hidden] { display: none }` steht im Stylesheet des BROWSERS, und jede
   Autoren-Regel mit `display:` schlaegt es — hier das `display: flex` darueber.
   Das HUD setzte `hidden` also brav, und die Karte stand weiter da: Kopf,
   Fassung, Prompt (gemeldet zu Fassung 0ff1c56, nur die Zeilen waren geleert).
   Jedes andere versteckbare Element hier hat seine eigene Regel — `.vorlesen`,
   `.stand`, `.wartet`, `.neu`, `.einstellungen`,
   `.token-dialog`. Diese eine fehlte. */
.boot[hidden] { display: none; }

.boot-karte {
  width: min(560px, 100%);
  background: var(--flaeche-92); border: 1px solid var(--rand); border-radius: 14px;
  padding: 16px 18px 14px;
  backdrop-filter: blur(12px);
  /* Derselbe Schatten wie an einem Fenster, plus ein Schimmer in der
     Akzentfarbe — das ist der Unterschied zwischen "Kasten" und "faehrt hoch". */
  box-shadow: 0 18px 40px var(--schatten-stark), 0 0 0 1px var(--schimmer-akzent), 0 0 60px var(--schimmer-akzent-stark);
  position: relative; overflow: hidden;
  animation: boot-karte-auf .34s cubic-bezier(.2, .7, .3, 1) both;
}
@keyframes boot-karte-auf {
  from { opacity: 0; transform: translateY(10px) scale(.97); }
  to { opacity: 1; transform: none; }
}
/* Zeilenraster wie auf einer Roehre — 3 % Deckkraft, mehr wuerde den Text
   ausfransen. Es liegt UEBER dem Inhalt und faengt nichts ab. */
.boot-karte::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: repeating-linear-gradient(180deg, var(--boot-scanlinien) 0 1px, transparent 1px 3px);
}

.boot-kopf {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding-bottom: 10px; margin-bottom: 10px;
  border-bottom: 1px solid var(--rand);
}
.boot-marke {
  font-size: 12px; letter-spacing: .14em; text-transform: uppercase; color: var(--akzent);
  text-shadow: 0 0 12px var(--akzent-glow);
}
.boot-fassung { font-family: var(--mono); font-size: 11px; color: var(--text-leise); }

.boot-liste { display: flex; flex-direction: column; gap: 3px; }

.boot-zeile {
  font-family: var(--mono); font-size: 13px; line-height: 1.5;
  white-space: pre; display: flex; align-items: baseline; gap: 8px;
  opacity: 0;
}
.boot-zeile.da { opacity: 1; }

/* DER MARKER kommt erst, wenn die Zeile fertig getippt ist — vorher weiss
   niemand, wie sie ausgeht. Deshalb die Verzoegerung, nicht aus Zierde. */
.boot-marker { color: var(--akzent); opacity: 0; }
.boot-zeile.da .boot-marker { animation: boot-marker-an .26s ease .3s both; }
.boot-zeile.anders .boot-marker { color: var(--denkt); }
.boot-zeile.stoert .boot-marker { color: var(--fehler); }
@keyframes boot-marker-an {
  from { opacity: 0; text-shadow: none; }
  40% { opacity: 1; text-shadow: 0 0 14px currentColor; }
  to { opacity: 1; text-shadow: 0 0 6px var(--akzent-glow-schwach); }
}

/* Die Klasse heisst `anders` und nicht `wartet`: `.wartet` ist im Kopf die
   Pille „2 warten" (Rand, Radius, Polsterung), und eine Protokollzeile erbte
   sie mitsamt Kasten — im Bild sah das aus wie eine markierte Zeile.

   ZEICHEN FUER ZEICHEN. `steps()` auf die Breite geht nur mit
   Festbreitenschrift auf: `--n` ist die Zeichenzahl der Zeile in `ch`, und die
   stimmt nur, wenn jedes Zeichen gleich breit ist. */
.boot-text { display: inline-block; overflow: hidden; width: 0; color: var(--text-leise); }
.boot-zeile.da .boot-text { animation: boot-tippen .3s steps(30, end) both; }
@keyframes boot-tippen { to { width: var(--n); } }
.boot-wert { color: var(--text); }
.boot-zeile.anders .boot-wert { color: var(--denkt); }
.boot-zeile.stoert .boot-wert { color: var(--fehler); }

.boot-eingabe {
  display: flex; align-items: center; gap: 8px;
  margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--rand);
  font-family: var(--mono); font-size: 13px; color: var(--text-leise);
  opacity: 0; transition: opacity var(--dauer) ease;
}
.boot.fertig .boot-eingabe { opacity: 1; }
.boot-cursor {
  display: inline-block; width: .6em; height: 1.05em;
  background: var(--akzent); box-shadow: 0 0 10px var(--akzent-glow);
  animation: boot-blinken 1.05s step-end infinite;
}
@keyframes boot-blinken { 0%, 50% { opacity: 1; } 50.01%, 100% { opacity: 0; } }

/* Die Kugel tritt zurueck, solange das Protokoll laeuft, und faehrt danach
   wieder auf — sie ist der Dienst, der gerade hochkommt. Eine eigene Keyframe,
   weil `#kern` seine `height`-Transition fuer den aufgeklappten Verlauf haelt. */
/* DER DIALOG TRITT ZURUECK, SOLANGE DAS PROTOKOLL LAEUFT.
   Ohne das steht der letzte Zug von vorhin hinter der Karte und liest sich
   durch sie hindurch — am 19.09.2026 live fotografiert. Auf einem frischen
   Dienst faellt es nicht auf, weil dort kein Verlauf liegt; genau deshalb
   steht der Satz hier und nicht nur im Kopf.
   Ausgeblendet, nicht weggeschoben: sonst spraenge der Kasten in der Hoehe.
   Der Lautsprecher gehoert dazu, er liegt absolut im selben Kasten. */
/* OHNE UEBERGANG HINEIN, MIT UEBERGANG HERAUS. Die Karte faehrt in 340 ms auf,
   der Dialog blendete in 300 ms ab — gemessen lagen beide 200 ms lang
   uebereinander, und in dieser Zeit las man den letzten Zug durch das
   Protokoll hindurch. Beim Zurueckkommen gilt die Transition wieder, dort
   soll nichts springen. */
body.bootet .lauf { opacity: 0; transition: none; }
/* DAS BAND WAECHST WEICH, UND DER AVATAR GEHT WEICH MIT.
   `#kern` ist `height: 44px; flex: none` — die Kugel schrumpft also gar nicht,
   sie wird vom wachsenden Band nach OBEN geschoben. Das geschah bisher in
   einem Bild, also als Sprung. `laufAnpassen` setzt dem Lauf eine ausgerechnete
   `height`; die zu animieren macht den ganzen Umbruch darueber gleitend, ohne
   dass irgendwo eine zweite Hoehe gepflegt werden muesste.
   JA, DAS IST EINE LAYOUT-EIGENSCHAFT. Es laeuft aber nicht dauernd, sondern
   genau dann, wenn sich der gesagte Satz aendert — ein Uebergang von 280 ms,
   kein Dauerlaeufer wie eine Kartenbahn. */
.lauf { transition: opacity var(--dauer-lang) ease, height var(--dauer) var(--kurve-raus); }
@media (prefers-reduced-motion: reduce) {
  .lauf { transition: opacity var(--dauer-lang) ease; }
}

body.bootet #kern { animation: kern-hoch 1.1s cubic-bezier(.2, .7, .3, 1) both; }
@keyframes kern-hoch {
  from { opacity: .25; transform: scale(.88); }
  60% { opacity: .5; }
  to { opacity: 1; transform: none; }
}

@media (max-width: 520px) {
  .boot-karte { padding: 13px 14px 12px; }
  .boot-zeile, .boot-eingabe { font-size: 11.5px; }

  /* Mobiler Header: schlanker und innerhalb des Viewports. */
  .kopf {
    padding: 10px 14px; font-size: 12px;
    flex-wrap: wrap; gap: 8px;
  }
  .kopf-links { gap: 8px; max-width: 100%; }
  #status-text {
    max-width: calc(100vw - 180px);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .kopf-rechts { display: flex; align-items: center; gap: 6px; margin-left: auto; }
  .stand, .wartet, .neu { font-size: 10px; }
  .pause { width: 28px; height: 28px; padding: 0; justify-content: center; }
  .pause span { display: none; }
  #uhr { font-size: 12px; }
}

/* WER KEINE BEWEGUNG WILL, BEKOMMT KEINE — und dann gibt es die Sequenz gar
   nicht erst (hud.js springt ans Ende). Die Regeln bleiben als Netz. */
@media (prefers-reduced-motion: reduce) {
  .boot-karte, .boot-zeile.da .boot-text, .boot-zeile.da .boot-marker,
  .boot-cursor, body.bootet #kern { animation: none !important; }
  .boot-text { width: auto !important; }
  .boot-marker { opacity: 1; }
  .boot.geht, .lauf { transition: none !important; }
  /* Der Kopfhoerer springt dann an seinen Platz, statt zu fahren — die
     Endzustaende sind dieselben, nur ohne den Weg dazwischen.
     `!important` ist hier Pflicht und keine Bequemlichkeit: dieser Block
     steht WEIT VOR `.mikro-neben` in derselben Datei, und eine Media-Query
     bringt keine Spezifitaet mit — ohne das Ausrufezeichen gewinnt schlicht
     die spaetere Regel, und die Bewegung liefe weiter. Genau so gemessen. */
  .mikro-neben {
    transition: color var(--dauer-kurz), border-color var(--dauer-kurz), background var(--dauer-kurz) !important;
  }
}


/* HOCHGESCROLLT: DIE KUGEL GEHT GANZ WEG.
   Sie kleiner werden zu lassen war der halbe Schritt — wer im Verlauf liest,
   liest, und eine Kugel, die dabei ein Drittel der Buehne haelt, ist nur noch
   im Weg. Also gibt sie beim Hochscrollen ihre ganze Hoehe ab und kommt
   zurueck, sobald wieder das Gespraech dran ist.

   BEMESSEN WIRD DABEI NICHTS. Der aufgeklappte Lauf hat keinen eigenen
   Deckel, sondern nimmt, was die Buehne hergibt (`flex: 1 1 auto` an beiden,
   `min-height: 0`, damit der Flex-Algorithmus unter die Inhaltsgroesse darf).
   Ein fester Wert waere hier falsch: 52vh passen bei 1440x900 bequem, sind
   auf einem 800 px hohen Telefon aber schon 416 px und damit mehr, als
   zwischen Kopf und Fuss ueberhaupt frei ist — der Block liefe in den Fuss.

   Und weil die Kugel ihre Hoehe weich abgibt, waechst der Lauf mit ihr
   mit; er braucht selbst keinen Uebergang. Ein `max-height`-Uebergang von
   190 px auf einen grossen Wert laeuft ohnehin ungleichmaessig ab. */
#kern { transition: height var(--dauer) ease, opacity var(--dauer) ease; }

/* ══ DER AUFGEKLAPPTE VERLAUF IST EIN OVERLAY ═════════════════════════════

   Marco am 23.09.2026 mit Bild: „Wenn ich den verlauf aufmache, springt alles
   nach oben, es soll eher ein overlay sein und die widgets bleiben wo sie
   sind."

   Genau das tat die alte Fassung, und sie tat es mit Absicht: die Kugel gab
   ihre Hoehe ab (`#kern { height: 0 }`), Band und Lauf wuchsen in den frei
   gewordenen Streifen, und ein Deckel an `.mitte` sollte verhindern, dass die
   Kartenzeile dabei auf null faellt. Das Ergebnis war trotzdem, dass sich beim
   Nachlesen die halbe Buehne neu sortiert — die Karten rutschen nach oben und
   werden oben angeschnitten, und man sucht die Stelle wieder, an der man war.

   NACHLESEN IST EIN BLICK ZUR SEITE, KEIN MODUSWECHSEL. Der Lauf legt sich
   deshalb als Flaeche UEBER die Buehne und nimmt niemandem Platz weg: absolut
   im `.gespraech` (das traegt `position: relative`), unten verankert, nach
   OBEN wachsend. Kugel, Karten und Mikrofon bleiben, wo sie sind.

   DIE HOEHE DES BANDES MUSS DABEI STEHEN BLEIBEN. Nimmt man den Lauf aus dem
   Fluss, faellt `.gespraech` auf null und das Mikrofon rutscht hoch — also
   genau der Sprung, um den es geht, nur andersherum. `verlaufOeffnen` misst
   das Band deshalb VOR dem Aufklappen und haelt es als `min-height` fest.

   `z-index: 8` liegt ueber der Kartenzeile (6) und unter dem FAB (12) und
   jedem Dialog (20 und hoeher). */
body.verlauf-offen .lauf {
  /* `bottom: -14px` HEBT DAS EIGENE POLSTER AUF. Die Flaeche braucht unten
     Luft zum Rand, der lebende Satz darf davon aber nichts merken: mit
     `bottom: 0` saesse er 14 px hoeher als eben noch, und das ist der Sprung,
     den man sieht. Dasselbe waagerecht, dort schon seit dem ersten Wurf
     (`margin: 0 -18px` gegen `padding: … 18px`). */
  /* DIE VERSCHIEBUNGEN RECHNEN DEN RAHMEN MIT.
     Polster 14/18 plus 1 px Rahmen — im Ruhezustand hat der Lauf beides
     nicht. Ohne das eine Pixel Zugabe sitzt der lebende Satz aufgeklappt um
     genau 1 px hoeher und 1 px weiter rechts; gemessen war es auch genau das.
     `box-sizing: border-box` gilt global, der Rahmen liegt also innen. */
  position: absolute; left: 0; right: 0; bottom: -15px; z-index: 8;
  max-height: min(70vh, 640px); min-height: 0;
  padding: 14px 18px; margin: 0 -19px;
  /* WEICH HEREIN, NICHT EINFACH DA. Marco am 23.09.2026: „Es soll einfach
     weich reinfaden und nicht einfach nur erscheinen." Opazitaet und ein
     kurzer Weg von unten — beides auf dem Compositor, also billig; `height`
     oder `top` zu animieren kostete ein Layout je Bild. */
  animation: verlauf-auf var(--dauer) var(--kurve-raus) both;
  background: color-mix(in srgb, var(--grund) 88%, transparent);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  border: 1px solid var(--rand); border-radius: 16px;
  box-shadow: 0 18px 48px rgba(0, 0, 0, .18);
}
/* UND WEICH WIEDER HINAUS (23.09.2026) — kuerzer als herein, wer zuklappt,
   ist fertig. hud.js setzt `.verlauf-geht` fuer 180 ms und nimmt erst danach
   `verlauf-offen` ab; ohne das war die volle Flaeche im naechsten Bild weg.
   NICHT DER GANZE LAUF BLENDET AB, NUR SEINE FLAECHE UND DIE HISTORIE. Der
   lebende Satz steht im Overlay genau da, wo er im Band steht (`bottom:
   -14px` oben); blendete er mit aus, waere er 180 ms lang weg und stuende
   danach schlagartig wieder da — ein Blinzeln statt eines Schliessens. */
body.verlauf-geht .lauf { animation: verlauf-zu var(--dauer-kurz) ease-in both; }
@keyframes verlauf-zu {
  to { background-color: transparent; border-color: transparent; box-shadow: none;
       backdrop-filter: blur(0); -webkit-backdrop-filter: blur(0); }
}
body.verlauf-geht .verlauf { animation: verlauf-zu-inhalt var(--dauer-kurz) ease-in both; }
@keyframes verlauf-zu-inhalt { to { opacity: 0; transform: translateY(6px); } }
@media (prefers-reduced-motion: reduce) {
  body.verlauf-geht .lauf, body.verlauf-geht .verlauf { animation: none; }
}
@keyframes verlauf-auf {
  from { opacity: 0; transform: translateY(10px) scale(.995); }
  to   { opacity: 1; transform: none; }
}
/* Wer Bewegung abbestellt hat, bekommt sie nicht — die Flaeche blendet dann
   nur auf, statt zu wandern. */
@media (prefers-reduced-motion: reduce) {
  body.verlauf-offen .lauf { animation: verlauf-auf-ruhig var(--dauer-kurz) ease both; }
  @keyframes verlauf-auf-ruhig { from { opacity: 0; } to { opacity: 1; } }
}

/* FRÜHERE ZÜGE TRETEN ZURÜCK.
   Kleiner und leiser als der laufende Zug — sonst sieht die Bühne beim ersten
   Blick aus wie eine Liste und man sucht, was gerade gilt. Der Abstand nach
   unten trennt den Verlauf vom lebenden Paar; leer ist er nicht da. */
.verlauf:empty { display: none; }
.verlauf { margin: 0 0 18px; }
.verlauf-zug { margin: 0 0 14px; opacity: .55; }
.verlauf-zug:last-child { margin-bottom: 0; }
.verlauf-nutzer {
  color: var(--text-leise); font-size: 13px; line-height: 1.4; margin: 0 0 4px;
}
.verlauf-nutzer:not(:empty)::before { content: "„"; }
.verlauf-nutzer:not(:empty)::after { content: "“"; }
.verlauf-herold {
  color: var(--text); font-size: 14px; line-height: 1.45; margin: 0;
}
.verlauf-nutzer:empty, .verlauf-herold:empty { display: none; }

/* DER KNOPF BEKOMMT EINEN EIGENEN STREIFEN — aus zwei Gründen, die einander
   widersprechen, wenn man nur einen davon bedenkt:

   Im FLUSS stehen darf er nicht: er kommt und geht mit dem Text, und die Kugel
   ist das Element, das Platz abgibt (`#kern`, siehe oben). Sie schrumpfte dann
   bei jeder Antwort um seine Höhe und wüchse danach wieder.

   ÜBER dem Text liegen darf er aber auch nicht. Genau so stand er zuerst,
   absolut in der unteren rechten Ecke, „weil die Zeile zentriert ist und mit
   `text-wrap: balance` selten bis an den Rand läuft". Gemessen an einer vollen
   Antwort überdeckte er sichtbare Schrift bei 1440×900 (1296 px²), 900×900
   (1180 px²), 821×1200 (1568 px²) und 420×800 (1081 px²) — sauber war
   ausgerechnet nur 1100×900, die Breite, an der man es zuerst ansieht.

   Also beides: absolut positioniert (keine Höhenänderung) in einem dauerhaft
/* DIE ZWEI SPRECHER.
   Was Marco gesagt hat, steht leise und in Anführungszeichen oben, Herold darunter
   in voller Schriftfarbe. Getrennt werden die beiden allein durch Abstand, Farbe und
   die Anführungszeichen — hier stand bis zum 18.09.2026 eine Haarlinie dazwischen,
   Marco braucht sie nicht. Der Abstand trägt die Trennung jetzt allein und ist
   deshalb größer als die 13 px, die er neben der Linie hatte.
   Begrenzt waren BEIDE Zeilen, und zwar aus demselben Grund: solange der Block
   unbegrenzt wachsen kann, kann er den Fuss erreichen. Die Nutzer-Zeile waechst
   beim Sprechen live mit dem Zwischenergebnis mit — das war der Fall, in dem die
   Ueberlappung auftrat. Seit dem 19.09.2026 haelt den Fuss der Deckel auf `.lauf`
   frei (siehe oben), und die Klammern sind deshalb weg: was ueber den Deckel
   hinausgeht, ist erscrollbar statt abgeschnitten. */
/* DIE FRAGE BLEIBT LESBAR, AUCH WENN DIE ANTWORT LANG IST —
   aber ohne Kasten. Hier klebte sie kurz oben fest, mit einem hellen
   Hintergrund darunter, damit die Antwort nicht durchscheint. Das sah aus wie
   ein aufgelegtes Rechteck mit harten Kanten, und die Antwort verschwand
   halb darunter. Geloest wird es jetzt eine Ebene tiefer: der Lauf scrollt
   nicht mehr ans ENDE, sondern an den ANFANG der Frage (siehe `nachUnten` in
   hud.js). Damit steht sie oben, ganz, und nichts liegt uebereinander. */
.nutzer-zeile {
  color: var(--text-leise); font-size: 15px; line-height: 1.4; margin: 0 0 20px; min-height: 1.4em;
}
.nutzer-zeile:not(:empty)::before { content: "„"; }
.nutzer-zeile:not(:empty)::after { content: "“"; }
.nutzer-zeile.vorlaeufig { font-style: italic; opacity: .7; }

/* KLEINERE SCHRIFT, UND KEIN ABGESCHNITTENES ENDE MEHR.
   Vorher: `clamp(17px, 2.2vh, 22px)` — 19,8 px gegen 15 px in der Nutzer-Zeile, ein
   Sprung um ein Drittel. Danach stand hier `max-height` + `overflow: hidden`:
   das schnitt auf dem Telefon 133 von 215 px Text ab, ohne ein einziges Zeichen, das
   davon erzählt hätte. Die Zeilenbegrenzung kürzte an derselben Stelle, setzte aber
   ein „…“. Seit dem 19.09.2026 wird gar nicht mehr gekürzt: der Text steht
   vollständig im scrollbaren Lauf, und das „…“ hatte ohnehin nur gesagt, dass
   etwas fehlt — nicht was. */
/* ENTWEDER ER ODER HEROLD — siehe `bandRegie` in hud.js, dort steht das Warum.
   Kein `visibility`, sondern `display: none`: die stumme Zeile soll auch ihren
   Platz hergeben, sonst bleibt im Band eine Luecke stehen, wo eben noch ein
   Absatz stand. Ohne `data-zug` (ganz frisch geladen) gilt nichts davon und
   beide stehen da wie bisher. */
/* DER AKTIVE SATZ STEHT UNTEN, DIREKT UEBER DEM MIKROFON.
   Vom 23.09.2026 morgens bis mittags war die stumme Zeile schlicht
   `display: none` — auf Marcos "entweder spreche ich, oder Herold". Danach
   hat er es praeziser gefasst: waehrend er auf eine Antwort ANTWORTET, will
   er beides sehen, denn er bezieht sich ja darauf; verschwinden soll Herolds
   Satz erst im Moment des Abschickens. Das Verschwinden IST die Quittung.
   Vier Zustaende also, und `order` traegt sie: wer gerade dran ist, steht
   unten. Im Markup steht die Nutzerzeile oben — genau deshalb wird gedreht,
   wenn er spricht, und nicht umgekehrt.
   Der andere wird nicht versteckt, sondern LEISE UND EINZEILIG: sein
   diktierter Absatz war ueber 400 Zeichen lang und drueckte ungebremst jede
   Antwort aus dem Streifen (gemessen 72-80 px Luft zwischen Avatar und
   Mikrofon, je nach Fensterhoehe). */
.lauf { display: flex; flex-direction: column; }
/* `flex: 0 0 auto` IST HIER KEIN SCHMUCK. `laufAnpassen` setzt dem Lauf eine
   feste Hoehe (die des lebenden Zuges); ein Flex-Kind mit dem voreingestellten
   `flex-shrink: 1` wird darin zusammengedrueckt, und zwar das obere zuerst.
   Gemessen am 23.09.2026: Herolds Zeile hatte Text, `scrollHeight` sagte 41 px
   — und die Zeile selbst stand auf HOEHE 0, war also spurlos weg. Sie soll
   ueberstehen und den Lauf rollen lassen, nicht schrumpfen. */
.lauf > * { flex: 0 0 auto; }
.verlauf { order: 0; }
.nutzer-zeile { order: 1; }
.herold-zeile { order: 2; }
/* HEROLDS SATZ STEHT IMMER UNTEN. Die Reihenfolge wird nicht mehr gedreht —
   siehe die Regel darunter: seine Zeile ist die eine, die immer ganz lesbar
   sein muss, und unten kann die Ueberlauf-Maske sie nicht anschneiden (die
   frisst von OBEN). */

/* Eine leere Zeile darf keinen Platz nehmen — sonst steht im Ruhezustand ein
   unsichtbarer Absatz zwischen Avatar und Antwort. */
.nutzer-zeile:empty, .herold-zeile:empty { display: none; }

/* DIE NICHT AKTIVE ZEILE: eine Zeile, leiser, mit Ellipse. */
/* BEIDE ZEILEN STEHEN GANZ DA — KEINE WIRD MEHR GEKUERZT.
   Marco am 23.09.2026: „Ich muss unten unter dem Avatar immer meine eigene zu
   letzt gesprochene Nachricht sehen und darunter … die Antwort vom Avatar."
   Das ist die dritte Fassung dieser Regel an einem Tag, und alle drei gingen
   in dieselbe Richtung: erst war die stumme Zeile ganz weg (`display: none`),
   dann wurde die nicht aktive gekuerzt, dann nur noch seine eigene — jetzt
   keine. Der Unterschied zwischen den beiden traegt ohnehin schon die Form:
   seine Zeile steht in Anfuehrungszeichen und kleiner, Herolds Antwort
   darunter in voller Groesse. Was der Platz nicht hergibt, holt sich das Band
   vom Avatar (siehe die Hoehen-Transition an `.lauf`). */
/* DER WECHSEL BLENDET UEBER (23.09.2026). Wer das Wort bekommt, wird in
   280 ms laut, wer es abgibt, ebenso leise — statt dass beide im selben Bild
   umschalten. Die Lage tauscht weiterhin sofort (`order` hat keinen Weg);
   dass es nicht springt, tragen die kurze Gleitbewegung der neuen Zeile
   (`bandRegie` in hud.js) und die Welle, die ihrer Lage nachfolgt. */
.nutzer-zeile, .herold-zeile { transition: opacity var(--dauer) ease; }
@media (prefers-reduced-motion: reduce) { .nutzer-zeile, .herold-zeile { transition: none; } }

/* DAS DENK-ZEICHEN IST WEG (23.09.2026, am selben Tag gebaut und wieder
   entfernt). Drei Punkte zwischen dem Abschicken und dem ersten Wort — Marco:
   „Die Denkpunkte brauchen wir doch eigentlich gar nicht, weil wir doch dieses
   rote animierte Avatar-Linien-Zeug haben, wenn Herold am Nachdenken ist. Also
   ist das irgendwie doppelt gemoppelt." Stimmt und ist im Code nachzulesen:
   `zielFarbe()` schaltet bei `gehirn === "denkt"` oder `"werkzeug"` auf
   `farben.denkt`, die Kugel sagt es also laengst — und sie sagt es an der
   Stelle, auf die man ohnehin schaut. */

.herold-zeile {
  font-size: clamp(15px, 1.7vh, 18px); line-height: 1.45; margin: 0;
  text-wrap: balance;
  cursor: pointer;
  -webkit-user-select: text; user-select: text;
}

.herold-zeile:hover,
.herold-zeile:focus-visible {
  color: var(--hoert);
  outline: none;
}
.herold-zeile.fehler { color: var(--fehler); }
/* ABGEWUERGT: der Text bleibt LESBAR stehen — was Marco nicht hoeren wollte,
   will er vielleicht ueberfliegen. Gesagt wird es mit einem Wort statt mit
   einem Strich: die Zeile ist zentriert, eine Kante links stuende weit weg
   vom Text. Ein angehaengtes Wort teilt sich den Fluss mit dem Text und
   laeuft mit ihm um. */
.herold-zeile.abgebrochen { opacity: .75; }
.herold-zeile.abgebrochen::after {
  content: " · abgebrochen"; font-size: 12px; color: var(--text-leise); white-space: nowrap;
}

/* DER SLIDER — DIE OFFENE ARBEIT, RECHTS NEBEN DEM ANTWORTSATZ.
   Bis zum 21.09.2026 war das eine schwebende Ebene (`position: fixed`), auf
   der jedes Fenster seine eigene, gemerkte Lage hatte. Das passte zu ein,
   zwei kurzlebigen Fenstern; als Arbeitsliste war es schaedlich — die
   Fenster ueberlappten, und ihre Reihenfolge war z-Order statt Alter.

   Danach stand hier ein mehrzeiliges `auto-fill`-Raster. Es fuellte von
   links oben, und genau das war der Einwand: bei zwei Fenstern klebten sie
   in der linken Ecke, waehrend der Antwortsatz darueber mittig stand. Zwei
   Achsen in einem Bild, und keine davon erklaert die andere.
   Dann war es EINE waagerechte Zeile unter dem Text — und seit dem
   22.09.2026 ist es EINE SENKRECHTE SPALTE rechts daneben (siehe `.buehne`).

   `safe center` IST DER GANZE TRICK, und er gilt jetzt auf der senkrechten
   Achse. Blosses `center` zentriert auch dann noch, wenn der Inhalt hoeher
   ist als die Flaeche — dann ragt er oben UND unten hinaus, und der obere
   Ueberhang ist nicht mehr erscrollbar, weil es ueber dem Scroll-Ursprung
   nichts zu scrollen gibt. `safe` faellt bei Ueberlauf auf `flex-start`
   zurueck: wenige Fenster stehen mittig auf der Hoehe des Antwortsatzes,
   viele beginnen oben und laufen nach unten weiter.

   KEINE GASSEN MEHR AM SLIDER. Solange er unter dem Text lag, musste er sich
   auf BEIDEN Seiten die breitere der beiden festen Spalten freihalten, damit
   seine Mitte unter der Mitte des Antwortsatzes sass. Jetzt IST er die rechte
   Spalte; die Achse haelt die Phantomgasse der Buehne, nicht mehr ein
   Aussenabstand hier. */
/* EINE ZEILE, DIE SEITLICH BLAETTERT — SEIT DEM 22.09.2026.
   Marco nach der Abnahme des Rasters: „Die Karten sind zu breit. Und es muss
   ein Slider sein." Gemessen war das Raster bei 1600 px zwei Karten zu je 528
   in einer 1068-px-Bahn: die halbe Buehne fuer einen Titel und drei Merkmale.

   UND UNTER 1440 WAR ES SCHLIMMER, NICHT BESSER. `auto-fit` mit einer
   420-px-Spur haelt zwei Spalten nur, solange 2x420 plus 12 Luecke in die
   Bahn passen — unter 852 px Bahnbreite faellt es auf EINE Spur zurueck, und
   die dehnt sich ueber alles. Live nachgemessen am Rasterstand:

     1600 px Fenster → Bahn 1068, zwei Karten zu 528
     1440 px Fenster → Bahn  908, zwei Karten zu 448
     1280 px Fenster → Bahn  748, EINE Karte zu 748   ← ein Banner
     1100 px Fenster → Bahn  568, EINE Karte zu 568

   Die 748 sind der eigentliche Befund: nicht der 528er-Fall war zu breit,
   sondern sein Nachbar eine Stufe darunter.

   `flex` UND KEIN RASTER. Ein Raster verteilt, was da ist; ein Slider fuehrt
   eine Reihe an der Kante vorbei. `flex: 0 0 min(340px, 100%)` an der Karte
   (siehe `.widget`) ist der Unterschied: die Karte hat eine BREITE, statt
   eine Spur zu fuellen. 340 px, weil bei 1068 px Bahn drei nebeneinander
   aufgehen (3x340 + 2x12 = 1044). Vier waeren 250 px — und dann waechst der
   Aufgabentitel von zwei auf drei bis vier Zeilen und holt sich in der Hoehe
   zurueck, was die Karte in der Breite spart.

   DAS `min(…, 100%)` IST DIE SICHERUNG VON GESTERN AUF DER NEUEN ACHSE.
   Ohne sie waere 340 px keine Wunschbreite, sondern eine feste — und
   zwischen 821 und 960 px Fensterbreite ist die Bahn 328 bis 428 px schmal,
   weil die Themenliste dort noch ihre Gasse nimmt. Genau dort schnitt der
   Rasterstand die Karte an beiden Seiten ab (gemessen 830 px Fenster: Bahn
   298, Karte 420, 61 px Ueberhang). Das Loch liegt exakt zwischen den zwei
   Breiten, die man normalerweise prueft.

   `justify-content: safe center` LOEST `align-content: safe center` AB, und
   der Grund wandert mit der Achse. Blosses `center` zentriert auch dann noch,
   wenn die Reihe breiter ist als die Bahn — dann ragt sie links UND rechts
   hinaus, und der linke Ueberhang ist nicht mehr erscrollbar, weil es vor dem
   Scroll-Ursprung nichts zu scrollen gibt. `safe` faellt bei Ueberlauf auf
   `flex-start` zurueck: wenige Karten stehen mittig unter dem Antwortsatz,
   viele beginnen links und laufen nach rechts weiter.

   `align-items: flex-start` IST DER HORIZONT (Invariante 1). Karten sind
   verschieden hoch — 96 bis 220 px, je nach Inhalt. Mittig ausgerichtet
   schwaemmen sie auf drei verschiedenen Ober- und Unterkanten; an einer
   gemeinsamen Oberkante ist die Reihe eine Reihe. Und sie steht OBEN in der
   Zelle statt mittig: eine Reihe, die bei zwei Karten in die Mitte rutscht
   und bei fuenf nach oben springt, ist genau die Form, die mit dem Inhalt
   wechselt.

   `height: 100%` IST KEINE KOSMETIK, SONDERN DER DECKEL.
   Hier stand zuerst `height: auto; max-height: 100%`, damit die Reihe nur so
   hoch wird wie ihre hoechste Karte. Das hat den Hoehendeckel der Karte
   STILL AUSGEHEBELT: `max-height: min(var(--karte-hoch), 100%)` an `.widget`
   braucht eine aufloesbare Elternhoehe fuer sein `100%`, und an einem
   `auto`-hohen Flex-Container gibt es die nicht — das Prozent bleibt
   unbestimmt und der ganze `min()`-Ausdruck faellt weg. Lokal gemessen
   (22.09.2026, sechs Karten ueber elf Breiten): Karten von 244 bis 326 px
   statt der 220, bei 860 px Fenster war jede einzelne ueber dem Budget.
   Mit einer definiten Hoehe loest das Prozent wieder auf und der Deckel
   greift. Die Reihe steht trotzdem oben — dafuer sorgt `align-items`, nicht
   die Containerhoehe. */
.widgets {
  /* `relative`: eine Karte, die geht, liegt waehrend ihrer Abblende absolut
     ueber ihrer alten Stelle (`widgetWeg` in hud.js) — damit die Nachbarn
     schon zusammenruecken koennen, statt 380 ms spaeter zu springen. */
  position: relative;
  height: 100%;
  display: flex; flex-direction: row;
  /* SENKRECHT MITTIG IM RAUM UEBER DEM AVATAR (22.09.2026).
     Gemessen bei 1600x900: zwischen Kopfzeile (Unterkante 56) und
     Avatar (Oberkante 625) liegen 569 px, ihre Mitte bei 341 — die Karten
     standen bei 60…540, Mitte 300. Sie hingen also oben statt in diesem Raum
     zu stehen, 41 px daneben. Marco: "Vielleicht koennen die irgendwie auch
     noch zentriert werden ueber dem Avatar-Balken und dem oberen Rand."
     `safe center` und nicht `center`: ist eine Karte hoeher als die Bahn,
     faellt es auf `start` zurueck, statt oben UND unten abzuschneiden —
     dieselbe Vorsicht wie beim `justify-content` darunter. */
  align-items: safe center;
  justify-content: center;
  justify-content: safe center;
  gap: 12px;
  /* GLEICH OBEN WIE UNTEN, sonst ist die Mitte um die halbe Differenz
     daneben. Mit 4/14 blieben nach dem Zentrieren 5 px Versatz stehen —
     unsichtbar, aber messbar, und eine bekannte Ungenauigkeit kostet hier
     ein Zeichen. Die Summe bleibt dieselbe. */
  padding: 9px 0;
  min-width: 0;
  overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x proximity;
  overscroll-behavior-x: contain;
}
/* `center` UND NICHT MEHR `start` (24.09.2026). Hier stand „waagerecht liest
   man von links" — Marco hat es umgedreht: „bei 3 soll das Zweite in der
   Mitte sein … auch mit Scroll, Snapping." Die Mitte der Bahn ist die Achse
   der Buehne, also rastet die Karte dort ein, auf die der Blick faellt. Am
   Anfang und am Ende haelt die Bahn an ihrer Kante; eine leere halbe Bahn
   davor gibt es nicht. */
.widget { scroll-snap-align: center; }
/* Der Auslauf an beiden Enden, nur bei Ueberlauf (setzt `ueberlaufZeichnen`
   in hud.js): so weit, dass auch die erste und die letzte Karte mittig
   einrasten koennen. Passt alles hinein, zentriert `justify-content`. */
.widgets.laeuft-ueber { padding-inline: max(0px, calc(50% - var(--karte-breit) / 2)); }

/* ══ TIEFE: WAS AUSSEN STEHT, STEHT HINTEN ═══════════════════════════════

   Marco am 23.09.2026, nach der Storyline-Seite von SBS: „so ein geiles orb
   effect mit Umschauen und anklicken einzelner widgets". Die Kugel selbst ist
   verworfen — Herolds Karten sind Text, und Text auf einer gekrümmten Fläche
   liest man nicht. Was den Reiz dort ausmacht, ist aber gar nicht die Kugel,
   sondern Trägheit und Tiefe, und beides geht auf der vorhandenen Bahn.

   SCROLL-GETRIEBEN UND OHNE EINE ZEILE JAVASCRIPT je Bild: `animation-timeline:
   view(inline)` bindet den Ablauf an die Sichtbarkeit der Karte in der Bahn.
   Eine Fassung mit Rechnung je Bild wäre teurer als der Effekt wert ist.

   `scale` UND `filter: opacity()` — NICHT `transform` und `opacity`. Der
   Auftritt der Karte laeuft ueber Transitions auf genau diesen beiden
   (`.widget.sichtbar`, `.widget.geht`); zwei Regeln auf derselben Eigenschaft
   und die spaetere gewinnt, die Karte spraenge beim Hereinkommen. `scale` ist
   seit langem eine eigene Eigenschaft und wird MIT dem `transform` verrechnet,
   `filter: opacity()` multipliziert sich mit der Deckkraft. Beide bleiben auf
   dem Compositor.

   0,85 und 0,35 hat Marco gewaehlt (die zurueckhaltende Fassung waere
   0,92/0,55 gewesen): deutlich raeumlich, und die Karte ganz aussen ist
   bewusst kaum noch zu lesen — sie ist ja auch nicht gemeint. */
@keyframes karte-tiefe {
  entry 0%   { scale: .85; filter: opacity(.35); }
  entry 100% { scale: 1;   filter: opacity(1); }
  exit 0%    { scale: 1;   filter: opacity(1); }
  exit 100%  { scale: .85; filter: opacity(.35); }
}
@supports (animation-timeline: view()) {
  .widget {
    animation: karte-tiefe linear both;
    animation-timeline: view(inline);
  }
  /* Tiefe ist Anordnung, nicht Bewegung — sie bleibt, nur flacher. */
  @media (prefers-reduced-motion: reduce) {
    @keyframes karte-tiefe-ruhig {
      entry 0%   { scale: .96; filter: opacity(.7); }
      entry 100% { scale: 1;   filter: opacity(1); }
      exit 0%    { scale: 1;   filter: opacity(1); }
      exit 100%  { scale: .96; filter: opacity(.7); }
    }
    .widget { animation-name: karte-tiefe-ruhig; }
  }
}

/* DASS ES WEITERGEHT, MUSS MAN SEHEN, OHNE ZU WISCHEN.
   Gemessen am 21.09.2026 in der ersten waagerechten Fassung: bei zwoelf
   Fenstern auf 1440 px passten VIER genau bis an die Kante — kein
   angeschnittenes Fenster, kein Hinweis, acht unsichtbar. Und die Scrollleiste
   hilft nicht: `offsetWidth - clientWidth` ist 0, sie liegt als Ueberlagerung
   ueber dem Inhalt und erscheint erst BEIM Wischen. Man muesste sie schon
   bedienen, um zu erfahren, dass es sie gibt.
   Hier stand zuerst eine Maske, die den rechten Rand ausfransen liess. Marco
   hat an ihrer Stelle Blaetterpfeile gewaehlt (siehe `.fenster-blaettern`),
   und die koennen mehr: sie ZEIGEN nicht nur, dass da etwas liegt, man kommt
   mit ihnen auch hin — mit der Maus und mit der Tastatur. Die Maske ist
   deshalb ersatzlos weg; zwei Hinweise nebeneinander waeren einer zu viel.

   UND SEIT DEM 22.09.2026 GILT DERSELBE SATZ FUER DIE SCROLLLEISTE.
   Marco: „Aktuell faden die nur rein und danach ploepp einfach auch ein
   Scrollbalken dazu." Sie war hier absichtlich konfiguriert — als genauere
   Auskunft darueber, WIE VIEL noch kommt. Das Argument traegt nicht: sie
   nimmt 0 px, liegt als Ueberlagerung ueber dem Inhalt und erscheint erst
   BEIM Wischen, man muesste sie also schon bedienen, um zu erfahren, dass es
   sie gibt. Als Hinweis ist sie damit der dritte, der nichts sagt — und seit
   die Bahn waagerecht laeuft, erschiene sie als Strich UNTER den Karten,
   genau dort, wo das Auge nach dem Lesen ankommt.
   Verborgen wird sie, nicht abgeschaltet: Rad, Wischen und Tastatur scrollen
   weiter, `overflow-x: auto` bleibt. `scrollbar-width` deckt Firefox und
   neueres Chromium ab, die Pseudoklasse das aeltere WebKit. */
.widgets { scrollbar-width: none; }
.widgets::-webkit-scrollbar { display: none; }

/* DIE BLAETTERPFEILE — SEIT DEM 22.09.2026 WIEDER NACH LINKS UND RECHTS,
   UND ZUM ERSTEN MAL IN EINER EIGENEN SPALTE.
   Sie liegen NEBEN der Scrollflaeche, nicht darin: ein Knopf im
   Scroll-Container wuerde mitgescrollt und waere genau dann verschwunden,
   wenn man ihn braucht (Invariante 8). Bisher galt das nur fuer den
   ELTERNKNOTEN — sie hingen `absolute` am Rahmen `.fensterzeile` und schwebten
   damit trotzdem UEBER der Randkarte. Senkrecht ging das durch: oben und unten
   war Luft. Waagerecht deckt derselbe Knopf die halbe Hoehe der ersten und der
   letzten Karte ab, und dort steht Text.
   Also traegt `.fensterzeile` jetzt drei Spalten und die Pfeile stehen wirklich
   daneben.

   NUR: DIE SPALTEN MUESSEN VON AUSSEN BEZAHLT WERDEN, NICHT VON DER BAHN.
   Ein Pfeil kostet 34 px plus 4 px Gasse. Nimmt er sie aus der Bahn, bleiben
   von 1068 noch 992 — und drei 340er Karten brauchen 1044. Erst gemessen,
   dann gesehen: bei 1600 px standen zwei Karten statt der drei, die Marco
   bestellt hat. Die halbe Knopfbreite zurueckzuholen (`margin-inline: -17px`)
   haette die Rechnung auch nicht gerettet und den Knopf wieder auf den
   Kartentext gelegt — also die Falle, derentwegen die Spalte ueberhaupt
   entstanden ist.
   `.fensterzeile` greift deshalb um genau diese 38 px in die Randgassen der
   Buehne hinaus. Dort ist Platz: die Gassen sind 246 px breit und tragen
   Themenliste und Schlange, die beide `fixed` liegen und am AEUSSEREN Rand
   beginnen. Die Bahn behaelt damit ihre vollen 1068 px, und die Pfeile stehen
   trotzdem neben ihr statt darauf.
   `hidden` setzt `hud.js`, sobald es in diese Richtung nichts mehr zu holen
   gibt: am Anfang gibt es kein Zurueck, am Ende kein Weiter, und ohne Ueberlauf
   gar keinen Pfeil.
   Und sie sind echte `<button>`. Damit ist der Slider auch ohne Zeigegeraet
   bedienbar — das war beim Ziehen am Fensterkopf frueher die Aufgabe der
   Pfeiltasten, die mit dem Schweben weggefallen sind. */
/* `min-width` UND `min-height` AUF 0 SIND KEIN SCHMUCK. Ein Grid-Item steht
   auf `auto` und schrumpft nicht unter die Groesse seines Inhalts. Am
   21.09.2026 wurde die waagerechte Zeile aus zwoelf 340-px-Fenstern deshalb
   4224 px breit und riss die ganze SEITE ins Querscrollen, statt dass der
   Slider in sich scrollt. Dieselbe Falle steht hier wieder, weil die Zeile
   wieder waagerecht ist — und diesmal ist die mittlere SPALTE der Ort, an dem
   sie zuschlaegt: `minmax(0, 1fr)` und nicht `1fr`, sonst waechst sie mit
   ihrem Inhalt statt ihn scrollen zu lassen. */
.fensterzeile {
  /* DAS KARTENFORMAT STEHT HIER und nicht an `.widget`: die Pfeile brauchen
     es auch, und als Geschwister der Bahn erben sie nichts von der Karte.
     SEIT DEM 22.09.2026 IST ES EIN FORMAT UND KEIN DECKEL MEHR. 220 px war
     die Obergrenze einer Karte, die so hoch wurde, wie ihr Inhalt ausfiel —
     und eine Reihe mit ausgefransten Unterkanten sieht nach Formular aus,
     nicht nach Kartenspiel. 480 px sind die feste Statur: bei 340 px Breite
     ist das Verhaeltnis 0,71, also hochkant wie eine Sammelkarte, und in die
     1068 x 546 px grosse Bahn passt sie mit Luft. Begruendung an `.widget`.

     SEIT DEM 22.09.2026 SIND ES ZWEI FORMATE JE ACHSE UND NICHT EIN MASS.
     Marco: „Die Breite und die Hoehe duerfen die Widgets auch selber
     entscheiden." Entschieden wird es im DIENST (`kartenFormat`,
     src/widgets.ts) und kommt als `data-breite` / `data-hoehe` an der Karte
     an; hier stehen nur die Zahlen dazu.

     DIE BREITEN SIND VIELFACHE DES KARTENSCHRITTS, und das ist keine
     Kosmetik: die Bahn schnappt (`scroll-snap-type: x proximity`), und ein
     Format zwischen zwei Rastpunkten zieht nach jedem Blaettern noch einmal
     nach. 692 = 2x340 + 12 Luecke, also genau zwei Plaetze — eine breite
     Karte belegt zwei Rastpunkte statt anderthalb.

     DIE ZAHLEN STEHEN ALLE HIER OBEN, auch die, die nur die Karte braucht.
     Die Blaetterpfeile sind Geschwister der Bahn und erben nichts von
     `.widget`; seit die Karten verschieden hoch sein duerfen, rechnen sie
     ohnehin nicht mehr mit der Kartenhoehe (siehe `.fenster-blaettern`). */
  --karte-breit: 340px;
  --karte-breit-breit: 692px;
  --karte-hoch: 480px;
  --karte-hoch-knapp: 300px;
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: start; gap: 4px;
  /* 34 Knopf + 4 Gasse — die Breite, die ein Pfeil kostet, aus der Randgasse
     statt aus der Bahn.
     DIE ZAHL HAENGT AN DEN PFEILEN, NICHT AM RAND DES BILDSCHIRMS. Wo es
     keine Pfeile gibt, gibt es auch nichts zu bezahlen — siehe die
     820-px-Abfrage, die sie deshalb auf 0 zuruecknimmt.
     SEIT DEM 24.09.2026 GAR NICHT MEHR: die Bahn steht auf beiden Gassen
     (`grid-column: 1 / 4`), die Pfeile bezahlen sich links und rechts aus
     ihnen, und ein Rand auf nur EINER Seite schoebe die Mitte von der Achse.
     Ein negativer Rand schoebe den Pfeil ueber die Bildschirmkante. */
  margin-inline: 0;
  min-height: 0; min-width: 0;
}
.fenster-blaettern {
  z-index: 2;
  width: 34px; height: 34px; padding: 0;
  display: grid; place-items: center;
  border: 1px solid var(--rand); border-radius: 50%;
  background: var(--flaeche); color: var(--text-leise);
  cursor: pointer; transition: color var(--dauer), background var(--dauer), opacity var(--dauer);
  box-shadow: 0 4px 14px var(--schatten);
}
/* EIN PFEIL, DER NICHTS ZU HOLEN HAT, VERSCHWINDET — SEINE SPALTE NICHT.
   `display: none` waere hier ein Sprung: sobald man am Ende ankommt, faellt
   der rechte Pfeil weg, die Bahn wird 38 px breiter und alle Karten rutschen.
   Marcos zweite Invariante gilt auch fuer Bewegung, die aus dem Layout kommt:
   „Eine Form, die mit dem Inhalt wechselt, gewoehnt sich niemand an."
   `visibility` nimmt den Knopf aus Bild und Tastaturfolge und laesst die
   Spalte stehen — dasselbe Muster wie an `.lupe-blaettern[hidden]`. */
.fenster-blaettern[hidden] { visibility: hidden; display: grid; }
.fenster-blaettern:hover { color: var(--text); border-color: var(--akzent); }
.fenster-blaettern:focus-visible { outline: 2px solid var(--akzent); outline-offset: 2px; }
/* Links und rechts von der Bahn, ganz ausserhalb und ohne Ueberlappung — was
   sie kostet, zahlt die Randgasse (siehe oben).

   UND AUF HALBER KARTENHOEHE, NICHT AUF HALBER ZELLHOEHE.
   `align-items: center` an der Zeile hat sie in die Mitte der Rasterzelle
   gesetzt — und die ist die ganze obere Buehnenzeile, waehrend die Karten
   oben daran haengen. Lokal gemessen (1600x900): die Karten endeten bei
   y=281, der Pfeil sass bei y=363, also 82 px darunter in der Leere. Er sah
   aus wie ein Knopf, der zu nichts gehoert.
   SEIT DEM 22.09.2026 STEHT ER MITTIG IN DER ZEILE UND NICHT MEHR AUF HALBER
   KARTENHOEHE. Zwei Aenderungen haben sich dabei getroffen: die Bahn
   zentriert ihre Karten senkrecht (`align-items: safe center` an `.widgets`,
   Commit adca718), und die Karten duerfen seither verschieden hoch sein. Die
   alte Rechnung `4px + (--karte-hoch - 34) / 2` traf damit beides nicht mehr
   — sie mass vom oberen Bahnrand herunter, waehrend die Karten in der Mitte
   stehen, und sie kannte nur EIN Kartenformat.
   Die Mitte der Rasterzelle IST jetzt die Mitte der Karten, weil die Bahn die
   ganze Zelle fuellt (`height: 100%`) und ihre Karten darin zentriert. Das
   war vor dem Zentrieren anders — damals sass der Pfeil 82 px unter der
   letzten Karte in der Leere, und genau deshalb stand hier eine Rechnung
   statt `center`. */
.fenster-blaettern.zurueck, .fenster-blaettern.vor {
  align-self: center;
}
.fenster-blaettern.zurueck { grid-column: 1; }
.fenster-blaettern.vor { grid-column: 3; }
.widgets { grid-column: 2; }
/* Eine leere Bahn hat auch kein Polster — sonst stuende ueber dem
   Antwortsatz eine Luecke, die nichts erklaert. */
.widgets:empty { display: none; }

/* EIN FENSTER — ein Glied der Slider-Zeile.
   Keine Position; die BREITE steht jetzt hier statt in der Bahn.
   `transform` bleibt frei fuer das Ein- und Ausblenden.
   ZWEI DECKEL, UND DER KLEINERE GEWINNT. 220 px ist der gewuenschte Wert;
   `100%` ist die Sicherung: hoeher als die Bahn wird kein Fenster, sonst
   staende eines allein darin und der Rest waere nur noch erscrollbar.
   Ein `vh`-Deckel taete es nicht — er weiss nicht, wie hoch die Bahn
   gerade ist.
   Was nicht hineinpasst, scrollt im Fenster (`.widget-inhalt`).
   Schatten und Blur trennen es von der Kugel darueber — ohne sie schwimmt
   der Text im Verlauf. */
.widget {
  position: relative;
  display: flex; flex-direction: column;
  /* DIE KARTE HAT EINE BREITE, SIE FUELLT KEINE SPUR MEHR.
     Das ist der ganze Unterschied zwischen Raster und Slider: 340 px, immer,
     und was nicht mehr in die Bahn passt, liegt rechts daneben und wird
     herangeblaettert. Bei 1068 px Bahn stehen damit drei nebeneinander
     (3x340 + 2x12 Luecke = 1044) statt zweier zu je 528.
     `0 0` davor: die Karte darf WEDER wachsen NOCH schrumpfen. Ohne die
     schrumpfende Null draengte `flex` sechs Karten in die Bahn, statt dass
     sie ueberlaeuft — und ein Slider, dessen Inhalt immer hineinpasst, hat
     nichts zu blaettern.
     `min(…, 100%)` macht aus 340 einen WUNSCH statt einer festen Zahl —
     dieselbe Sicherung, die das Raster als `minmax(min(420px, 100%), 1fr)`
     trug, auf der neuen Achse. Zwischen 821 und 960 px Fensterbreite ist die
     Bahn nur 328 bis 428 px breit (die Themenliste nimmt dort noch ihre
     Gasse), und ohne das `min` ragte die Karte wieder an beiden Seiten
     heraus. Der Grund steht ausfuehrlich an `.widgets`.

     DIE ZAHL KOMMT SEIT DEM 22.09.2026 AUS `--karte-breit` und steht nicht
     mehr hier: es gibt zwei Breiten, und welche gilt, sagt `data-breite`
     gleich darunter. Das `min(…, 100%)` bleibt an DIESER Stelle, damit es
     fuer beide Formate gilt. */
  flex: 0 0 min(var(--karte-breit), 100%); width: auto;
  /* `height` UND NICHT `max-height` — DAS IST DER GANZE UNTERSCHIED ZWISCHEN
     EINEM KASTEN UND EINER KARTE.
     Bis zum 22.09.2026 stand hier ein Deckel, und darunter wurde die Karte so
     hoch, wie ihr Inhalt ausfiel: gemessen 176, 176 und 220 px nebeneinander.
     Marco: „Dieses Widget sieht immer noch scheisse aus. … Sie sollen aussehen
     wie Sammelkarten." Die ausgefransten Unterkanten waren der groesste
     Einzelposten daran — ein Satz Karten ist ein Satz, weil alle Karten
     dieselbe Statur haben.
     Gleich hoch bleiben sie AUCH DANN, wenn die Bahn niedriger ist als 480:
     `min(…, 100%)` gilt fuer jede Karte gleich, es gewinnt also ueberall
     dieselbe Zahl. Genau dafuer braucht das Prozent eine aufloesbare
     Elternhoehe (`height: 100%` an `.widgets`) — an einem `auto`-hohen
     Flex-Container faellt der ganze `min()`-Ausdruck weg, und dann waeren die
     Karten wieder verschieden hoch.
     Was nicht hineinpasst, rollt im KOERPER (`.widget-inhalt`); Band, Name und
     Fusszeile rollen nie.
     Wer aufklappt, darf darueber hinaus: `--karte-hoch` wird dann zur
     Startgroesse und nicht zur Grenze (siehe `.widget.aufgeklappt`).
     Die Zahl selbst steht an `.fensterzeile`, weil auch die Blaetterpfeile
     mit ihr rechnen — sie sind Geschwister der Bahn und wuerden von einer
     Deklaration hier nichts erben.
     UND SIE IST NICHT MEHR `KARTE_HOCH` AUS `src/widgets.ts`: die 220 dort
     wiegen seit heute den INHALT und nicht die Karte. Warum die beiden
     auseinandergetreten sind, steht dort. */
  /* SEIT DEM 23.09.2026 WIEDER EIN DECKEL UND KEINE STATUR. Marco: „Die
     brauchen nicht mehr Platz ein, als ihr Inhalt nimmt. Das muss komplett
     dynamisch sein." Die feste Hoehe liess unter kurzem Inhalt 200 px leere
     Flaeche stehen — gemessen an einer Aufgabenkarte mit zwei Zeilen Text.
     Damit gilt der Text darueber nur noch fuer den Deckel: `--karte-hoch` ist
     die GRENZE, bis zu der eine Karte waechst, und `100%` die Sicherung gegen
     eine niedrige Bahn. Beide brauchen weiterhin die aufloesbare Elternhoehe
     an `.widgets`. */
  height: auto;
  max-height: min(var(--karte-hoch), 100%);
  background: var(--flaeche-92); border: 1px solid var(--rand); border-radius: 18px;
  /* KEIN Polster an der Karte, sondern an ihren Zonen: das Kopfband laeuft bis
     an die Kante, und nur so liest es sich als Band statt als Zeile. Der
     Radius von 18 statt 14 gehoert zur neuen Groesse — bei 480 px Hoehe liest
     sich die alte Rundung wie eine Ecke.
     `overflow: hidden` haelt Band und Fusszeile in der Rundung. */
  padding: 0; overflow: hidden;
  backdrop-filter: blur(12px);
  /* KEIN Schlagschatten — Marco am 22.09.2026: er sieht nicht schoen aus. Abgesetzt
     ist das Fenster trotzdem, ueber den Rahmen und das Milchglas dahinter; der Wurf
     war die dritte Kante und damit eine zu viel. Gilt genauso am `.mcp-kasten`.
     TIEFE KOMMT SEITHER AUS DEM AUFBAU: Rahmen, Milchglas, Radius, die
     Oberkante in der Rangfarbe und das getoente Band darunter. */
  opacity: 0; transform: translateY(8px) scale(.98);
  transition: opacity var(--dauer-lang) ease, transform var(--dauer-lang) ease;
}

/* DAS FORMAT DIESER KARTE — zwei Umschreibungen, sonst nichts.
   Beide setzen nur die VARIABLE um, nicht die Regel: `flex-basis` und
   `height` stehen weiterhin genau einmal oben an `.widget`, samt ihrem
   `min(…, 100%)`. Eine zweite Deklaration hier wuerde die Sicherung gegen die
   schmale Bahn (821 bis 960 px Fenster) still verlieren — das Loch, das am
   22.09.2026 schon einmal die Karte an beiden Seiten abgeschnitten hat.

   WER SIE SETZT: der Dienst. `kartenFormat` (src/widgets.ts) leitet das
   Format aus den Bausteinen ab, `hud.js` schreibt es als `data-`Attribut an
   den Kasten. Fehlt das Attribut — altes Fenster, fremder Sender —, gilt der
   Normalfall von oben, und die Karte sieht aus wie bisher. */
.widget[data-breite="breit"] { --karte-breit: var(--karte-breit-breit); }
.widget[data-hoehe="knapp"] { --karte-hoch: var(--karte-hoch-knapp); }

/* ── DIE RANGFARBE ─────────────────────────────────────────────────────────
   Marco am 22.09.2026: „Dann sieht man am Rand des Blickfelds, ob etwas auf
   einen wartet, ohne zu lesen — wie die Typfarbe einer Sammelkarte."

   Die Zahl kommt von WorkWith (`RANG`, 0–60), das Band waehlt `rangBand` in
   hud.js und setzt es als `data-rang` an die Karte. HIER steht nur, welche
   Farbe dazugehoert — und zwar als zwei Variablen, die Oberkante, Punkt, Wort
   und Bandflaeche gemeinsam bedienen. Eine Regel je Stelle waere viermal
   dieselbe Entscheidung.

   Ohne Rang bleibt es grau: ein Kalender oder ein Wetterfenster ist keine
   Schuld, und eine Farbe, die dort nur „irgendwas" hiesse, machte die drei
   anderen bedeutungslos. */
.widget {
  --rang: var(--text-leise); --rang-flaeche: var(--text-alpha-schwach);
  /* DIE KANTE IST EIGENS GESETZT, weil sie das Einzige ist, was ohne Wort
     spricht. Bei „zur Kenntnis" und ohne Rang traegt sie deshalb die
     Rahmenfarbe: ein drei Pixel breiter Strich in Schriftgrau las sich im
     hellen Theme als schwarzer Balken und schrie genau dort am lautesten, wo
     nichts zu melden ist. */
  --rang-kante: var(--rand);
}
.widget[data-rang="wartet"] { --rang: var(--denkt);  --rang-flaeche: var(--denkt-alpha);  --rang-kante: var(--denkt); }
.widget[data-rang="stockt"] { --rang: var(--fehler); --rang-flaeche: var(--fehler-alpha); --rang-kante: var(--fehler); }
.widget[data-rang="laeuft"] { --rang: var(--akzent); --rang-flaeche: var(--akzent-alpha); --rang-kante: var(--akzent); }
/* DIE OBERKANTE IST DER TYPSTREIFEN. Drei Pixel ueber die volle Breite, ueber
   dem Band und innerhalb der Rundung — das ist die Kante, die man aus dem
   Augenwinkel sieht, bevor man ein Wort liest. `absolute` und nicht ein
   `border-top`, damit die Rundung sie beschneidet statt sie zu begradigen. */
/* UND AUCH KEIN FARBSTREIFEN OBEN. Mit dem Band war er die zweite Stelle,
   die den Rang traegt; ohne das Band waere er die lauteste Farbe auf einer
   sonst ruhigen Flaeche — ein 3-px-Streifen ist ein Strich, keine Flaeche.
   Der Punkt in der Kopfzeile sagt dasselbe und sagt es einmal. */
/* DIE KARTEN TRETEN NACHEINANDER AUF, NICHT GEMEINSAM.
   Marco am 22.09.2026: „Aktuell faden die nur rein." Kommen drei Karten auf
   einmal, starten sie mit derselben Geste zur selben Millisekunde — das Auge
   bekommt keinen Einstieg und sieht einen Block aufblitzen statt einer Reihe,
   die sich fuellt. 70 ms Versatz je Karte machen daraus eine Bewegung von
   links nach rechts, also in Leserichtung.
   DIESELBE MACHART WIE DIE BAUSTEINE DRINNEN (`.widget.staffelt .stufe`):
   eine Variable traegt die laufende Nummer, das CSS rechnet die Verzoegerung.
   `--k` setzt `hud.js` beim Einhaengen, und zwar je ANKUNFT und nicht je
   Kartenzahl — sonst wartete die zehnte Karte des Tages 700 ms auf sich
   selbst.

   UND DER RIEGEL IST DERSELBE: `.staffelt` steht nur an einer NEUEN Karte
   und faellt bei jeder Aktualisierung weg. Ohne ihn liefe der Versatz bei
   jedem Neuzeichnen erneut, und die Bahn zappelte genau dann, wenn man sie
   liest — dieselbe Falle, die bei den Bausteinen schon einmal zugeschlagen
   haette. */
.widget.staffelt { transition-delay: calc(var(--k, 0) * 70ms); }
.widget.sichtbar { opacity: 1; transform: none; }
/* Wer geht, geht sofort. Der Versatz gehoert dem Auftritt; beim Abraeumen
   waere er eine Warteschlange vor dem Ausgang. */
.widget.geht { opacity: 0; transform: translateY(-6px) scale(.98); transition-delay: 0s; }
/* WER AUFKLAPPT, WILL LESEN. Der Deckel schuetzt die Uebersicht, nicht die
   Absicht — also gilt er fuer die aufgeklappte Karte nicht mehr, sie nimmt
   bis zur vollen Bahnhoehe. `.widget-inhalt` rollt dann in sich weiter.
   Die BREITE bleibt: eine Karte, die sich beim Aufklappen auch noch
   verbreitert, schoebe die ganze Reihe rechts von ihr weg. */
/* `max-height` und nicht `height`: auch aufgeklappt nimmt die Karte nur, was
   ihr Text braucht — die volle Bahnhoehe ist die Grenze, kein Mass. */
.widget.aufgeklappt { max-height: 100%; }
/* Der Inhalt scrollt, der Kopf bleibt stehen — sonst wandert der Titel weg
   und man weiss beim Scrollen nicht mehr, worin man liest.
   Die Bausteine stehen untereinander, und der Abstand zwischen ihnen steht
   HIER — einmal, statt als `margin-top` an jedem einzelnen Kasten. */
/* DER KOERPER IST DIE EINZIGE ZONE, DIE ROLLT — und die einzige, die waechst.
   Die Karte endet seit dem 23.09.2026 dort, wo ihr Inhalt aufhoert — die
   feste Statur mit Leerflaeche darunter hat Marco zurueckgenommen (siehe
   `height` an `.widget`). Die Fusszeile sitzt damit direkt unter dem Koerper.
   Das Polster steht seit dem 22.09.2026 hier statt an `.widget` — die Karte
   selbst hat keines mehr, sonst liefe das Kopfband nicht bis an die Kante. */
.widget-inhalt {
  /* `1 1 auto` statt `1`: mit Basis 0 traegt der Koerper in einer
     inhaltshohen Karte nichts zu ihrer Hoehe bei. Mit `auto` ist er so hoch wie
     sein Inhalt und schrumpft erst, wenn der Deckel greift — dann rollt er. */
  flex: 1 1 auto; overflow-y: auto; min-height: 0;
  display: flex; flex-direction: column; gap: 10px;
  padding: 12px 16px;
}
/* DIE FUSSZEILE — die Werteleiste der Karte.
   Hier stehen die Antwortvorschlaege, und sie stehen IMMER unten: bis zum
   22.09.2026 waren sie der letzte Baustein im rollenden Koerper und rutschten
   bei einer vollen Karte unter die Scrollkante. Jetzt sind sie eine eigene
   Zone (siehe `widgetZonen` in hud.js) und damit ausserhalb der Rollflaeche —
   dieselbe Regel, die die Blaetterpfeile aus dem Slider geholt hat
   (Invariante 8): ein Knopf, den man wegscrollen kann, ist genau dann weg,
   wenn man ihn braucht. */
.widget-fuss {
  flex: none; padding: 10px 16px 13px;
  border-top: 1px solid var(--rand);
  background: var(--text-alpha-schwach);
}
/* Cooldown-Balken an der Unterkante: nur sichtbar, während der Timer läuft.
   Er liegt seit dem 22.09.2026 AUF der Kante und nicht mehr als Balken im
   Polster — das Polster gehoert jetzt der Fusszeile, und zwei Dinge
   uebereinander waeren eines zu viel. Als Kante liest er sich ausserdem wie
   der Rest der Karte: eine Linie, die etwas sagt. */
.widget-cooldown {
  position: absolute; left: 0; right: 0; bottom: 0;
  height: 3px;
  background: var(--rand);
  overflow: hidden; z-index: 1;
  pointer-events: none;
}
.widget-cooldown::before {
  content: "";
  display: block; height: 100%; width: var(--cooldown-breite, 100%);
  background: var(--akzent);
  transition: width .2s linear;
}
.widget[hidden] .widget-cooldown, .widget-cooldown[hidden] { display: none; }

/* ── Bausteine ─────────────────────────────────────────────────────────────
   Ein Fenster ist eine Folge von Bausteinen (siehe `src/widgets.ts`). Die
   Abstaende zwischen ihnen gehoeren deshalb dem Fenster und nicht mehr den
   einzelnen Kaesten: vorher trug jede Art ihr eigenes `margin-top`, und wer
   einen Baustein an eine neue Stelle setzte, bekam entweder zwei Abstaende
   oder keinen. Jetzt steht die Zahl EINMAL, oben an `.widget-inhalt`. */

/* Der Titel traegt, alles andere ordnet ein. */
.block-titel { font-size: 15px; line-height: 1.35; color: var(--text); }
.block-merkmale { display: flex; flex-wrap: wrap; gap: 6px; }
.merkmal {
  display: inline-flex; align-items: baseline; gap: 5px;
  padding: 3px 9px; border-radius: 999px;
  background: var(--text-alpha-schwach); border: 1px solid var(--rand);
  font-size: 12px; color: var(--text);
}
.merkmal-label { color: var(--text-leise); font-size: 11px; }

/* DREI TONLAGEN, EIN BAUSTEIN.
   `normal` ist der Fliesstext eines `text`-Fensters, `knapp` der fertige Satz
   aus `bedeutet` (er traegt die Aussage, nicht die Merkmale darunter, also
   steht er in Schriftfarbe), `abgesetzt` der Nachsatz unter einem Strich. */
.block-text { font-size: 15px; line-height: 1.5; white-space: pre-wrap; color: var(--text); }
.block-text.knapp { font-size: 13px; line-height: 1.45; white-space: normal; }
/* Markdown-Rendering innerhalb von .block-text */
.block-text p { margin: 0 0 8px 0; }
.block-text p:last-child { margin-bottom: 0; }
.block-text h1,
.block-text h2,
.block-text h3,
.block-text h4,
.block-text h5,
.block-text h6 { margin: 10px 0 6px 0; line-height: 1.3; color: var(--text); }
.block-text h1 { font-size: 17px; }
.block-text h2 { font-size: 16px; }
.block-text h3 { font-size: 15px; }
.block-text h4,
.block-text h5,
.block-text h6 { font-size: 14px; }
.widget-inhalt .block-text ul,
.widget-inhalt .block-text ol { margin: 6px 0; padding-left: 18px; }
.widget-inhalt .block-text ul { list-style: disc; }
.widget-inhalt .block-text ol { list-style: decimal; }
.widget-inhalt .block-text li { margin: 3px 0; display: list-item; }
.block-text code {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  font-size: 13px;
  padding: 1px 4px;
  border-radius: 4px;
  background: var(--text-alpha-schwach);
  color: var(--text);
}
.block-text pre {
  margin: 8px 0;
  padding: 8px 10px;
  border-radius: 6px;
  background: var(--text-alpha-schwach);
  border: 1px solid var(--rand);
  overflow-x: auto;
}
.block-text pre code {
  background: transparent;
  padding: 0;
  border-radius: 0;
  font-size: 12px;
}
.block-text blockquote {
  margin: 8px 0;
  padding: 6px 12px;
  border-left: 3px solid var(--akzent);
  color: var(--text-leise);
  background: var(--text-alpha-schwach);
  border-radius: 0 6px 6px 0;
}
.block-text hr { border: 0; height: 1px; background: var(--rand); margin: 10px 0; }
.block-text a { color: var(--akzent); text-decoration: underline; }
.block-text a:hover { color: var(--text); }

/* ANTWORTVORSCHLAEGE — was Marco darauf erwidern koennte.
   Schaltflaechen und keine Pillen: `.merkmal` daneben sieht aehnlich aus,
   sagt aber etwas ueber die Sache, waehrend hier etwas zu TUN ist. Der
   Unterschied traegt der Rahmen in Akzentfarbe und der Zeiger.
   Sie stehen als letzter Baustein unten in der Karte, also dort, wo das Auge
   nach dem Lesen ohnehin ankommt — und `flex-wrap`, weil drei Vorschlaege in
   einer 340 px breiten Karte auf zwei Zeilen gehen duerfen. Genau diese
   zweite Zeile rechnet der Dienst im Hoehenbudget mit (`kartenGewicht`). */
.block-vorschlaege { display: flex; flex-wrap: wrap; gap: 6px; }
.vorschlag {
  font: inherit; font-size: 12.5px; line-height: 1.2;
  padding: 5px 11px; border-radius: 999px;
  border: 1px solid var(--akzent); background: var(--akzent-alpha);
  color: var(--akzent); cursor: pointer;
  transition: background var(--dauer-kurz), color var(--dauer-kurz);
}
.vorschlag:hover { background: var(--akzent); color: var(--flaeche); }
.vorschlag:focus-visible { outline: 2px solid var(--akzent); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .vorschlag { transition: none; } }

/* DER GRIFF AM GEKUERZTEN TEXT.
   Er sieht aus wie ein Link und nicht wie ein Knopf: er fuehrt in denselben
   Text hinein, statt etwas zu tun. Linksbuendig unter dem Satz, damit die
   Leserichtung nicht abbricht — ein zentrierter Griff waere eine zweite
   Achse in einer Karte, die sonst konsequent links ausgerichtet ist. */
.block-mehr {
  align-self: flex-start; margin-top: -2px;
  background: none; border: 0; padding: 0;
  font: inherit; font-size: 12.5px; color: var(--akzent);
  cursor: pointer; text-align: left;
  /* ER DARF NIE WEGGESCROLLT SEIN — dieselbe Regel, die die Blaetterpfeile
     aus dem Slider geholt hat (Invariante 8): ein Knopf im Scroll-Container
     ist genau dann weg, wenn man ihn braucht. Hier war das gemessen und
     nicht befuerchtet: bei zwei von vier Karten schnitt der 176-px-Deckel
     den Griff waagerecht durch (lokal gerendert, 1600x900).
     `sticky` statt eines eigenen Kastens ausserhalb von `.widget-inhalt`:
     der Griff gehoert zu SEINEM Textbaustein, nicht zur Karte — eine Karte
     kann zwei gekuerzte Texte tragen. Der Streifen darunter deckt den Text
     ab, der sonst unter ihm durchliefe. */
  position: sticky; bottom: -1px; z-index: 1;
  padding-block: 3px 1px; width: 100%;
  background: linear-gradient(to top, var(--flaeche-92) 72%, transparent);
}
.block-mehr:hover { color: var(--text); }
.block-mehr:focus-visible { outline: 2px solid var(--akzent); outline-offset: 2px; border-radius: 4px; }

.block-text.abgesetzt {
  padding-top: 10px; border-top: 1px solid var(--rand);
  font-size: 13px; line-height: 1.5; color: var(--text-leise);
}

.block-aufzaehlung { padding-top: 10px; border-top: 1px solid var(--rand); }
.block-aufzaehlung-titel {
  font-size: 11px; text-transform: uppercase; letter-spacing: .08em;
  color: var(--text-leise); margin-bottom: 5px;
}
.block-aufzaehlung ul { margin: 0; padding-left: 16px; }
.block-aufzaehlung li { font-size: 12px; line-height: 1.5; color: var(--text-leise); }
/* DER KOPF IST NUR NOCH EINE UEBERSCHRIFT.
   Bis zum 21.09.2026 war er der Anfasser: `cursor: grab`, `touch-action: none`
   gegen das Mitscrollen der Seite und ein Fokusring, weil das Verschieben per
   Pfeiltaste sonst unsichtbar gewesen waere. Es wird nichts mehr verschoben,
   also ist alles drei weg — ein Greif-Zeiger ueber etwas, das sich nicht
   greifen laesst, ist ein Versprechen, das die Oberflaeche nicht haelt. */
/* DAS KOPFBAND — ERSTE ZONE DER KARTE, UND SEIT DEM 22.09.2026 EIN BAND.
   Es laeuft bis an beide Kanten (das Polster sitzt hier statt an `.widget`)
   und traegt die Rangfarbe als Flaeche. Damit ist es das, was man von einer
   Sammelkarte zuerst sieht — vorher war es eine blaue Zeile ueber einer Folge
   gleich schwerer Textkaesten.
   Links QUELLE und ZUSTAND, rechts die Knoepfe: `w.art` sagt, aus welcher
   Vorlage die Karte kommt, `w.titel` sagt, wie es um die Sache steht. */
/* KEIN FARBBAND IM KOPF (22.09.2026).
   Marco: "Das sieht aber immer noch zu sehr nach Sammelkarte aus. UX GEILE
   WIDGETS BITTE, keine Sammelkarten mit orangenen Header-Hintergruenden."
   Er hatte "wie Sammelkarten, die eingeblendet werden" gesagt und das
   ERSCHEINEN gemeint — daraus ist ein Kartenspiel geworden, mit getoenter
   Kopfflaeche und Trennlinie darunter. Zwei Kanten und eine Farbflaeche fuer
   eine Zeile, die nur sagt, woher die Karte kommt.
   Der Rang bleibt sichtbar, aber als Punkt: ein 7-px-Kreis in der Rangfarbe
   traegt dieselbe Auskunft, ohne dass die Karte ein Etikett bekommt. */
.widget-kopf {
  display: flex; justify-content: space-between; align-items: center; gap: 8px;
  flex: none; padding: 12px 12px 6px 16px;
  background: none;
}
.widget-marke { display: inline-flex; align-items: baseline; gap: 7px; min-width: 0; }
/* Der Punkt traegt die Farbe auch dort, wo das Band getoent kaum auffaellt —
   im hellen Theme ist die 14-%-Flaeche sehr leise. */
.widget-rangpunkt {
  width: 7px; height: 7px; border-radius: 50%; flex: none;
  background: var(--rang); align-self: center;
}
.widget-art {
  font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--text-leise); flex: none;
}
/* Der Zustand steht in der Rangfarbe und darf kuerzen: er ist die zweite
   Auskunft im Band, und ein Umbruch machte aus dem Band zwei Zeilen. */
.widget-titel {
  font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--rang);
  min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.widget-titel::before { content: "·"; margin-right: 7px; color: var(--text-leise); }

/* DIE NAMENSZONE — was auf der Karte steht, gross genug, um es zu sein.
   Der Name war bis zum 22.09.2026 ein `titel`-Baustein MITTEN in der Folge
   und mit 15 px genauso gross wie der Fliesstext darunter. Eine Karte, deren
   Name nicht groesser ist als ihre Fussnote, hat keine Hierarchie, die man
   ohne Lesen erkennt.
   Drei Zeilen und dann Punkte: der volle Titel steht ohnehin im Vorlesetext,
   und ein vierzeiliger Name nimmt dem Koerper die Zone weg. */
.widget-name { flex: none; padding: 13px 16px 0; }
.widget-name .block-titel {
  font-size: 17px; line-height: 1.32; font-weight: 600;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3;
  overflow: hidden;
}
.widget-knoepfe { display: inline-flex; align-items: center; gap: 4px; }
.widget-zu { background: none; border: 0; color: var(--text-leise); font-size: 16px; cursor: pointer; padding: 0 2px; }
/* Ein Fenster ist Text, den niemand gesprochen hat. Der Lautsprecher holt das
   nach; er sitzt vor dem ×, weil man ihn öfter braucht als das Wegklicken. */
.widget-vorlesen {
  background: none; border: 0; color: var(--text-leise); cursor: pointer;
  padding: 0 2px; display: inline-flex; align-items: center;
  transition: color var(--dauer-kurz);
}
.widget-vorlesen:hover { color: var(--text); }
.widget-vorlesen.laeuft { color: var(--akzent); }
/* ÜBER DIESES FENSTER SPRECHEN. Gehalten wird wie beim grossen Mikrofon, also
   sieht der Knopf auch so aus, wenn er hält: `touch-action: none`, damit das
   Halten auf dem Telefon nicht die Seite scrollt, und keine Textauswahl unter
   dem Finger. Er steht als erster der drei — vor dem Lautsprecher, weil er zu
   dem gehört, was im Fenster steht, und nicht zum Umgang damit. */
.widget-mikro {
  background: none; border: 0; color: var(--text-leise); cursor: pointer;
  padding: 0 2px; display: inline-flex; align-items: center;
  touch-action: none; -webkit-user-select: none; user-select: none;
  transition: color var(--dauer-kurz);
}
.widget-mikro:hover { color: var(--text); }
.widget-mikro.haelt { color: var(--akzent); }
.widget ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.widget li { display: flex; justify-content: space-between; gap: 10px; align-items: baseline; }
.eintrag-titel { font-size: 15px; }
.eintrag-untertitel { font-size: 12px; color: var(--text-leise); margin-top: 2px; }
.badge { font-size: 11px; padding: 2px 8px; border-radius: 999px; background: var(--akzent-alpha); color: var(--akzent); white-space: nowrap; }
/* Der Durchgang: eine Liste, die sich bewegt. Besprochenes bleibt stehen und
   verblasst nur — verschwundene Punkte saehen aus wie vergessene. */
.durchgang { display: flex; flex-direction: column; gap: 10px; }
.dg-kopf { display: flex; align-items: center; gap: 10px; }
.dg-zaehler { font-size: 12px; color: var(--text-leise); white-space: nowrap; }
.dg-balken { flex: 1; height: 3px; border-radius: 999px; background: var(--rand); overflow: hidden; }
.dg-balken > span { display: block; height: 100%; background: var(--akzent); transition: width var(--dauer-lang); }
.dg-liste { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.dg-punkt { display: flex; align-items: baseline; gap: 8px; }
.dg-zeichen { width: 12px; color: var(--text-leise); font-size: 12px; }
.dg-text { display: flex; flex-direction: column; flex: 1; min-width: 0; }
.dg-titel { font-size: 14px; }
.dg-unter { font-size: 11px; color: var(--text-leise); margin-top: 1px; }
.dg-entscheidung {
  font-size: 11px; margin-top: 3px; color: var(--akzent);
  padding-left: 8px; border-left: 2px solid var(--akzent);
}
.dg-durch { opacity: .45; }
.dg-durch .dg-zeichen { color: var(--akzent); }
.dg-laeuft .dg-zeichen { color: var(--akzent); }
.dg-laeuft .dg-titel { color: var(--text); font-weight: 600; }
.dg-rest {
  margin-top: 4px; padding-top: 8px; border-top: 1px solid var(--rand);
  font-size: 11px; line-height: 1.45; color: var(--text-leise);
}

/* ── Die Kaskade ───────────────────────────────────────────────────────────
   DAS FENSTER FAEHRT HEREIN, SEIN INHALT BAUT SICH AUF.
   Bis zum 21.09.2026 fuhr nur der Rahmen (`.widget`, .35s) und der Inhalt
   stand schlagartig da. Jetzt kommt jeder Baustein einzeln nach, ueber `--i`
   um 45 ms versetzt — dieselbe Machart wie der Bootscreen weiter oben, und
   ohne Bibliothek.

   ZWEI RIEGEL, OHNE DIE DAS SCHAEDLICH WAERE:

   1. `.staffelt` steht NUR beim ersten Zeichnen am Fenster (`hud.js`,
      `classList.toggle("staffelt", neu)`). Der Inhalt wird bei JEDER
      Aktualisierung neu gesetzt, und der Durchgang aktualisiert nach jedem
      Punkt — ohne den Riegel flackerte die ganze Liste bei jedem "weiter".
   2. Die Kaskade laeuft auf den BAUSTEINEN, nie auf `.widget`. Dessen
      `transform` gehoert dem Herein und Hinaus, weil `left`/`top` inline vom
      Ziehen kommen.

   UND SEIT DEM 22.09.2026 WARTET SIE AUF IHRE KARTE. Die Karten treten selbst
   um `--k` mal 70 ms versetzt auf (siehe `.widget.staffelt` weiter oben) —
   ohne denselben Vorlauf hier waere die Kaskade der dritten Karte durch,
   bevor die Karte ueberhaupt zu sehen ist, und man saehe einen fertigen Block
   einblenden statt eines Aufbaus. `--k` steht an der Karte und erbt sich in
   die Bausteine, es ist also dieselbe Zahl, nicht eine zweite. */
.widget.staffelt .stufe {
  animation: block-rein var(--dauer-lang) var(--kurve-raus) both;
  animation-delay: calc(var(--k, 0) * 70ms + var(--i, 0) * 45ms);
}
@keyframes block-rein {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}
/* WER KEINE BEWEGUNG WILL, BEKOMMT KEINE — dann ist alles sofort da.
   Der Versatz der KARTEN gehoert mit hierher: `transition: none` allein
   naehme die Fahrt und liesse die Wartezeit stehen — die dritte Karte
   erschiene dann 140 ms nach der ersten, schlagartig. Das waere kein
   ruhigerer Auftritt, sondern ein stotternder. */
@media (prefers-reduced-motion: reduce) {
  .widget.staffelt .stufe { animation: none; }
  .widget.staffelt { transition-delay: 0s; }
  /* DER AUFTRITT DER DIAGRAMME FAELLT WEG, DAS DIAGRAMM NICHT.
     Alle vier Regeln stehen unter `.widget.staffelt` und laufen `backwards`,
     ihr Ruhezustand ist also das fertige Diagramm — `animation: none` genuegt
     deshalb, und es bleibt nichts im Anfangszustand stehen. Waere eine davon
     `both`, saehen genau die Leute, die nur keine Bewegung wollten, einen
     leeren Kasten: `scaleX(0)` beim Balken, ein zugezogener Wisch beim
     Blatt, Strichstaerke 0 bei den Punkten. */
  .widget.staffelt .dia-fuellung,
  .widget.staffelt .dia-wert,
  .widget.staffelt .dia-blatt,
  .widget.staffelt .dia-punkt { animation: none; }
}

/* ── Kalender: ein Baustein, drei Dichten ──────────────────────────────────
   `tag` ist die Liste (`.block-liste`) und braucht hier nichts. Woche und
   Monat zeigen auch die Tage, an denen NICHTS ist — genau dafuer schaut man
   auf ein Raster, und genau das kann eine Liste nicht. */
.kal-woche { display: flex; flex-direction: column; gap: 6px; }
.kal-tag {
  display: grid; grid-template-columns: 64px 1fr; gap: 8px; align-items: baseline;
  padding: 5px 0; border-top: 1px solid var(--rand);
}
.kal-tag:first-child { border-top: 0; padding-top: 0; }
.kal-tag-kopf { font-size: 11px; color: var(--text-leise); text-transform: uppercase; letter-spacing: .04em; }
/* Heute ist der Tag, den man sucht, wenn man auf eine Woche schaut. */
.kal-tag.heute .kal-tag-kopf { color: var(--akzent); font-weight: 600; }
.kal-tag-liste { display: flex; flex-direction: column; gap: 4px; margin: 0; padding: 0; list-style: none; }
.kal-tag-liste li { display: flex; gap: 7px; align-items: baseline; justify-content: flex-start; }
.kal-zeit { font-size: 11px; color: var(--text-leise); white-space: nowrap; font-variant-numeric: tabular-nums; }
.kal-titel { font-size: 13px; line-height: 1.35; min-width: 0; overflow-wrap: anywhere; }
/* Ein freier Tag ist eine Auskunft und kein Loch — aber eine leise. */
.kal-frei { font-size: 12px; color: var(--text-leise); opacity: .6; }

.kal-monat { display: flex; flex-direction: column; gap: 8px; }
/* LEISE, NICHT WIE EINE ZWEITE UEBERSCHRIFT. Das Fenster traegt oben schon
   eine in Akzentfarbe und Versalien ("SEPTEMBER"); dieselbe Form direkt
   darunter las sich als Stottern. Was hier fehlt und oben nicht steht, ist
   das JAHR — also bleibt die Zeile, aber als Beschriftung des Blattes. */
.kal-monat-kopf { font-size: 11px; color: var(--text-leise); letter-spacing: .02em; }
.kal-blatt { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.kal-wochentag {
  font-size: 10px; text-align: center; color: var(--text-leise);
  text-transform: uppercase; letter-spacing: .04em; padding-bottom: 2px;
}
.kal-zelle {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  min-height: 34px; padding: 3px 0; border-radius: 6px;
  border: 1px solid transparent;
}
.kal-zahl { font-size: 12px; color: var(--text); font-variant-numeric: tabular-nums; }
/* Die Randtage der Nachbarmonate stehen mit, damit das Raster aufgeht —
   sie sind aber nicht das, was man liest. */
.kal-zelle.fremd .kal-zahl { color: var(--text-leise); opacity: .45; }
.kal-zelle.heute { border-color: var(--akzent); }
.kal-zelle.heute .kal-zahl { color: var(--akzent); font-weight: 600; }
.kal-punkte { display: flex; align-items: center; gap: 2px; min-height: 6px; }
.kal-punkt { width: 4px; height: 4px; border-radius: 50%; background: var(--akzent); }
.kal-mehr { font-size: 9px; color: var(--text-leise); line-height: 1; }

/* EINE KENNZAHL MIT `data-vorschlag` IST EIN KNOPF — sie fragt nach dem, was
   dahintersteckt. Nur die mit Label; ohne Label gibt es nichts zu fragen. */
.kennzahl[data-vorschlag] { cursor: pointer; border-radius: 10px; transition: background var(--dauer-kurz) ease; }
.kennzahl[data-vorschlag]:hover,
.kennzahl[data-vorschlag]:focus-visible { background: var(--flaeche); outline: none; }

.kennzahlen { display: grid; grid-template-columns: repeat(auto-fit, minmax(90px, 1fr)); gap: 10px; }
.kennzahl { text-align: center; }
.kennzahl .wert { font-size: 26px; font-weight: 600; color: var(--text); }
.kennzahl .label { font-size: 11px; color: var(--text-leise); text-transform: uppercase; letter-spacing: .05em; margin-top: 2px; }

/* DER FUSS WANDERT MIT.
   Bis zum 18.09.2026 blieb er mittig stehen, waehrend die Kugel auswich — bei
   offenem Fenster stand die obere Haelfte damit 186 px links von der unteren.
   Genau dieser Versatz zwischen Kugel und Eingabefeld war schon einmal der
   Befund (siehe den Kommentar ueber `.buehne`), damals als Nebenwirkung der
   Widget-Spalte. Er ist wieder weg, diesmal ohne `:has()`: beide Haelften
   haengen an derselben Variablen und ruecken gemeinsam.
   `calc()` und nicht schlicht `var()` wie bei `.mitte`, weil die 20 px hier dem
   Fuss selbst gehoeren — bei `.mitte` stehen sie an der `.buehne`. */
.fuss {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  padding: 10px 20px 18px;
}
.mikro {
  width: 76px; height: 76px; border-radius: 50%; border: 2px solid var(--rand);
  background: var(--flaeche); color: var(--text-leise); display: grid; place-items: center;
  cursor: pointer; touch-action: none; transition: transform var(--dauer-kurz), border-color var(--dauer), color var(--dauer), box-shadow var(--dauer);
}
.mikro.aktiv { border-color: var(--hoert); color: var(--hoert); box-shadow: 0 0 24px var(--hoert-glow); transform: scale(1.06); }
.mikro:disabled { opacity: .4; cursor: not-allowed; }

/* ZUHÖREN UND KOPFHÖRER FLANKIEREN DAS MIKROFON.
   Beide gehoeren zum Hoeren, also stehen sie dort, wo gehoert wird — links
   und rechts vom grossen Knopf, nicht in einer Leiste darunter. Es waren
   einmal zwei dekorative `<span aria-hidden>` an dieser Stelle und die
   ECHTEN Knoepfe standen weiter unten: zwei Kopfhoerer-Symbole im Fuss, von
   denen keines das war, was man anfassen wollte. Hier steht jetzt das
   Bedienelement selbst, und sein `.an`-Zustand IST die Anzeige.
   Rund und textlos wie `.zahnrad` — 44 px bleiben mit dem Finger treffbar. */
/* DREI SPALTEN, DAMIT DAS MIKROFON IN DER MITTE BLEIBT.
   Links haengt ein Knopf, rechts haengen zwei — als schlichte Reihe waere
   der grosse Knopf um einen halben Knopf nach links gerutscht, und er ist
   das Ziel, das man im Dunkeln trifft. `1fr auto 1fr` gibt beiden Seiten
   dieselbe Breite, egal wie viele Knoepfe darin stehen; die Seiten ruecken
   ueber `justify-content` an das Mikrofon heran.
   `width: 100%` ist dabei tragend und keine Zugabe: `.fuss` ist eine
   Flex-Spalte mit `align-items: center`, das Raster waere also schrumpf-
   breit — und dann loesen sich die beiden `1fr` zu ihrem eigenen Inhalt auf
   statt zu gleichen Haelften. Gemessen auf 390 px stand das Mikrofon so
   21 px links der Mitte, auf 1280 px fiel es nicht auf. */
.mikro-ring { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; width: 100%;
  /* Ein Platz = Knopf + Gasse. Die Zahl steht hier EINMAL, weil sie an zwei
     Stellen gebraucht wird (der Weg, den „Zuhoeren" faehrt) und je Stufe
     anders ist — zweimal gepflegt waere sie nach der ersten Aenderung falsch. */
  --platz: 58px; }
.mikro-seite { display: flex; align-items: center; gap: 14px; }
.mikro-seite.links { justify-content: flex-end; padding-right: 14px; }
.mikro-seite.rechts { justify-content: flex-start; padding-left: 14px; }
.mikro-neben {
  width: 44px; height: 44px; padding: 0;
  display: grid; place-items: center;
  border: 1px solid var(--rand); border-radius: 50%;
  background: transparent; color: var(--text-leise);
  cursor: pointer;
  transition: color var(--dauer-kurz), border-color var(--dauer-kurz), background var(--dauer-kurz),
              transform var(--dauer) var(--kurve-raus), opacity var(--dauer-kurz) ease, visibility var(--dauer);
}
.mikro-neben:hover:not(:disabled) { color: var(--text); }
.mikro-neben:focus-visible { outline: 2px solid var(--akzent); outline-offset: 2px; }
.mikro-neben:disabled { opacity: .4; cursor: not-allowed; }
.mikro-neben.an { color: var(--hoert); border-color: var(--hoert); background: var(--hoert-alpha); }
/* „Ohne Rückfrage" traegt die WARNFARBE, nicht die Hoerfarbe. Ein scharfer
   Zustand, den man nicht sieht, ist eine Falle — und dieser ueberlebt einen
   Reload. Deshalb steht die Regel NACH `.mikro-neben.an` und ist enger. */
.mikro-neben.warnend.an { color: var(--fehler); border-color: var(--fehler); background: var(--fehler-alpha); }

/* DER KOPFHOERER KOMMT GEFAHREN, ER ERSCHEINT NICHT.
   Der Kopfhoerer-Modus greift nur, WAEHREND zugehoert wird (die Barge-in-
   Erkennung haengt am Pegelwaechter des laufenden Satzes). Ist Zuhoeren aus,
   ist der Knopf ohne Wirkung — dann ist er weg.

   Das LAYOUT bleibt dabei stehen: beide Plaetze sind immer reserviert, und
   bewegt wird ausschliesslich mit `transform` und `opacity`. Der naive Weg
   waere `display: none` (Umbruch, nicht animierbar) oder eine Breite von 0
   auf voll — und eine Breiten-Animation ist genau der Posten, den Chromes
   Compositing-Protokoll als `compositeFailed` meldet: jeder Bild sein
   eigenes Layout. Transform und Deckkraft laufen dagegen am Hauptthread
   vorbei.

   Solange „Zuhoeren" allein steht, schiebt es sich um einen Platz nach
   rechts und sitzt damit am Mikrofon. Beim Einschalten faehrt es auf seinen
   echten Platz zurueck — also nach links — und gibt den Platz frei, in den
   der Kopfhoerer hineinwaechst. Weil sich die Spaltenbreiten (`1fr`) dabei
   nicht aendern, bleibt das Mikrofon die ganze Zeit exakt stehen.

   `visibility` steht mit im Uebergang: eine Deckkraft von 0 allein liesse
   den Knopf anklickbar und in der Tab-Reihenfolge. Es blendet erst am ENDE
   des Ausfahrens um, weil `visibility` sichtbar bleibt, solange eine der
   beiden Seiten sichtbar ist — deshalb sieht man das Wegfahren noch. */
.mikro-neben.nur-beim-zuhoeren {
  opacity: 0; visibility: hidden; pointer-events: none;
  transform: translateX(calc(var(--platz) * -0.45)) scale(.55);
}
.mikro-ring.zuhoeren .mikro-neben.nur-beim-zuhoeren {
  opacity: 1; visibility: visible; pointer-events: auto;
  transform: none;
}
.mikro-neben.rueckt-beiseite { transform: translateX(var(--platz)); }
.mikro-ring.zuhoeren .mikro-neben.rueckt-beiseite { transform: none; }

/* Mitgehört, aber nicht gemeint: der Satz steht da, damit man sieht, dass das
   Mikrofon lebt — und blass, damit man sieht, dass er nicht gezählt hat. */
.nutzer-zeile.mitgehoert { opacity: .35; font-style: italic; }

/* Pause: ein Kopf-Knopf, kein Fuss-Schalter. Er gilt fuer alles, nicht fuer
   eine der Spielarten darunter. */
.pause, .theme {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; padding: 0;
  border: 1px solid transparent; border-radius: 50%;
  background: transparent; color: var(--text-leise);
  font: inherit; cursor: pointer;
  transition: color var(--dauer-kurz), border-color var(--dauer-kurz), background var(--dauer-kurz);
}
.pause:hover, .theme:hover { color: var(--text); background: var(--text-alpha-schwach); border-color: var(--rand); }
.pause span { display: none; }
.pause.an { color: var(--fehler); border-color: var(--fehler); background: var(--fehler-alpha); }
/* Stumm: Sprachausgabe abwürgen, ohne zu pausieren. Im aktiven Zustand
   durchgestrichener Lautsprecher, damit der Unterschied zur Pause sichtbar ist. */
.pause.stumm.an { color: var(--text-leise); border-color: transparent; background: rgba(255, 255, 255, .08); }
.pause.stumm.an svg path { d: path("M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3 3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73 4.27 3zM12 4 8 9H7.45l-.25-.25L12 14.25V4z"); }
.theme svg { display: block; }
.theme svg[hidden] { display: none; }

/* Pausiert: der Kern verliert seine Farbe. Ein stummer Assistent, der aussieht
   wie ein wacher, ist eine Falle. */
body.pausiert #kern { filter: grayscale(1); opacity: .45; }
/* Nebenfenster: sichtbar, aber stumm und taub. Ohne diesen Unterschied sucht
   man den Fehler im Mikrofon statt im zweiten Tab. */
/* Das Nebenfenster ist taub, nicht stumm: der Kern zeigt weiter, was Herold
   tut (er spricht hier ja mit). Gedaempft wird nur der Fuss — dort sitzt
   das Mikrofon, und genau das ist woanders. */
body.nebenfenster .fuss { opacity: .5; }

/* Der Stand: beilaeufig. Er aendert sich selten und will nicht gelesen,
   sondern gefunden werden — also dieselbe Farbe wie die Uhr und keine
   Umrandung, die ihn zu einem Knopf machen wuerde. */
.stand {
  font-size: 11px; color: var(--text-leise); opacity: .7; white-space: nowrap;
  /* Die Kopfzeile schreibt alles gross; ein Datum in Versalien liest sich wie
     eine Ueberschrift. Hier steht aber eine Nebenangabe. */
  text-transform: none; letter-spacing: 0;
}
.stand[hidden] { display: none; }

/* „Es kommt noch etwas." */
.wartet {
  font-size: 11px; color: var(--akzent); padding: 2px 8px;
  border: 1px solid var(--rand); border-radius: 999px; white-space: nowrap;
}
.wartet[hidden] { display: none; }

/* „Es gibt eine neuere Fassung." Unauffaellig wie der Stand, nicht wie ein
   Alarm: das HUD laedt sich ohnehin von selbst neu, sobald nichts laeuft —
   der Knopf ist nur fuer den, der nicht warten will. */
.neu {
  font-size: 11px; color: var(--text-leise); opacity: .7;
  background: none; border: 1px solid var(--rand); border-radius: 999px;
  padding: 1px 8px; cursor: pointer; white-space: nowrap;
  text-transform: none; letter-spacing: 0; font: inherit; font-size: 11px;
}
.neu:hover { opacity: 1; color: var(--akzent); border-color: var(--akzent); }
.neu[hidden] { display: none; }

.hinweis { margin: 0; font-size: 12px; color: var(--text-leise); }

/* TEXT-EINGABEFELD — über dem Mikrofon, wächst mit dem Inhalt.
   Es steht in .mitte, nicht im Fuss, damit zusätzliche Höhe den Avatar
   und die Antwortzeilen nach oben schiebt (justify-content: flex-end). */
.text-form {
  width: min(560px, 100%); margin: 10px 0 0; display: flex; justify-content: center;
}
.text-eingabe {
  width: 100%; min-height: 44px; max-height: 140px; padding: 10px 16px;
  background: var(--flaeche); color: var(--text); border: 1px solid var(--rand);
  border-radius: 18px; font: inherit; font-size: 15px; line-height: 1.4;
  resize: none; overflow: hidden; outline: none;
  transition: border-color var(--dauer-kurz), background var(--dauer-kurz), box-shadow var(--dauer-kurz);
}

/* Ruhezustand: Feld fast unsichtbar, solange es leer ist. */
.text-eingabe:placeholder-shown {
  background: transparent;
  border-color: transparent;
}
.text-eingabe:placeholder-shown::placeholder { color: var(--text-leise); opacity: 0; transition: opacity var(--dauer-kurz); }

/* Hover-Bereich: Rahmen und Platzhalter werden sichtbar. */
.text-form:hover .text-eingabe:placeholder-shown:not(:focus),
.text-eingabe:placeholder-shown:hover:not(:focus) { border-color: var(--rand); }
.text-form:hover .text-eingabe:placeholder-shown:not(:focus)::placeholder,
.text-eingabe:placeholder-shown:hover:not(:focus)::placeholder { opacity: .6; }

/* Fokus: Hintergrundfarbe und Akzent-Rahmen. */
.text-eingabe:focus { border-color: var(--akzent); background: var(--flaeche-92); }
.text-eingabe:focus::placeholder { opacity: .7; }

@media (hover: none) {
  .text-eingabe { font-size: 16px; }
  /* Touch: leicht sichtbarer Rand, weil es keinen Hover gibt. */
  .text-eingabe:placeholder-shown:not(:focus) { border-color: var(--rand); }
  .text-eingabe:placeholder-shown:not(:focus)::placeholder { opacity: .45; }
}

/* ZAHNRAD — der Knopf zum Fenster darunter.
   Er liegt bewusst NICHT im Fuss, sondern an derselben Kante wie das Panel,
   das er aufklappt (`.einstellungen` gleich darunter): dieselben 20 px von
   rechts, dieselbe Ebene (z-index 12, also ueber allem in der Buehne).
   Er rueckt vor den Fenstern nicht zur Seite, so wenig wie das Panel selbst:
   beide liegen darueber. Zwei Ecken fuer ein Ding waeren eine zu viel.
   Rund und quadratisch statt Pille: ohne Text hat eine Pille nichts mehr
   zu umschliessen, und ein 34-px-Ziel bleibt auch mit dem Finger treffbar. */
.zahnrad {
  position: fixed; right: 20px; bottom: 18px; z-index: 12;
  width: 34px; height: 34px; padding: 0;
  display: grid; place-items: center;
  border: 1px solid var(--rand); border-radius: 50%;
  background: var(--flaeche); color: var(--text-leise);
  cursor: pointer;
  transition: color var(--dauer-kurz), border-color var(--dauer-kurz), background var(--dauer-kurz);
}
.zahnrad:hover { color: var(--text); border-color: var(--akzent); }
.zahnrad:focus-visible { outline: 2px solid var(--akzent); outline-offset: 2px; }

/* ── Diagramme: Balken und Linie ───────────────────────────────────────────
   KEINE BIBLIOTHEK UND KEIN GITTER. Der Kalender darueber ist der Beleg, dass
   es ohne geht; Balken sind Rechtecke, eine Linie ist ein Pfad.
   Was hier BEWUSST FEHLT: Gitternetze, Achsenkreuze, eine Legende mit
   Kaestchen, eine zweite Farbe je Reihe. Dasselbe Urteil wie bei den Karten
   ("keine Sammelkarten mit orangenen Header-Hintergruenden", Pillen sahen "zu
   technisch" aus). Die Beschriftung steht AM Wert, nicht in einer Tafel
   daneben, die man mit dem Auge abgleichen muss.

   BEIDE BRAUCHEN DIE BREITE KARTE (`kartenFormat` in src/widgets.ts stellt
   sie darauf): in 340 px stuenden die Beschriftungen der Balken untereinander
   statt daneben, und eine Linie aus zwoelf Punkten haette je Punkt 25 px. */
/* ── Der Auftritt: das Diagramm OEFFNET sich ───────────────────────────────
   Marco am 22.09.2026: „Die Charts sehen irgendwie sehr technisch aus … wenn
   sich das Widget oeffnet, sollte mit einem kleinen Stagger-Effekt sich das
   Liniendiagramm oeffnen." Bis dahin war ein Diagramm beim Einblenden fertig
   da und fadete nur mit `block-rein` herein — ein Bild, das erscheint, statt
   einer Auskunft, die sich aufbaut.

   ALLES HIER HAENGT AN `--dia-start`, DEM STARTPUNKT DES BAUSTEINS. Das ist
   dieselbe Rechnung wie in der Kaskade weiter oben (`--k` mal 70 ms fuer die
   Karte, `--i` mal 45 ms fuer den Baustein) — das Diagramm faengt also an,
   wenn sein Kasten kommt, und nicht wenn die Seite laedt.

   UND DER RUHEZUSTAND IST DAS FERTIGE DIAGRAMM. Jede Regel unten beschreibt
   nur, WOHER etwas kommt; wo es hingehoert, steht in den gewoehnlichen Regeln
   darunter. Deshalb `backwards` und nicht `both`: vor dem Start gilt das
   `from`, nach dem Ende faellt alles auf die Grundregel zurueck. Das ist
   kein Geschmack, sondern der Riegel:
     · Ohne `.staffelt` (also bei JEDER Aktualisierung) laeuft nichts, und die
       Karte steht still — sonst zeichnete sich die Linie bei jedem „weiter"
       des Durchgangs neu.
     · Bei `prefers-reduced-motion` genuegt `animation: none` weiter unten,
       und das Diagramm ist vollstaendig da. Mit `both` bliebe es im `from`
       stehen — ein UNSICHTBARES Diagramm fuer genau die Leute, die nur keine
       Bewegung wollten. */
.dia-balken, .dia-linie { --dia-start: calc(var(--k, 0) * 70ms + var(--i, 0) * 45ms); }

/* DIE LINIE LAEUFT VON LINKS NACH RECHTS AUF — ueber einen Wisch, nicht ueber
   `stroke-dashoffset`.
   GEMESSEN am 22.09.2026, weil der naheliegende Weg hier nicht traegt: mit
   `pathLength="100"`, `stroke-dasharray: 100` und `stroke-dashoffset: 100`
   lagen in Chromium noch 56 % der Tinte auf dem Blatt (677 von 1204 Punkten),
   bei Versatz 50 waren es 75 % — kein Aufbau, sondern ein Streifenmuster.
   Derselbe Versuch OHNE `vector-effect: non-scaling-stroke`: 0, dann exakt
   die Haelfte, dann alles. Der Grund ist die Kombination, die dieses Blatt
   braucht — `non-scaling-stroke` rechnet Strichmuster in BILDSCHIRM-Einheiten,
   und `pathLength` normiert nur die Nutzereinheiten. Wer das Strichmuster
   zurueckholen will, muesste die gezerrte Laenge in Pixeln kennen, also genau
   das, was `preserveAspectRatio="none"` hier vermeidet.
   Ein Wisch ueber `clip-path` leistet dasselbe, denn die Kurve ist in x
   streng monoton: eine Kante, die nach rechts laeuft, IST die zeichnende
   Hand. Er nimmt die Flaeche gleich mit, was der Strichweg nicht koennte.
   Die rechte Kante endet bei -8px statt 0: der runde Strichabschluss und der
   Punkt auf dem letzten Wert ragen ueber das Blatt hinaus (`overflow:
   visible`), und ein Wisch, der genau an der Kante haelt, schnitte sie im
   letzten Vollbild ab. */
.widget.staffelt .dia-blatt {
  animation: dia-zug .64s cubic-bezier(.25, .8, .3, 1) backwards;
  animation-delay: calc(var(--dia-start) + 100ms);
}
@keyframes dia-zug {
  from { clip-path: inset(0 100% 0 0); }
  to { clip-path: inset(0 -8px 0 0); }
}
/* DIE PUNKTE SETZEN SICH DANACH, NICHT MIT. Sie sind die Lesehilfe auf einem
   Verlauf, der schon da ist — kaemen sie mit dem Strich, waere die Bewegung
   eine einzige breite Front. `stroke-width` statt `transform`, weil ein
   Strich ohne Laenge keine Flaeche hat, um die sich ein `transform-origin`
   legen koennte. Es sind hoechstens zwoelf Punkte fuer je 240 ms, einmal. */
.widget.staffelt .dia-punkt {
  animation: dia-setzen .24s cubic-bezier(.2, .9, .3, 1.2) backwards;
  animation-delay: calc(var(--dia-start) + 700ms + var(--p, 0) * 35ms);
}
@keyframes dia-setzen {
  from { stroke-width: 0; opacity: 0; }
  to { stroke-width: 6; opacity: 1; }
}

.dia-balken { display: flex; flex-direction: column; gap: 7px; }
/* DREI SPALTEN UND DIE MITTLERE DEHNT SICH. Die Beschriftung links haelt eine
   feste Breite, damit die Balken auf EINER Kante beginnen — sonst faengt
   jeder woanders an und die Laengen sind nicht mehr vergleichbar, was der
   einzige Zweck eines Balkendiagramms ist.
   `minmax(0, …)` an der Spur: ohne die Null waechst sie mit ihrem Inhalt und
   schiebt die Wertspalte aus der Karte. */
.dia-zeile {
  display: grid; grid-template-columns: minmax(64px, 22%) minmax(0, 1fr) auto;
  gap: 10px; align-items: center;
}
.dia-label {
  font-size: 12px; color: var(--text-leise);
  /* Ein umbrechendes Etikett macht die Zeile hoch und die Reihe ungleich —
     lieber gekuerzt und im `title` vollstaendig. */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* DIE SPUR IST SICHTBAR, AUCH WO DER BALKEN KURZ IST. Sie ist der Massstab:
   ohne sie sieht man bei einem 8-%-Balken nicht, worauf sich die 8 % bezieht. */
.dia-spur { height: 10px; border-radius: 5px; background: var(--akzent-alpha); overflow: hidden; }
/* EIN VERLAUF UND KEINE VOLLTONFARBE. Ein Balken in einer einzigen Farbe ist
   ein Kaestchen mit einer Kante; derselbe Balken, der aus der Spur heraus
   zur Spitze hin dichter wird, hat seinen Schwerpunkt dort, wo auch die Zahl
   steht. `--akzent-glow` ist die Akzentfarbe bei 45 % — der Name kommt vom
   ersten Verwender, der Wert ist genau der weiche Anfang, der hier gebraucht
   wird. */
.dia-fuellung {
  display: block; height: 100%; border-radius: 5px;
  background: linear-gradient(90deg, var(--akzent-glow), var(--akzent));
  /* Der Balken waechst beim Erscheinen aus seiner Kante heraus — und seit dem
     22.09.2026 EINER NACH DEM ANDEREN (`--n` an der Zeile, siehe hud.js).
     Vier Balken, die zur selben Millisekunde losfahren, sind ein Block, der
     aufklappt; 70 ms Abstand machen daraus eine Reihe, die sich fuellt —
     dieselbe Rechnung wie bei den Karten und den Bausteinen.
     `transform: scaleX` und NICHT `width`: eine Breite laesst bei jedem Bild
     das Layout neu rechnen, ein `transform` bleibt beim Compositor. */
  transform-origin: left center;
}
.widget.staffelt .dia-fuellung {
  animation: dia-wachsen .5s cubic-bezier(.2, .7, .3, 1) backwards;
  animation-delay: calc(var(--dia-start) + var(--n, 0) * 70ms);
}
@keyframes dia-wachsen { from { transform: scaleX(0); } to { transform: none; } }
.dia-wert {
  font-size: 12px; font-variant-numeric: tabular-nums; color: var(--text);
  white-space: nowrap;
}
/* Die Zahl kommt MIT IHREM Balken, nicht vor ihm. Stuenden alle Zahlen
   sofort da, waere die Staffelung der Balken eine Verzoegerung ohne Zweck —
   die Auskunft waere ja schon vollstaendig gelesen. */
.widget.staffelt .dia-wert {
  animation: dia-zahl .3s ease-out backwards;
  animation-delay: calc(var(--dia-start) + var(--n, 0) * 70ms + 140ms);
}
@keyframes dia-zahl { from { opacity: 0; transform: translateX(-4px); } to { opacity: 1; transform: none; } }

.dia-linie { position: relative; display: flex; flex-direction: column; gap: 6px; }
/* `preserveAspectRatio: none` zieht ein 100x100-Koordinatensystem auf jede
   Kartenbreite — so muss das HUD die Pixelbreite nicht kennen und die Kurve
   stimmt in beiden Formaten. Die Strichstaerke wird dabei MITGEZERRT, und aus
   2 px wuerden waagerecht 14: `vector-effect` haelt sie fest. */
.dia-blatt { display: block; width: 100%; height: 160px; overflow: visible; }
.dia-kurve {
  fill: none; stroke: var(--akzent); stroke-width: 2;
  stroke-linejoin: round; stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
/* DIE FLAECHE IST DER GRUND, WARUM ES OHNE ACHSEN GEHT: sie gibt dem Verlauf
   einen Boden, an dem das Auge die Hoehe misst. Ein Gitternetz taete dasselbe,
   nur mit zehn Linien mehr.
   SIE BLENDET NACH UNTEN AUS. Als Volltonflaeche hatte sie eine zweite harte
   Kante — unten, quer ueber die ganze Breite — und genau solche Kanten machen
   aus einer Auskunft ein Messgeraet. Der Verlauf steht als Element in
   `public/index.html`; die Farbe faerben die beiden Regeln darunter, damit er
   den Themenwechsel mitmacht. Die Reservefarbe hinter dem `url()` greift,
   falls das Blatt einmal ohne diesen Vorrat gerendert wird — ohne sie waere
   die Flaeche dann gar nicht da. */
.dia-flaeche { fill: url(#dia-flaeche-verlauf) var(--akzent-alpha); stroke: none; }
.dia-verlauf-hoch { stop-color: var(--akzent); stop-opacity: .34; }
.dia-verlauf-tief { stop-color: var(--akzent); stop-opacity: 0; }
/* Der Vorrat zeigt nichts, er haelt nur den Verlauf. Ohne feste Groesse
   belegte das leere Blatt die vollen 300 x 150 px, die ein `<svg>` ohne
   Maße vorgibt — oben links ueber dem Kopf. */
.dia-vorrat { position: absolute; width: 0; height: 0; }
/* DIE PUNKTE AUF DER KURVE — Striche ohne Laenge mit rundem Abschluss.
   Ein `<circle>` waere unter `preserveAspectRatio="none"` ein liegendes Ei;
   `non-scaling-stroke` macht aus dem Abschluss dagegen einen runden Punkt in
   Bildschirmeinheiten. Sie sitzen auf der Kurve und tragen deshalb ihre
   Farbe, nicht die der Flaeche. */
.dia-punkt {
  fill: none; stroke: var(--akzent); stroke-width: 6; stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
/* Die Spanne statt einer Achsenskala: EINE Zahl, die sagt, wie hoch oben ist. */
.dia-spanne {
  position: absolute; top: 0; right: 0;
  font-size: 11px; font-variant-numeric: tabular-nums; color: var(--text-leise);
}
.dia-enden {
  display: flex; justify-content: space-between;
  font-size: 11px; color: var(--text-leise);
}

/* Einstellungen: ein kleines Fenster ueber dem Fuss, kein eigener Bildschirm.
   Was hier steht, stellt man einmal ein und dann monatelang nicht mehr. */
:root {
  /* Die Einstellungen teilen sich EINE Feldhoehe. Drei Bauformen stehen hier
     untereinander (Auswahl, Textfeld, Regler) und sahen vorher je nach
     Browser verschieden hoch aus — auf derselben Zeile, nebeneinander. */
  --ein-feld-h: 34px;
}

/* ══ DAS EINSTELLUNGSFENSTER ══════════════════════════════════════════════
   Es hat dieselbe Bauform wie ein Widget: getoente Flaeche, 14 px Radius,
   Blur dahinter, weicher Schatten. Bis zum 21.09.2026 war es davon das
   einzige Element, das nach Browser-Voreinstellung aussah — native Auswahl-
   felder, native Regler, native Haken und zwei Knoepfe, die GAR keinen Stil
   hatten (`class="dauerhoeren"` war vom Mikrofon-Knopf abgeschrieben, die
   Regel dazu gibt es nicht und gab es nie). Marco: "Mach das mal richtig
   schoen und sexy, so wie der Rest der Seite."

   HOEHE NACH INHALT. `inset: 0` mit `margin: auto` zentriert — und streckt
   dabei auf die erlaubte Hoehe, weil die Hoehe `auto` ist. Das Fenster stand
   deshalb IMMER 580 px hoch da, auch mit vier Zeilen darin; unter dem
   Charakter-Reiter lagen 600 px Nichts. `fit-content` nimmt die Streckung
   zurueck, `max-height` bleibt die Obergrenze. */
.einstellungen {
  position: fixed; inset: 0; margin: auto; z-index: 100;
  width: min(480px, calc(100vw - 40px));
  height: fit-content; max-height: min(620px, calc(100vh - 40px));
  background: var(--flaeche-92); border: 1px solid var(--rand); border-radius: 14px;
  padding: 16px 20px 18px; display: flex; flex-direction: column; gap: 14px;
  backdrop-filter: blur(12px);
  box-shadow: 0 18px 40px var(--schatten), 0 0 0 100vw rgba(0, 0, 0, .4);
}
.einstellungen[hidden] { display: none; }

/* DIE EINSTELLUNGEN TAUCHEN AUF, STATT AUFZUPLOPPEN (23.09.2026).
   `hidden` bleibt die einzige Wahrheit, hud.js aendert sich nicht. Den Weg
   dazwischen tragen zwei neuere Bausteine: `@starting-style` gibt dem
   Auftritt einen Anfang (ein Element, das aus `display: none` kommt, hat
   sonst keinen Vorzustand, von dem aus etwas laufen koennte), und
   `transition-behavior: allow-discrete` haelt beim Schliessen das `display`
   so lange stehen, bis die Abblende durch ist. Die Abdunklung (der 100vw-
   Schatten) blendet mit, weil sie am selben Kasten haengt.
   GEHEN IST KUERZER ALS KOMMEN — wer schliesst, ist fertig und will die
   Buehne zurueck; die Dauer der [hidden]-Regel gilt fuer den Weg DORTHIN. */
.einstellungen {
  transition: opacity var(--dauer) var(--kurve-raus), transform var(--dauer) var(--kurve-raus),
              display var(--dauer) allow-discrete;
}
.einstellungen[hidden] {
  opacity: 0; transform: translateY(6px) scale(.98); pointer-events: none;
  transition-duration: var(--dauer-kurz); transition-timing-function: ease-in;
}
@starting-style {
  .einstellungen:not([hidden]) { opacity: 0; transform: translateY(6px) scale(.98); }
}
@media (prefers-reduced-motion: reduce) { .einstellungen { transition: none; } }
.ein-kopf { display: flex; justify-content: space-between; align-items: center;
  font-size: 12px; letter-spacing: .06em; text-transform: uppercase; color: var(--akzent); }

/* ── Die Reiter ────────────────────────────────────────────────────────────
   Eine Segmentleiste statt unterstrichener Reiter. Der Grund ist nicht Mode:
   unterstrichene Reiter sind die Bauform einer SEITE mit Unterseiten, und
   dieses Fenster ist keine Seite — es ist ein Kasten mit drei Faechern. Die
   Pillenform ist ausserdem die Sprache des uebrigen HUD (`.merkmal`,
   `.badge`, `.wartet`, die runden Fussknoepfe). */
.ein-tabs {
  display: flex; gap: 2px; padding: 3px;
  background: var(--text-alpha-schwach); border: 1px solid var(--rand);
  border-radius: 999px;
}
.ein-tab {
  flex: 1; background: none; border: 0; border-radius: 999px;
  padding: 6px 4px; color: var(--text-leise);
  font: inherit; font-size: 12.5px; font-weight: 500; cursor: pointer;
  transition: color var(--dauer-kurz) ease, background var(--dauer-kurz) ease, box-shadow var(--dauer-kurz) ease;
}
.ein-tab:hover:not(.aktiv) { color: var(--text); }
.ein-tab:focus-visible { outline: 2px solid var(--akzent); outline-offset: 2px; }
/* Das aktive Segment kommt nach VORN — helle Flaeche und ein eigener kleiner
   Schatten. Nur Farbe zu wechseln reicht bei gleicher Flaeche nicht: man sieht
   dann zwar, welches Wort blau ist, aber nicht, dass es ein Fach ist. */
.ein-tab.aktiv {
  color: var(--text); background: var(--flaeche);
  box-shadow: 0 1px 2px var(--schatten), inset 0 0 0 1px var(--rand);
}

.ein-panels { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; }
/* KEIN `flex: 1` MEHR AUF DEM FACH. Es streckte das Fach auf die volle Hoehe
   des Fensters und war die zweite Haelfte der Leere von oben. */
.ein-panel { display: flex; flex-direction: column; gap: 12px; }
.ein-panel[hidden] { display: none; }

/* ── Eine Zeile: Name links, Bedienelement rechts ───────────────────────── */
.ein-zeile { display: flex; align-items: center; gap: 12px; font-size: 13px; position: relative; }
.ein-zeile[hidden] { display: none; }
.ein-name { width: 84px; color: var(--text-leise); flex: none; }

/* ── Felder ────────────────────────────────────────────────────────────────
   `appearance: none` nimmt dem Auswahlfeld sein Betriebssystem-Aussehen; das
   Zeichen rechts malen wir selbst, damit es die Schriftfarbe traegt und in
   beiden Themen stimmt. Ein Pfeil als Bilddatei koennte das nicht. */
.ein-zeile label, .ein-zeile select { font: inherit; }
.ein-zeile-hoch { display: block; }
.ein-zeile-hoch .ein-name { display: block; margin-bottom: 6px; }
.ein-zeile-hoch textarea { display: block; width: 100%; resize: vertical; min-height: 120px; font-family: monospace; font-size: 12px; }
.ein-zeile select,
.ein-zeile input[type="text"],
.ein-zeile input[type="number"],
.ein-zeile input[type="time"],
.ein-zeile textarea {
  flex: 1; min-width: 0; font: inherit; font-size: 13px;
  padding: 0 10px; height: var(--ein-feld-h);
  background: var(--grund); color: var(--text);
  border: 1px solid var(--rand); border-radius: 9px;
  transition: border-color var(--dauer-kurz) ease, box-shadow var(--dauer-kurz) ease;
}
.ein-zeile select { appearance: none; -webkit-appearance: none; padding-right: 30px; cursor: pointer; }
.ein-zeile textarea { height: auto; padding: 8px 10px; resize: vertical; line-height: 1.45; }
.ein-zeile select:hover, .ein-zeile input[type="text"]:hover, .ein-zeile input[type="number"]:hover,
.ein-zeile input[type="time"]:hover, .ein-zeile textarea:hover { border-color: var(--text-leise); }
.ein-zeile select:focus-visible,
.ein-zeile input[type="text"]:focus-visible,
.ein-zeile input[type="number"]:focus-visible,
.ein-zeile input[type="time"]:focus-visible,
.ein-zeile textarea:focus-visible {
  outline: none; border-color: var(--akzent);
  box-shadow: 0 0 0 3px var(--akzent-alpha);
}
/* Das Winkelzeichen des Auswahlfeldes. Es sitzt am Label (dem Flex-Rahmen),
   nicht am Feld — ein `::after` am `<select>` selbst zeichnet kein Browser. */
.ein-zeile:has(select)::after {
  content: ""; position: absolute; right: 12px; top: 50%;
  width: 7px; height: 7px; margin-top: -3px;
  border-right: 1.5px solid var(--text-leise); border-bottom: 1.5px solid var(--text-leise);
  transform: rotate(45deg); pointer-events: none;
}
.ein-zeile:has(textarea) { align-items: flex-start; }
.ein-zeile:has(textarea) .ein-name { padding-top: 8px; }
.ein-wert {
  min-width: 42px; text-align: right; flex: none;
  color: var(--akzent); font-size: 12px; font-variant-numeric: tabular-nums;
}

/* ── Regler ────────────────────────────────────────────────────────────────
   Der gefuellte Teil links vom Griff kommt aus `--fuell` (in Prozent, gesetzt
   von `fuelleRegler` in hud.js). Ohne ihn waere die Bahn eine gleichmaessige
   Linie, und der Regler saehe aus wie eine Verzierung statt wie ein Wert. */
.einstellungen input[type="range"] {
  flex: 1; min-width: 0; appearance: none; -webkit-appearance: none;
  background: transparent; height: 18px; margin: 0; cursor: pointer;
}
.einstellungen input[type="range"]::-webkit-slider-runnable-track {
  height: 4px; border-radius: 999px;
  background: linear-gradient(to right,
    var(--akzent) 0 calc(var(--fuell, 50%)),
    var(--rand) calc(var(--fuell, 50%)) 100%);
}
.einstellungen input[type="range"]::-moz-range-track { height: 4px; border-radius: 999px; background: var(--rand); }
.einstellungen input[type="range"]::-moz-range-progress { height: 4px; border-radius: 999px; background: var(--akzent); }
.einstellungen input[type="range"]::-webkit-slider-thumb {
  appearance: none; -webkit-appearance: none;
  width: 14px; height: 14px; margin-top: -5px; border-radius: 50%;
  background: var(--flaeche); border: 2px solid var(--akzent);
  box-shadow: 0 1px 3px var(--schatten);
  transition: transform var(--dauer-kurz) ease, box-shadow var(--dauer-kurz) ease;
}
.einstellungen input[type="range"]::-moz-range-thumb {
  width: 14px; height: 14px; border-radius: 50%; border: 2px solid var(--akzent);
  background: var(--flaeche); box-shadow: 0 1px 3px var(--schatten);
}
.einstellungen input[type="range"]:hover::-webkit-slider-thumb { transform: scale(1.12); }
.einstellungen input[type="range"]:active::-webkit-slider-thumb { box-shadow: 0 0 0 5px var(--akzent-alpha); }
.einstellungen input[type="range"]:focus-visible { outline: none; }
.einstellungen input[type="range"]:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 5px var(--akzent-alpha); }

/* ── Der Schalter ──────────────────────────────────────────────────────────
   Ein Modul ist an oder aus — ein Haken sagt das auch, aber er sagt es in der
   Handschrift des Betriebssystems, und daneben steht ein HUD, das nichts
   davon uebernimmt. Das echte `<input>` bleibt erhalten und nur unsichtbar:
   Tastatur, Vorlesegeraete und der vorhandene `change`-Listener haengen
   daran. Gemalt wird das `<span>` daneben. */
.schalter-feld {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}
.schalter {
  flex: none; position: relative; display: inline-block;
  width: 34px; height: 20px; border-radius: 999px;
  background: var(--text-alpha-schwach); border: 1px solid var(--rand);
  transition: background var(--dauer-kurz) ease, border-color var(--dauer-kurz) ease;
}
.schalter::after {
  content: ""; position: absolute; left: 2px; top: 2px;
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--text-leise);
  transition: transform var(--dauer-kurz) var(--kurve-raus), background var(--dauer-kurz) ease;
}
.schalter-feld:checked + .schalter { background: var(--akzent); border-color: var(--akzent); }
.schalter-feld:checked + .schalter::after { transform: translateX(14px); background: #fff; }
.schalter-feld:focus-visible + .schalter { outline: 2px solid var(--akzent); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) {
  .schalter, .schalter::after { transition: none; }
}

/* ── Der Fuss ──────────────────────────────────────────────────────────────
   Beide Knoepfe trugen `class="dauerhoeren"` — abgeschrieben von der ID des
   Mikrofon-Knopfes. Eine CSS-Regel dieses Namens hat es nie gegeben, also
   standen dort zwei graue Browser-Knoepfe in einem sonst durchgestalteten
   Fenster. "Von vorn" verwirft die Unterhaltung und traegt deshalb die
   ruhigere Form: ein Knopf, der etwas wegwirft, soll nicht der einladendste
   auf dem Schirm sein. */
.ein-fuss {
  display: flex; align-items: center; gap: 8px;
  padding-top: 12px; border-top: 1px solid var(--rand);
}
.ein-knopf {
  font: inherit; font-size: 12.5px; font-weight: 500;
  padding: 0 14px; height: 32px; border-radius: 999px;
  border: 1px solid var(--rand); background: transparent; color: var(--text);
  cursor: pointer; white-space: nowrap;
  transition: color var(--dauer-kurz) ease, background var(--dauer-kurz) ease, border-color var(--dauer-kurz) ease;
}
.ein-knopf:hover { background: var(--text-alpha-schwach); border-color: var(--text-leise); }
.ein-knopf:focus-visible { outline: 2px solid var(--akzent); outline-offset: 2px; }
.ein-knopf.haupt { border-color: var(--akzent); color: var(--akzent); background: var(--akzent-alpha); }
.ein-knopf.haupt:hover { background: var(--akzent); color: var(--flaeche); border-color: var(--akzent); }
/* Leise, aber nicht unsichtbar: ohne eigenen Rand sah "Von vorn" aus wie
   eine Beschriftung neben dem Knopf daneben, nicht wie einer. */
.ein-knopf.leise { color: var(--text-leise); }
.ein-knopf.leise:hover { color: var(--fehler); border-color: var(--fehler); background: var(--fehler-alpha); }
.ein-hinweis { font-size: 11px; color: var(--text-leise); flex: 1; text-align: right; line-height: 1.3; }

/* DER SCHLEIER MUSS UEBER ALLES, WAS MAN OHNE TOKEN NICHT ANFASSEN SOLL.
   Er lag auf 10 und damit UNTER der Einstellungs-Ebene (12). Solange der
   Einstellungen-Knopf im Fuss mitlief, fiel das nicht auf: Fuss-Inhalt ist
   nicht positioniert und verschwand brav hinter dem Schleier. Seit das
   Zahnrad fest auf Ebene 12 haengt, stand es als einziges Bedienelement auf
   dem Schleier — anklickbar, mit einem Panel dahinter, das ohne Token
   ohnehin nichts laden kann. Gemessen am 19.09.2026 live: elementFromPoint
   in der Knopfmitte traf das Zahnrad, nicht den Dialog. */
.token-dialog { position: fixed; inset: 0; background: var(--dialog-hintergrund); display: grid; place-items: center; z-index: 20; }
.token-dialog[hidden] { display: none; }
.token-dialog form { display: flex; flex-direction: column; gap: 10px; width: min(320px, 90vw); }
.token-dialog label { font-size: 13px; color: var(--text-leise); }
.token-dialog input, .token-dialog button {
  font: inherit; padding: 10px 14px; border-radius: 10px; border: 1px solid var(--rand);
  background: var(--flaeche); color: var(--text);
}
.token-dialog button { background: var(--akzent); color: #06211d; border: 0; cursor: pointer; }

@media (max-width: 820px) {
  body { height: 100dvh; grid-template-rows: auto minmax(0, 1fr) auto; }
  .buehne { padding: 0 14px; }
  /* KEINE GASSEN AUF DEM TELEFON, SONDERN EINE SPALTE — UND DIE REIHENFOLGE
     BLEIBT DIE VOM GROSSEN SCHIRM: was offen ist, dann was vorliegt, dann was
     gesagt wird. Sie wird hier ausdruecklich gelegt, weil sie im Markup eine
     andere ist (`.mitte` steht zuerst, damit sie am grossen Schirm im
     Lesefluss vor den Raendern liegt).
     `display: block` hat das frueher erledigt — aber Block-Fluss heisst
     Markup-Reihenfolge, und damit staende auf dem Telefon der Antwortsatz
     ueber den Karten. Genau das soll er nicht mehr. */
  .buehne {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto auto minmax(0, 1fr) auto;
    overflow-y: auto;
  }
  .laeuft-gerade { grid-column: 1; grid-row: 2; padding-left: 0; align-self: start; }
  .fensterzeile { grid-column: 1; grid-row: 3; }
  .mitte { grid-column: 1; grid-row: 4; }
  /* Auf dem Telefon tritt der Avatar noch weiter zurueck: dort bleibt neben
     den Karten kein Platz fuer beides. */
  body.fenster-offen #kern { height: 28px; }
  #kern { width: min(440px, 100%); }
  .gespraech { min-height: 0; }
  /* KEIN SLIDER AUF DEM TELEFON, SONDERN DIE SEITE SELBST.
     Die Zeile ist hier keine Zeile NEBEN dem Text, sondern die Fortsetzung
     der Seite darunter: sie stellt sich hochkant, faengt oben an und scrollt
     nicht in sich, sondern macht die Buehne laenger. Waagerecht zu blaettern
     waere hier doppelt falsch — die Wischgeste gehoert schon der Seite. Zwei ineinandergeschachtelte
     Scrollflaechen auf einem Telefon sind eine zu viel, und die innere kaeme
     genau der Wischgeste in die Quere, mit der man die Seite liest.
     Der Hoehendeckel faellt aus demselben Grund (`.widget` gleich darunter). */
  .widgets {
    height: auto; max-height: none; align-self: start;
    flex-direction: column; align-items: stretch; justify-content: flex-start;
    overflow-x: visible; overflow-y: visible; scroll-snap-type: none;
    padding: 4px 0 8px;
  }
  /* Untereinander gibt es keinen Auslauf zur Achse. */
  .widgets.laeuft-ueber { padding-inline: 0; }
  /* Untereinander gibt es nichts zu blaettern — und damit auch keine Spalte
     freizuhalten. Hier ist `display: none` richtig, wo es an der Bahn falsch
     waere: am grossen Schirm nimmt der Pfeil seine Spalte mit, auf dem Telefon
     gibt es die Spalten gar nicht mehr.

     UND MIT DEN SPALTEN FAELLT DER NEGATIVE RAND — SONST SCHNEIDET ER DIE
     KARTE AB.
     Die -38 px am grossen Schirm bezahlen die beiden Pfeilspalten aus der
     246 px breiten Randgasse. Hier gibt es weder Pfeile noch Gasse: die
     Buehne hat in diesem Zweig EINE Spalte und nur 14 px Polster, der Rand
     zieht die Zeile also 24 px je Seite ueber den Bildschirm hinaus. Und weil
     hier nichts scrollt (`overflow: visible`, damit die Seite die Scrollflaeche
     bleibt), wird der Ueberstand nicht erscrollbar, sondern ABGESCHNITTEN.
     Marco am 22.09.2026 ueber den ganzen Bereich gemessen, Karte gegen
     Fensterkante:

       700 px Fenster → Karte 748, ragt links 24 / rechts 24 heraus
       800 px Fenster → Karte 848, ragt 24 / 24
       820 px Fenster → Karte 868, ragt 24 / 24
       821 px Fenster → Karte 289, sauber

     Also nicht an einer Kante, sondern durchgehend bis 820 px — und der
     Seitenueberlauf war dabei die ganze Zeit 0, weshalb die ueblichen Proben
     nichts meldeten. Gemessen werden muss die Karte gegen die FENSTERKANTE.
     `0` und nicht `-14`: die Karte haelt damit das Polster der Buehne ein, so
     wie vor dem Slider-Umbau. Ein negativer Rand liesse sie bis an den
     Bildschirmrand laufen — das waere eine eigene Gestaltungsentscheidung und
     nicht die Behebung dieses Fehlers. */
  .fensterzeile { display: block; margin-inline: 0; }
  .fenster-blaettern, .fenster-blaettern[hidden] { display: none; }
  /* KEINE 340 PX AUF EINEM 390-PX-SCHIRM. Die feste Breite ist die Zahl des
     Sliders; hier ist die Karte die Seite und nimmt sie ganz.

     UND KEIN HOCHKANTFORMAT. 340 x 480 ist die Statur einer Karte, die NEBEN
     anderen in einer Bahn steht — auf dem Telefon steht sie allein und ueber
     die volle Breite, und dieselben 480 px waeren dort eine halbleere Karte
     mit einer Luftsaeule darin. Die Gleichheit der Hoehen loest sich hier von
     selbst auf: untereinander gibt es keine gemeinsame Unterkante, an der
     etwas ausfransen koennte.
     Also `height: auto` — und weil der Koerper dann nicht mehr gegen eine
     feste Hoehe waechst, auch `flex: none` an ihm: mit `flex: 1` und einer
     `auto`-hohen Karte bliebe seine Basis 0 und der Inhalt draengte sich. */
  .widget {
    flex: 0 0 auto;
    width: auto; height: auto; max-height: none;
    backdrop-filter: blur(8px);
  }
  .widget-inhalt { flex: none; overflow-y: visible; }
  .hinweis { display: none; }
  /* Der Einstellungs-Knopf steht neben den Sprachschaltern. Die rechte Gasse
     verhindert, dass er auf schmalen Geraeten den letzten Schalter ueberdeckt. */
  .fuss { padding-right: 62px; }
  /* ... und genau diese Gasse kostet die Mitte, wenn die Knoepfe zu breit
     bleiben: rechts vom Mikrofon stehen ZWEI, links einer. Passt die rechte
     Gruppe nicht mehr in ihre `1fr`-Spalte, waechst die Spalte ueber ihren
     Anteil hinaus und schiebt das Mikrofon nach links — auf 360 px waren das
     15 px. Kleinere Knoepfe und engere Gassen halten die Gruppe im Anteil;
     40 px bleiben mit dem Finger treffbar. */
  .mikro-seite { gap: 10px; }
  .mikro-seite.links { padding-right: 10px; }
  .mikro-seite.rechts { padding-left: 10px; }
  .mikro-neben { width: 40px; height: 40px; }
  .mikro-ring { --platz: 50px; }
}

/* SEHR SCHMAL: die linke Gruppe traegt jetzt ZWEI Knoepfe, die rechte einen.
   Passt die linke nicht mehr in ihre `1fr`-Spalte, waechst die Spalte ueber
   ihren Anteil — und dann WANDERT das Mikrofon beim Einschalten von Zuhoeren
   (gemessen auf 320 px: cx 139 -> 158). Ein Knopf, der unter dem Finger
   wegrutscht, ist schlimmer als ein kleiner Knopf, also wird hier gekuerzt.
   34 px ist die Groesse, die das Zahnrad seit je hat — finger-tauglich genug.
   Die 62-px-Gasse rechts bleibt unangetastet: sie haelt den letzten Knopf
   unter dem Zahnrad weg, und ohne sie waere die Kollision zurueck. */
@media (max-width: 340px) {
  .mikro-seite { gap: 6px; }
  .mikro-seite.links { padding-right: 6px; }
  .mikro-seite.rechts { padding-left: 6px; }
  .mikro-neben { width: 34px; height: 34px; }
  .mikro-ring { --platz: 40px; }
  .zahnrad { right: 14px; bottom: 16px; }
}
/* ++ TOASTS FUER WERKZEUG-AUFRUFE ++ */

/* -- TOASTS ENDE -- */

/* Die Schlange: was nacheinander drankommt.
   Links, weil rechts die Fenster liegen — und weil sie NICHT im Weg sein
   soll: sie beantwortet eine Frage im Vorbeigehen ("kommt da noch was und
   was?"), sie ist kein Bedienelement. Deshalb schmal, leise und ohne Knopf. */
/* SEIT DEM 22.09.2026 STEHT SIE RECHTS UND IM RASTER.
   Sie lag `fixed` in der LINKEN Gasse und musste ihre Breite ueber
   `--schlangespalte` anmelden, damit das Raster ihr auswich. Die Themenliste
   wich ihr ebenfalls aus — und gab dafuer ihren Horizont auf
   (`body.schlange-da .themen`, jetzt weg). Zwei Mieter in einer Gasse, und
   der Preis war eine Invariante.
   Seit die Karten die Mitte haben, ist die rechte Gasse frei und beantwortet
   dieselbe Frage wie die Schlange: was tut Herold gerade? Also steht sie dort
   — als Rasterzelle, die ihren Platz selbst nimmt, statt ihn anzumelden. */
.schlange {
  flex: 0 1 auto; min-height: 0; overflow-y: auto;
  background: var(--flaeche); border: 1px solid var(--rand); border-radius: 12px;
  padding: 10px 12px; font-size: 12px;
}
.schlange[hidden] { display: none; }
.schlange-kopf {
  font-size: 11px; color: var(--text-leise); text-transform: uppercase;
  letter-spacing: .06em; margin-bottom: 6px;
}
.schlange-liste { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.schlange-zeile { display: flex; gap: 7px; align-items: baseline; color: var(--text-leise); line-height: 1.35; }
.schlange-zeile .zeichen { flex: 0 0 auto; opacity: .7; }
/* Der laufende Punkt ist der einzige, der volle Farbe bekommt: man soll auf
   einen Blick sehen, wo die Liste gerade steht. */
.schlange-zeile.laeuft { color: var(--text); }
.schlange-zeile.laeuft .zeichen { color: var(--akzent); opacity: 1; }
.schlange-quelle { font-size: 10px; opacity: .65; text-transform: uppercase; letter-spacing: .04em; }

/* Weg ist weg. Die Gasse bleibt trotzdem stehen (Invariante 2) — sie traegt
   dann den Werkzeug-Stapel allein, und das Mikrofon im Fuss bleibt mittig. */
@media (max-width: 900px) {
  .schlange { display: none; }
}

/* ── DIE THEMENKARTE: jedes offene Thema im Deck ───────────────────────────
   Bis zum 23.09.2026 stand hier die Themenliste, der Index links neben der
   Kugel — auf dem Telefon sogar ganz ausgeblendet. Marco hat sie an dem Tag
   gestrichen (Variante 1): jedes offene Thema ist eine Karte in derselben
   Bahn wie alles andere, kompakt oder aufgeklappt. Die Gasse, in der die
   Liste stand, gehoert seither der Bahn (`.fensterzeile`, oben).

   KOMPAKT HEISST EINE ZEILE, NICHT EINE KLEINE KARTE. Marke, Name, Zeitzeile
   — was man zum Wiederfinden braucht, nicht zum Lesen. Die ganze Flaeche ist
   ein Knopf (`aria-expanded="false"`), das × daneben ein eigener. Sie traegt
   denselben Rahmen und dieselbe Rangfarbe wie jede Karte: eine zweite Form
   fuer dieselbe Sache waere wieder die Leiste. */
.widget.kompakt {
  --karte-hoch: auto;
  flex-direction: row; align-items: stretch;
  max-height: none;
}
.thema-kompakt {
  flex: 1 1 auto; min-width: 0;
  display: grid; grid-template-columns: auto minmax(0, 1fr); column-gap: 8px; row-gap: 3px;
  align-items: baseline; text-align: left;
  padding: 11px 4px 11px 14px;
  background: none; border: 0; border-radius: inherit;
  color: inherit; font: inherit; cursor: pointer;
}
.thema-kompakt .widget-rangpunkt { grid-row: 1 / span 2; align-self: center; }
.thema-kompakt-titel {
  font-size: 14px; font-weight: 600; line-height: 1.3; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.thema-kompakt-zeile {
  font-size: 11px; color: var(--text-leise);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.thema-kompakt:hover .thema-kompakt-titel { color: var(--akzent); }
.thema-kompakt:focus-visible { outline: 2px solid var(--akzent); outline-offset: -2px; }
/* Der Klick ist angekommen, die Karte ist nur noch nicht offen: der Dienst
   zeichnet sie gleich neu. Ein Hauch statt eines Kreisels — das dauert
   Millisekunden, und ein Kreisel, der aufblitzt, ist Unruhe. */
.widget.kompakt.oeffnet { opacity: .7; }
.widget.kompakt .widget-knoepfe { padding: 0 10px 0 0; align-self: center; }
/* Besprochenes tritt zurueck, es verschwindet nicht. */
.widget.kompakt[data-zustand="besprochen"] .thema-kompakt-titel { color: var(--text-leise); font-weight: 500; }

/* DIE ZEITZEILE AN DER OFFENEN THEMENKARTE — Alter und Gespraechsstand als
   Text, nie als Balken. Ein Balken gehoert einer echten Ablaufzeit
   (`.widget-cooldown`), und die hat ein Thema nicht. */
.widget-zeit {
  flex: none; padding: 0 16px 0 30px; margin-top: -2px;
  font-size: 11px; color: var(--text-leise);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* – UND ×: zuklappen und weglegen. Das Minus ist leiser als das Kreuz, weil
   es nichts wegnimmt. Beide sind echte Knoepfe mit Namen fuer die Stimme. */
.widget-zuklappen {
  background: none; border: 0; color: var(--text-leise); cursor: pointer;
  font-size: 18px; line-height: 1; padding: 0 3px;
}
.widget-zuklappen:hover, .widget-zu:hover { color: var(--text); }
.widget-zuklappen:focus-visible, .widget-zu:focus-visible,
.widget-vorlesen:focus-visible, .widget-mikro:focus-visible {
  outline: 2px solid var(--akzent); outline-offset: 2px; border-radius: 4px;
}

/* RUECKGAENGIG — ein Satz und ein Knopf, oben mittig unter der Kopfzeile.
   Unten laege er auf dem Mikrofon und dem Antwortband, und beides ist in dem
   Moment, in dem man eine Karte weglegt, oft in Bewegung. Sechs Sekunden,
   dann ist das × endgueltig. */
.weggelegt {
  position: fixed; left: 50%; top: 64px; transform: translateX(-50%);
  z-index: 30; display: flex; align-items: center; gap: 12px;
  max-width: min(460px, calc(100vw - 32px));
  padding: 9px 10px 9px 16px; border-radius: 999px;
  background: var(--flaeche); border: 1px solid var(--rand);
  box-shadow: 0 6px 20px var(--schatten);
  font-size: 13px; color: var(--text);
}
.weggelegt[hidden] { display: none; }
.weggelegt-text { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.weggelegt button {
  flex: none; font: inherit; font-weight: 600; color: var(--akzent);
  background: none; border: 0; border-radius: 999px; padding: 4px 10px; cursor: pointer;
}
.weggelegt button:hover { background: var(--akzent-alpha); }
.weggelegt button:focus-visible { outline: 2px solid var(--akzent); outline-offset: 1px; }

/* „Von vorn": selten gebraucht, aber folgenreich — deshalb sichtbar anders
   als die Schalter daneben, ohne ein roter Alarmknopf zu sein. Er wirft kein
   Werkstueck weg, nur ein Gespraech. */
.neustart { border-color: var(--rand); opacity: .75; }
.neustart:hover { opacity: 1; color: var(--akzent); border-color: var(--akzent); }

.zug-zeit {
  font-size: 11px;
  color: var(--text-leise);
  opacity: 0.6;
  margin-bottom: 2px;
}

/* Verhalten: die Regler, mit denen Herolds Timing gestellt wird.
   Eingeklappt, weil man sie einmal einstellt und dann monatelang nicht mehr
   — aber erreichbar, ohne den Quelltext zu lesen. */
/* ── Verhalten: die Regler mit Namen, Wert und einem Satz dazu ──────────── */
.ein-verhalten { border-top: 1px solid var(--rand); padding-top: 14px; margin-top: 2px; }
.ein-verhalten[hidden] { display: none; }
.ein-verhalten-titel {
  font-size: 11px; color: var(--text-leise);
  text-transform: uppercase; letter-spacing: .08em; margin-bottom: 12px;
}
.ein-verhalten-liste { display: flex; flex-direction: column; gap: 14px; }
.ein-schraube { display: flex; flex-direction: column; gap: 3px; }
.ein-schraube-kopf { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
.ein-schraube-name { font-size: 12.5px; color: var(--text); }
.ein-schraube-wert { font-size: 12px; color: var(--akzent); white-space: nowrap; font-variant-numeric: tabular-nums; }
.ein-schraube-hilfe { font-size: 11.5px; color: var(--text-leise); line-height: 1.4; }
.ein-schraube input[type="range"] { width: 100%; }

/* ── EIN MODUL ─────────────────────────────────────────────────────────────
   Eine Karte je Modul statt einer Zeile mit Trennstrich darunter: Name,
   Schalter, der Satz darunter und die aufklappbare Werkzeugliste gehoeren
   zusammen, und eine Linie sagt das schwaecher als eine Flaeche.

   Der Modul-Titel ueber der Liste entfaellt — ueber ihr steht schon der
   Reiter "Module", und eine Ueberschrift, die das Fach wiederholt, ist eine
   Zeile, die nichts sagt.

   Die Werkzeuge bleiben zugeklappt, weil es viele sind (WorkWith allein
   bringt sechsundzwanzig mit) und die Frage „welche gibt es" seltener ist als
   „ist es an". Der erste Satz steht in der Zeile, die ganze Beschreibung im
   `title`. */
.ein-module { display: flex; flex-direction: column; }
.ein-module-titel { display: none; }
.ein-modul-block {
  padding: 11px 13px; border: 1px solid var(--rand); border-radius: 11px;
  background: var(--text-alpha-schwach);
  transition: border-color var(--dauer-kurz) ease;
}
.ein-modul-block + .ein-modul-block { margin-top: 8px; }
.ein-modul-block:hover { border-color: var(--text-leise); }
/* Der Name traegt, der Schalter steht rechts am Rand — dieselbe Ordnung wie
   in jeder Zeile darueber: erst worum es geht, dann was man damit tut. */
.ein-modul {
  display: flex; align-items: center; gap: 10px;
  font-size: 13.5px; cursor: pointer;
}
.ein-modul-name { color: var(--text); font-weight: 500; }
.ein-modul-fuellung { flex: 1; }
.ein-modul-hilfe { color: var(--text-leise); font-size: 12px; line-height: 1.4; margin-top: 5px; }
.ein-werkzeuge { margin-top: 8px; }
.ein-werkzeuge > summary {
  display: inline-flex; align-items: center; gap: 5px;
  color: var(--text-leise); font-size: 11.5px; cursor: pointer; list-style: none;
  padding: 3px 9px; border-radius: 999px; border: 1px solid var(--rand);
  transition: color var(--dauer-kurz) ease, border-color var(--dauer-kurz) ease;
}
.ein-werkzeuge > summary::-webkit-details-marker { display: none; }
.ein-werkzeuge > summary:hover { color: var(--text); border-color: var(--text-leise); }
.ein-werkzeuge > summary::before {
  content: ""; width: 5px; height: 5px; flex: none;
  border-right: 1.4px solid currentColor; border-bottom: 1.4px solid currentColor;
  transform: rotate(-45deg); transition: transform var(--dauer-kurz) ease;
}
.ein-werkzeuge[open] > summary::before { transform: rotate(45deg); }
.ein-werkzeuge-liste { margin-top: 8px; display: flex; flex-direction: column; gap: 1px; }
.ein-werkzeug {
  display: flex; gap: 8px; align-items: baseline;
  font-size: 12px; padding: 3px 0; color: var(--text-leise);
}
.ein-werkzeug code {
  color: var(--akzent); font-family: var(--mono); font-size: 11px;
  white-space: nowrap; flex: none;
}
.ein-werkzeug span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* DIE ZUGAENGE EINES MODULS — eintragen ja, zurueckbekommen nein.
   Gezeigt wird der Wirt und ein vierstelliger Fingerabdruck; die Adresse
   selbst verlaesst den Dienst nicht. Deshalb steht hier auch keine Zeile, die
   sich "bearbeiten" liesse: ersetzen heisst entfernen und neu eintragen. */
.ein-feeds { margin-top: 9px; display: flex; flex-direction: column; gap: 6px; }
.ein-feed {
  display: flex; align-items: center; gap: 8px;
  font-size: 12px; color: var(--text-leise);
  padding: 5px 9px; border: 1px solid var(--rand); border-radius: 9px;
}
.ein-feed-eintrag + .ein-feed-eintrag { margin-top: 6px; }
.ein-feed-name { color: var(--text); font-weight: 500; flex: none; }
/* „Link" oder „Konto": die eine Angabe, an der man sieht, WAS da haengt —
   ein offener Feed oder eine Anmeldung mit App-Passwort. */
.ein-feed-art {
  flex: none; font-size: 10px; letter-spacing: .04em; text-transform: uppercase;
  padding: 2px 6px; border-radius: 5px; border: 1px solid var(--rand); color: var(--text-leise);
}
.ein-feed-kalender {
  font-size: 11.5px; color: var(--text-leise); line-height: 1.4;
  padding: 5px 9px 0 9px;
}
.ein-feed-kalender label { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.ein-feed-kalender select {
  background: var(--text-alpha-schwach); border: 1px solid var(--rand); border-radius: 7px;
  color: var(--text); font: inherit; font-size: 11.5px; padding: 3px 6px; max-width: 100%;
}
.ein-feed-kalender select:focus-visible { outline: none; border-color: var(--akzent); }
.ein-feed-fussnote { margin-top: 4px; opacity: .78; line-height: 1.45; }
.ein-feed-wirt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ein-feed-marke { font-family: var(--mono); font-size: 10.5px; opacity: .75; flex: none; }
.ein-feed-fuellung { flex: 1; }
.ein-feed-weg {
  flex: none; border: 0; background: none; cursor: pointer; padding: 2px 6px;
  color: var(--text-leise); font: inherit; font-size: 15px; line-height: 1;
  border-radius: 6px; transition: color var(--dauer-kurz) ease, background var(--dauer-kurz) ease;
}
.ein-feed-weg:hover { color: var(--fehler); background: var(--text-alpha-schwach); }
.ein-feed-weg:focus-visible { outline: 2px solid var(--akzent); outline-offset: 1px; }
/* Aus der Umgebung: sichtbar, aber nicht wegklickbar — wer sie loswerden
   will, nimmt sie aus der .env, und ein Knopf, der das nicht kann, waere eine
   Luege. */
.ein-feed.aus-env { opacity: .72; }
.ein-feed.aus-env .ein-feed-marke::after { content: " · .env"; }
.ein-feed-neu { display: flex; gap: 6px; margin-top: 7px; flex-wrap: wrap; }
.ein-feed-neu input {
  background: var(--text-alpha-schwach); border: 1px solid var(--rand); border-radius: 9px;
  color: var(--text); font: inherit; font-size: 12px; padding: 6px 9px; min-width: 0;
}
.ein-feed-neu input:focus-visible { outline: none; border-color: var(--akzent); }
.ein-feed-neu select.feed-art {
  flex: none; background: var(--text-alpha-schwach); border: 1px solid var(--rand);
  border-radius: 9px; color: var(--text); font: inherit; font-size: 12px; padding: 6px 8px;
}
.ein-feed-neu select.feed-art:focus-visible { outline: none; border-color: var(--akzent); }
.ein-feed-neu .feed-name { flex: 0 1 108px; }
.ein-feed-neu .feed-url,
.ein-feed-neu .feed-benutzer,
.ein-feed-neu .feed-passwort { flex: 1 1 190px; }
.ein-feed-neu input[hidden] { display: none; }
/* Ein deklariertes Zugangs-Formular (`zugangEditor()`). Die Breite kommt aus
   der Deklaration des Moduls, nicht aus einem Klassennamen je Feld — sonst
   braeuchte jedes neue Modul hier eine eigene Zeile. */
.ein-feed-neu .feed-schmal { flex: 0 1 132px; }
.ein-feed-neu .feed-breit { flex: 1 1 190px; }
.ein-feed-neu .feed-zahl { flex: 0 1 78px; }
.ein-feed-neu .feed-schalter {
  flex: none; display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; color: var(--text-leise); cursor: pointer;
}
.ein-feed-neu .feed-schalter input { accent-color: var(--akzent); cursor: pointer; }
/* Der Zettel fuer eine Anmeldung am anderen Geraet. Der Code wird ABGETIPPT,
   oft vom Telefon — also gross, Festbreite und mit Abstand zwischen den
   Zeichen, damit 0 und O nicht dasselbe sind. */
.ein-zugang-wartet {
  margin-top: 8px; padding: 10px 12px; border-radius: 10px;
  border: 1px solid var(--akzent); background: var(--text-alpha-schwach);
}
.ein-zugang-schritt { font-size: 11.5px; line-height: 1.5; color: var(--text-leise); }
.ein-zugang-schritt.leise { opacity: .78; margin-top: 6px; }
.ein-zugang-schritt a { color: var(--akzent); }
.ein-zugang-code {
  font-family: var(--mono); font-size: 25px; letter-spacing: .16em;
  color: var(--text); margin: 7px 0 2px; user-select: all;
}
.ein-feed-hinweis.leise { opacity: .72; }
.ein-feed-neu button {
  flex: none; padding: 6px 13px; border-radius: 9px; cursor: pointer;
  border: 1px solid var(--rand); background: var(--text-alpha-schwach);
  color: var(--text); font: inherit; font-size: 12px;
  transition: border-color var(--dauer-kurz) ease, color var(--dauer-kurz) ease;
}
.ein-feed-neu button:hover:not(:disabled) { border-color: var(--akzent); color: var(--akzent); }
.ein-feed-neu button:disabled { opacity: .5; cursor: default; }
.ein-feed-hinweis { font-size: 11.5px; line-height: 1.45; margin-top: 6px; color: var(--text-leise); }
.ein-feed-hinweis.schlecht { color: var(--fehler); }
@media (max-width: 460px) {
  .ein-feed-neu .feed-name,
  .ein-feed-neu .feed-url,
  .ein-feed-neu .feed-benutzer,
  .ein-feed-neu .feed-passwort,
  .ein-feed-neu .feed-schmal,
  .ein-feed-neu .feed-breit,
  .ein-feed-neu .feed-zahl { flex: 1 1 100%; }
  .ein-feed-neu select.feed-art,
  .ein-feed-neu button { width: 100%; }
  /* Auf dem Telefon bricht die Zeile ohnehin — dann lieber den Namen ganz
     zeigen als ihn zugunsten des Wirts abzuschneiden. */
  .ein-feed { flex-wrap: wrap; }
  .ein-feed-wirt { flex: 1 1 100%; order: 3; }
}

/* WORAN HEROLD GERADE ARBEITET — UEBER dem Avatar, direkt ueber der Linie.
   Marco am 22.09.2026: "Die Toasts sollen ueber dem Avatar erscheinen, direkt
   ueber der Linie. Bei einem neuen Tool-Call soll der bisherige nach oben
   geschoben werden und der neue seinen Platz einnehmen."

   HIER STAND `top: 50%` MIT `translate(-50%, -50%)`, UND DAS WAR ZWEIMAL
   FALSCH. Erstens lag der Stapel damit MITTEN AUF der Tonlinie und verdeckte
   genau das, was er begleiten soll. Zweitens war er in seiner eigenen Mitte
   verankert: jeder neue Eintrag schob den bisherigen um die HALBE Zuwachshoehe
   nach oben und sich selbst um die andere Haelfte nach unten — nichts stand
   still, und der neue Toast nahm nie den Platz des alten ein. Es ist derselbe
   Sprung, der am 19.09.2026 schon einmal mit 43 px gemessen wurde (siehe den
   Kopf von `toastWeg` in `hud.js`).

   ER HAENGT AM BAND, NICHT AM AVATAR — UND ER DARF UEBER DIE KARTEN.
   Marco am 22.09.2026, nachdem er ihn kurz rechts gesehen hatte: „Jetzt sind
   die Tool Calls irgendwie auf der rechten Seite. Das ist auch bloed. Die
   Tool Calls sollen ueber dem Band sein. Als Toast gestapelt koennen sie,
   auch wenn sie nach oben wandern, gerne ueber die Widgets gehen."

   Der Grund fuer den Ausflug nach rechts war, dass er in der Flaeche lag, die
   die Karten bekommen sollten. Marco loest denselben Konflikt andersherum:
   die Toasts DUERFEN darueber liegen. Damit faellt das Argument weg, und er
   kommt zurueck.

   ZURUECK, ABER ANDERS VERANKERT. Vorher hing er an der Oberkante der
   Avatar-Linie, und die musste `hud.js` bei jedem Zeichnen an der Canvas-Box
   nachmessen (`--kernkante`, live 327 statt der gerechneten 340). Die
   Maschinerie bleibt weg: `.mitte` ist die Zone, in der gesprochen wird, und
   `bottom: 100%` heftet den Stapel an ihre Oberkante — eine bekannte Stelle,
   die niemand messen muss.

   UND DAMIT DECKT ER DEN AVATAR NICHT ZU. Er sitzt UEBER `.mitte`, und die
   Linie ist deren erstes Kind: was oberhalb liegt, liegt auch oberhalb der
   Linie. Das war die Falle der Fassung davor.

   DER ANKER SITZT AN DER UNTERKANTE, NICHT IN DER MITTE. `top: 50%` mit
   `translate(-50%, -50%)` war zweimal falsch: der Stapel lag mitten auf
   seinem Bezugspunkt, und weil er in seiner EIGENEN Mitte verankert war,
   schob jeder Neuzugang den bisherigen nur um die halbe Zuwachshoehe nach
   oben und sich selbst um die andere Haelfte nach unten (19.09.2026, 43 px
   gemessen). Mit `bottom` plus `justify-content: flex-end` entsteht der neue
   Eintrag unten an derselben Stelle, und alles Aeltere wandert nach oben. */
.mcp-lauf {
  position: absolute; left: 50%; bottom: calc(100% + 10px);
  transform: translateX(-50%);
  z-index: 5; pointer-events: none;
  display: flex; flex-direction: column; justify-content: flex-end;
  gap: 6px; align-items: center;
  max-width: min(60vh, 460px);
  /* Ein langer Zug soll nicht in die Kopfleiste laufen. Gedeckelt wird die
     FLAECHE, und weggeschnitten wird OBEN — was dort verschwindet, ist das
     Aelteste, und genau das ist die Richtung, in die der Stapel waechst.
     Eine schlichte Zahl statt der gemessenen von frueher: der Stapel beginnt
     jetzt an einer bekannten Kante, nicht an einer, die schrumpfen darf. */
  max-height: 44vh; overflow: hidden;
}
.mcp-lauf[hidden] { display: none; }

.mcp-kasten {
  background: var(--flaeche-92); border: 1px solid var(--rand); border-radius: 12px;
  padding: 6px 10px; min-width: 170px;
  animation: mcp-rein var(--dauer) ease backwards;
}
/* `backwards` UND NICHT `both`: `both` laesst den Endzustand stehen, also auch
   die `max-height` — ein Kasten koennte danach nie wieder wachsen. `backwards`
   setzt nur den Anfangszustand vor dem Start; danach gilt wieder der normale
   Stil. Und die Hoehe faehrt mit auf, sonst springt der Stapel um die volle
   Hoehe des Neuzugangs, statt sich zu schieben. */
@keyframes mcp-rein {
  from { opacity: 0; max-height: 0; padding-top: 0; padding-bottom: 0; transform: translateY(6px) scale(.97); }
  to { opacity: 1; max-height: 220px; padding-top: 6px; padding-bottom: 6px; }
}
.mcp-kopf {
  font-size: 10px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--akzent); margin-bottom: 3px;
}
/* DIE EINZELNE ZEILE IST DER TOAST. Sie faehrt ihre Hoehe auf, damit der
   Kasten unter ihr waechst und alles darueber sichtbar nach oben rutscht —
   ohne das ist es ein Sprung und kein Schieben. 2em reicht fuer die eine
   Zeile, die eine Werkzeugzeile hoechstens hat. */
.mcp-werkzeug {
  font-family: var(--mono); font-size: 11px; color: var(--text-leise);
  padding: 1px 0; opacity: .6; overflow: hidden;
  animation: mcp-werkzeug-rein var(--dauer) ease backwards;
}
@keyframes mcp-werkzeug-rein {
  from { opacity: 0; max-height: 0; padding-top: 0; padding-bottom: 0; transform: translateY(6px); }
  /* Auf 1, nicht auf .6: die neue Zeile ist immer die LAUFENDE, und die steht
     voll da. Verliert sie den Status spaeter an ihre Nachfolgerin, ist die
     Animation laengst vorbei und `.6` kommt aus dem normalen Stil. */
  to { opacity: 1; max-height: 2em; padding-top: 1px; padding-bottom: 1px; }
}
.mcp-werkzeug.laeuft { color: var(--text); opacity: 1; }
.mcp-werkzeug.laeuft::after {
  content: " ·"; animation: mcp-punkt 1s steps(4, end) infinite;
}
@keyframes mcp-punkt { 0% { content: " ·"; } 33% { content: " ··"; } 66% { content: " ···"; } }
@media (prefers-reduced-motion: reduce) {
  .mcp-kasten, .mcp-werkzeug { animation: none; }
  .mcp-werkzeug.laeuft::after { animation: none; content: " ···"; }
}

/* ANTWORTET DER SERVER? — nicht zu verwechseln mit dem Schalter daneben.
   Ein abgeschaltetes Modul kann erreichbar sein, ein eingeschaltetes tot. */
.mcp-puls { width: 7px; height: 7px; border-radius: 50%; display: inline-block; margin-left: 6px; }
.mcp-puls.ok { background: var(--akzent); }
.mcp-puls.tot { background: var(--fehler); animation: mcp-puls-schlag 1.6s ease-in-out infinite; }
@keyframes mcp-puls-schlag { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
@media (prefers-reduced-motion: reduce) { .mcp-puls.tot { animation: none; } }

/* ══ BILDER IM FENSTER ════════════════════════════════════════════════════
   Beweis-Screenshots einer QS-Runde, Anhaenge an einer Aufgabe. Die einzige
   Widget-Art, die man nicht vorlesen kann — sie ERSETZT die Antwort nicht,
   aber sie ist der einzige Weg, an dem, was ein Agent gebaut hat, etwas zu
   sehen. */
.bilder-satz { font-size: 13px; line-height: 1.5; color: var(--text); margin-bottom: 10px; }
.bilder { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
/* Ein einzelnes Bild bekommt die ganze Breite: zwei Spalten bei einem Bild
   heisst halbes Bild und daneben Luft. */
.bilder.einzeln { grid-template-columns: 1fr; }
.bild {
  margin: 0; cursor: zoom-in; border-radius: 9px; overflow: hidden;
  border: 1px solid var(--rand); background: var(--grund);
  transition: border-color var(--dauer-kurz) ease, transform var(--dauer-kurz) ease;
}
.bild:hover { border-color: var(--akzent); transform: translateY(-1px); }
.bild img {
  display: block; width: 100%; aspect-ratio: 16 / 10; object-fit: cover;
  object-position: top center;
}
/* Oben beschnitten, nicht mittig: ein Screenshot traegt seine Aussage im
   Kopfbereich — Titelzeile, Meldung, Fehler. Mittig beschnitten zeigt die
   Bildmitte, und die ist bei einer Oberflaeche meist leer. */
.bild figcaption {
  padding: 5px 8px; font-size: 11px; color: var(--text-leise);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  border-top: 1px solid var(--rand);
}

/* ══ DIE LUPE ═════════════════════════════════════════════════════════════ */
.lupe {
  position: fixed; inset: 0; z-index: 200;
  display: grid; grid-template-columns: auto 1fr auto; align-items: center;
  gap: 8px; padding: 48px 16px 56px;
  background: rgba(0, 0, 0, .82); backdrop-filter: blur(6px);
}
.lupe[hidden] { display: none; }
/* DIE LUPE BLENDET AUF — dieselbe Machart wie `.einstellungen`: das Schwarz
   kommt weich, das Bild waechst die letzten Prozent heran. */
.lupe { transition: opacity var(--dauer) ease, display var(--dauer) allow-discrete; }
.lupe[hidden] { opacity: 0; pointer-events: none; transition-duration: var(--dauer-kurz); }
.lupe .lupe-bild { transition: transform var(--dauer-lang) var(--kurve-raus); }
@starting-style {
  .lupe:not([hidden]) { opacity: 0; }
  .lupe:not([hidden]) .lupe-bild { transform: scale(.96); }
}
@media (prefers-reduced-motion: reduce) { .lupe, .lupe .lupe-bild { transition: none; } }
.lupe-bild {
  grid-column: 2; justify-self: center;
  max-width: 100%; max-height: 100%; object-fit: contain;
  border-radius: 10px; box-shadow: 0 24px 60px rgba(0, 0, 0, .6);
  /* Der Klick auf das Bild schliesst NICHT — deshalb muss es die Ereignisse
     auch bekommen. */
  cursor: default;
}
.lupe-zu, .lupe-blaettern {
  border: 1px solid rgba(255, 255, 255, .18); background: rgba(0, 0, 0, .4);
  color: #fff; cursor: pointer; border-radius: 50%;
  display: grid; place-items: center; line-height: 1;
  transition: background var(--dauer-kurz) ease, border-color var(--dauer-kurz) ease;
}
.lupe-zu:hover, .lupe-blaettern:hover { background: rgba(255, 255, 255, .16); border-color: rgba(255, 255, 255, .4); }
.lupe-zu:focus-visible, .lupe-blaettern:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.lupe-zu {
  position: absolute; top: 12px; right: 14px; z-index: 1;
  width: 36px; height: 36px; font-size: 20px; padding-bottom: 2px;
}
.lupe-blaettern { width: 40px; height: 40px; font-size: 24px; padding-bottom: 3px; }
.lupe-blaettern.links { grid-column: 1; }
.lupe-blaettern.rechts { grid-column: 3; }
.lupe-blaettern[hidden] { visibility: hidden; display: grid; }
.lupe-fuss {
  position: absolute; left: 0; right: 0; bottom: 16px;
  display: flex; justify-content: center; align-items: baseline; gap: 12px;
  padding: 0 16px; color: rgba(255, 255, 255, .8); font-size: 12px;
}
.lupe-titel[hidden], .lupe-zaehler[hidden] { display: none; }
.lupe-zaehler { color: rgba(255, 255, 255, .5); font-variant-numeric: tabular-nums; }
@media (max-width: 820px) {
  .lupe { padding: 44px 8px 52px; }
  .lupe-blaettern { width: 34px; height: 34px; font-size: 20px; }
}

/* ── Einrichtung des lokalen Wake Words ────────────────────────────────
   Der Kasten steht unter der Zuruf-Wahl und nur dann, wenn sie auf „Nur auf
   »Herold«" steht — drei Knoepfe fuer einen Modus, den man nicht gewaehlt
   hat, waeren drei Knoepfe zu viel. Eingerahmt, weil er eine EINRICHTUNG ist
   und keine weitere Einstellzeile: hier tut man einmal etwas und danach
   nicht wieder. */
.ein-wach {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 2px 0 6px;
  padding: 10px 12px;
  border: 1px solid var(--rand);
  border-radius: 10px;
}
.ein-wach[hidden] { display: none; }
/* Die Namenszeile steht im Kasten und nicht daneben: sie gehoert zur
   Einrichtung, nicht zu den Reglern darueber. Ohne die Nullung waere sie
   eine Einstellzeile wie jede andere und der Kasten liefe aus dem Raster. */
.ein-wach-name { margin: 0; }
.ein-wach-name input {
  flex: 1; min-width: 0;
  background: var(--grund); color: var(--text);
  border: 1px solid var(--rand); border-radius: 7px;
  padding: 6px 9px; font: inherit; font-size: 13px;
}
.ein-wach-name input:focus-visible { outline: 2px solid var(--akzent); outline-offset: 1px; }
.ein-wach-stand { font-size: 13px; color: var(--text); }
.ein-wach .ein-modul-hilfe { margin-left: 0; }
.ein-wach-knoepfe { display: flex; flex-wrap: wrap; gap: 8px; }
/* Der Messmodus ist ein ZUSTAND, kein Auftrag: solange er laeuft, muss man
   ihm ansehen, dass jede Entscheidung mitgeschrieben wird. */
.ein-wach-knoepfe button[aria-pressed="true"] {
  opacity: 1; color: var(--akzent); border-color: var(--akzent);
}

/* Die Mitschrift-Zeile im Wach-Block: Name links, Erklaerung in der Mitte,
   Schalter rechts — dieselbe Ordnung wie bei einem Modul. Die Erklaerung
   gehoert hierher und nicht in einen `title`: sie sagt, dass fuer die Dauer
   eines Satzes ein zweiter Dienst mithoert, und das liest man nicht, wenn man
   mit der Maus daraufzeigen muss. */
.ein-wach-schalter { align-items: flex-start; }
.ein-wach-schalter .ein-name { padding-top: 1px; }
.ein-wach-was {
  font-size: 11.5px; line-height: 1.4; color: var(--text-leise);
}

/* Eine Trennlinie INNERHALB eines Fachs: „ab hier geht es um etwas anderes".
   Auf der Sprache-Seite steht darueber, wie Herold klingt, darunter, wie er
   zuhoert. Eine eigene Ueberschrift waere zu viel — es sind zwei Haelften
   derselben Frage, nicht zwei Themen. */
.ein-trenner { height: 1px; background: var(--rand); margin: 4px 0 2px; }

/* Der Personality-Editor: das Textfeld IST die Seite, also darf es wachsen —
   aber nicht so weit, dass der Rest darunter aus dem Fenster faellt. */
#ein-eigen { min-height: 150px; max-height: 40vh; }
.ein-ton-knopf { align-self: flex-start; }

/* ── Modul-Felder und Routinen ─────────────────────────────────────────────
   Beides nutzt die Bauformen, die es schon gibt (`.ein-modul-block`,
   `.ein-zeile`, `.schalter-feld` + `.schalter`, `.ein-knopf`). Was hier steht,
   ist nur die Gliederung dazwischen: eine Trennlinie ueber den Feldern, eine
   Ueberschrift je Modul-Gruppe, und die Leiste, die die Vortrags-Reihenfolge
   zeigt. Keine neuen Farben, keine neuen Radien — alles aus den Tokens. */
.ein-modul-felder {
  margin-top: 9px; padding-top: 9px; border-top: 1px solid var(--rand);
  display: flex; flex-direction: column; gap: 8px;
}
.ein-modul-feld .ein-modul-fuellung { font-size: 11.5px; color: var(--text-leise); line-height: 1.35; }
.ein-modul-feld input[type="number"] { flex: none; width: 76px; text-align: right; }

.ein-baustein-gruppe { margin-top: 9px; }
.ein-baustein-modul {
  font-size: 10.5px; letter-spacing: .05em; text-transform: uppercase;
  color: var(--text-leise); margin-bottom: 5px;
}
.ein-baustein { cursor: pointer; }
.ein-baustein .ein-name { width: auto; min-width: 84px; color: var(--text); }
.ein-baustein .ein-modul-fuellung { font-size: 11.5px; color: var(--text-leise); line-height: 1.35; }

/* DIE REIHENFOLGE IST DER VORTRAG. Sichtbar, weil eine Einstellung, die man
   nicht sieht, keine ist — und mit zwei Knoepfen zu aendern, weil das der
   kuerzeste Weg ist, der auch mit der Tastatur funktioniert. */
.ein-folge {
  margin-top: 10px; padding: 8px 10px;
  border: 1px solid var(--rand); border-radius: 9px;
}
.ein-folge-zeile { display: flex; align-items: center; gap: 8px; font-size: 12px; padding: 2px 0; }
.ein-folge-nr { color: var(--text-leise); font-family: var(--mono); font-size: 11px; flex: none; width: 18px; }
.ein-folge-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ein-folge-zeile .ein-knopf { padding: 1px 8px; font-size: 12px; line-height: 1.4; }

.ein-routine-knoepfe { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 11px; }
#ein-routine-neu { margin-top: 10px; align-self: flex-start; }
.ein-knopf[disabled] { opacity: .4; cursor: default; }

/* Auf dem Telefon steht der Name ueber dem Feld: 84 px Beschriftung plus ein
   Eingabefeld passen bei 360 px nicht nebeneinander, ohne dass eines davon
   unlesbar wird. */
@media (max-width: 460px) {
  .ein-baustein, .ein-modul-feld { flex-wrap: wrap; }
  .ein-baustein .ein-modul-fuellung, .ein-modul-feld .ein-modul-fuellung { flex-basis: 100%; order: 3; }
}

/* ── Gespeicherte Daten eines Moduls (Modal, 24.09.2026) ──────────────────
   Ein natives <dialog>: oberste Ebene, Esc und Fokusfalle vom Browser. Die
   Masse folgen den Einstellungen (Flaeche, Rand, 11px-Radius der Bloecke). */
.ein-daten-knopf { margin-top: 8px; height: 28px; font-size: 12px; }
.modul-daten {
  width: min(760px, calc(100vw - 32px)); max-height: min(80vh, 900px);
  padding: 0; border: 1px solid var(--rand); border-radius: 14px;
  background: var(--flaeche); color: var(--text); font-family: var(--schrift);
  display: none; flex-direction: column; overflow: hidden;
}
.modul-daten[open] { display: flex; }
.modul-daten::backdrop { background: rgba(0, 0, 0, .55); }
.modul-daten-kopf {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 14px 16px; border-bottom: 1px solid var(--rand);
}
.modul-daten-kopf h2 { margin: 0; font-size: 15px; font-weight: 600; }
.modul-daten-inhalt { overflow: auto; padding: 12px 16px 16px; min-height: 80px; }
.modul-daten-inhalt:focus-visible { outline: 2px solid var(--akzent); outline-offset: -2px; }
.modul-daten-teil + .modul-daten-teil { margin-top: 14px; }
.modul-daten-teil h3 {
  display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 4px 12px;
  margin: 0 0 6px; font-size: 13px; font-weight: 600;
}
.modul-daten-ort { font-family: var(--mono); font-size: 11px; font-weight: 400; color: var(--text-leise); }
.modul-daten-json {
  margin: 0; padding: 10px 12px; border: 1px solid var(--rand); border-radius: 11px;
  background: var(--text-alpha-schwach); font-family: var(--mono); font-size: 11.5px; line-height: 1.45;
  white-space: pre-wrap; overflow-wrap: anywhere; max-height: 360px; overflow: auto;
}
.modul-daten-leer { font-size: 12.5px; color: var(--text-leise); padding: 6px 0; }
