/* ==========================================================================
   Komponenten und Layout.

   Alle Werte kommen aus tokens.css - in dieser Datei steht KEIN Farbwert und
   keine Pixelzahl, die nicht ueber eine Variable laeuft. Wer hier einen
   Literalwert einfuegt, hat das System umgangen: die Stelle laesst sich dann
   beim Rebranding oder beim Nachruesten eines Dark Mode nicht mitziehen.

   Inline-Styles gibt es nicht, und zwar nicht aus Stilgruenden: die CSP
   erlaubt kein 'unsafe-inline' fuer style-src (siehe app/core/middleware.py).
   Ein style="..."-Attribut wuerde vom Browser verworfen.

   AUFBAU
     1. Grundlagen (Reset, Elemente)
     2. Layout (oeffentliche Seiten, App-Shell, Sidebar)
     3. Komponenten
     4. Noch nicht migrierte Seiten
   ========================================================================== */

/* ==========================================================================
   1. GRUNDLAGEN
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  padding: 0;
  background: var(--surface-content);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: var(--text-base-lh);
  /* Auf macOS wirkt die Systemschrift ohne das hier eine Spur zu fett. */
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 {
  margin: 0;
  color: var(--text-strong);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.011em;   /* grosse Schrift wirkt sonst locker gesetzt */
}
h1 { font-size: var(--text-3xl); line-height: var(--text-3xl-lh); }
h2 { font-size: var(--text-xl);  line-height: var(--text-xl-lh); }
h3 { font-size: var(--text-lg);  line-height: var(--text-lg-lh); }

p { margin: 0 0 var(--space-4); }
p:last-child { margin-bottom: 0; }

a {
  color: var(--action-text);
  text-decoration: none;
}
a:hover { color: var(--action-text-hover); text-decoration: underline; }

/* EIN Fokusstil fuer alles Fokussierbare. `:focus-visible` statt `:focus`:
   der Ring erscheint bei Tastaturbedienung, aber nicht bei jedem Mausklick -
   sonst schalten Entwickler ihn irgendwann global ab, und dann fehlt er auch
   der Tastatur. */
:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

hr {
  height: 1px;
  margin: var(--space-6) 0;
  border: 0;
  background: var(--border);
}

code, pre { font-family: var(--font-mono); font-size: var(--text-sm); }

/* ==========================================================================
   2. LAYOUT
   ========================================================================== */

/* --- Oeffentliche Seiten (Anmeldung, Registrierung, Rechtstexte) ---------
   Schmale, zentrierte Spalte. Diese Seiten haben keine Sidebar - dort ist
   noch niemand angemeldet, es gibt also nichts zu navigieren. */
.container {
  max-width: 32rem;
  margin: 0 auto;
  padding: var(--space-12) var(--space-4);
}
/* Breitenstufen statt willkuerlicher Werte je Seite. Die Zeilenlaenge ist
   der Grund: eine Anmeldemaske mit zwei Feldern in 60rem sieht verloren aus,
   ein Rechtstext in 26rem liest sich als Endlosspalte. */
.container--narrow { max-width: 34rem; }   /* Anmelden, Reset, Bestaetigung */
.container--form   { max-width: 36rem; }   /* Registrierung (sechs Felder)  */
.container--wide   { max-width: 60rem; }   /* Rechtstexte                   */

/* Kopf ueber der Karte auf den Seiten vor der Anmeldung: Wortmarke, damit
   erkennbar ist, wo man sich anmeldet. */
.auth-head {
  display: flex;
  justify-content: center;
  margin-bottom: var(--space-6);
}
/* Zeile unter der Karte ("Schon ein Konto? Zur Anmeldung"). */
.auth-alt {
  margin: var(--space-6) 0 0;
  text-align: center;
  color: var(--text-muted);
  font-size: var(--text-sm);
}

/* --- App-Shell (alles hinter der Anmeldung) ------------------------------
   Grid statt Flexbox: die Sidebar hat eine feste Spaltenbreite, der Inhalt
   nimmt den Rest. `min-width: 0` auf der Inhaltsspalte ist der Teil, den man
   vergisst - ohne ihn sprengt eine breite Tabelle das Grid, statt in ihrem
   eigenen Kasten zu scrollen. */
.app-shell {
  display: grid;
  grid-template-columns: var(--sidebar-width) 1fr;
  /* ZWEI ZEILEN RECHTS: oben die Kopfzeile mit dem Kontomenue, darunter der
     Inhalt. Die Seitenleiste reicht ueber beide (siehe .sidebar). */
  grid-template-rows: auto 1fr;
  min-height: 100vh;
}

.app-main {
  grid-column: 2;
  grid-row: 2;
  display: flex;
  flex-direction: column;
  min-width: 0;
}

/* VOLLE BREITE IST DER STANDARD, nicht die Ausnahme.

   Bis hierher war es umgekehrt: Die Spalte war auf --content-max gedeckelt,
   und einzelne Seiten durften ausbrechen. Das war die falsche Vorgabe. Eine
   Verwaltungsoberflaeche besteht aus Listen, Rastern und Tabellen; fuer die
   ist jeder Pixel Gewinn, und der Deckel hinterliess rechts einen leeren
   Streifen. Ausbrechen mussten am Ende fast alle Seiten - eine Regel, von
   der die Mehrheit eine Ausnahme braucht, ist keine Regel.

   DER DECKEL IST NICHT WEG, SONDERN GEWANDERT: auf die Bausteine, die ihn
   brauchen. Eingabefelder bekommen --field-max, erklaerende Absaetze
   --prose-max. Damit bleibt lesbar, was gelesen wird, und breit, was
   verglichen wird.

   Der Rand kommt aus tokens.css und steht bewusst nicht als Zahl hier: er
   gilt fuer jede Seite hinter der Anmeldung. */
.app-content {
  flex: 1;
  width: 100%;
  padding: var(--content-padding);
}

/* --- Sidebar ------------------------------------------------------------- */
.sidebar {
  grid-column: 1;
  grid-row: 1 / span 2;
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  /* RUNDUM DASSELBE MASS. Vorher 24px oben/unten und 12px seitlich - die
     Eintraege sassen damit fast am Fensterrand. Im Referenzbild haben sie
     nach allen Seiten sichtbar Abstand, und das ist der halbe Unterschied
     zwischen "Liste am Rand" und "Navigation in einem Bereich". */
  /* OBEN MEHR ALS RINGSUM. Bei gleichmaessigen 16px sass die Wortmarke am
     Fensterrand und wirkte eingequetscht - der obere Rand ist die einzige
     Kante ohne Nachbarn, und dort faellt Enge zuerst auf. */
  padding: var(--space-8) var(--space-4) var(--space-4);
  background: var(--surface-sidebar);
  /* Kante UND ein sehr flacher Schatten. Die Linie allein genuegte, solange
     der Inhalt grau hinterlegt war; auf reinweissem Grund braucht die
     Abgrenzung den zweiten Hinweis, sonst franst die Leiste aus. Der Schatten
     ist bewusst kaum sichtbar - er soll die Kante stuetzen, nicht die Leiste
     ueber die Seite heben. */
  /* DER RAND IST ZURUECK, weil der Grund wieder hell ist: 1.25:1 gegen die
     weisse Inhaltsflaeche trennt fuer sich genommen zu schwach. Nicht
     --border (neutral-200) - der ist inzwischen fast so hell wie die Leiste
     selbst und zoege keine Linie. */
  border-right: 1px solid var(--border-strong);
  /* Bleibt beim Scrollen langer Tabellen stehen. */
  position: sticky;
  top: 0;
  height: 100vh;
}

.sidebar__brand { padding: 0 var(--space-2); }

.sidebar__nav {
  display: flex;
  flex-direction: column;
  /* 4px. Mit 38px hohen Eintraegen braucht es weniger Fuge als mit 50px -
     der Abstand steht immer im Verhaeltnis zu dem, was er trennt. */
  gap: var(--space-1);
}
.sidebar__label {
  padding: 0 var(--space-3);
  /* 20px statt 24. Die Gruppen bleiben als Gruppen lesbar - bei 6px zwischen
     den Eintraegen ist das immer noch der dreifache Abstand. */
  margin: var(--space-5) 0 var(--space-2);
}
.sidebar__label:first-child { margin-top: 0; }
.sidebar__label {
  /* Auf dem gedeckten Grund faellt --text-muted auf 2.96:1. */
  color: var(--text-muted-on-sidebar);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* Navigationseintrag. Der aktive Zustand ist an DREI Merkmalen erkennbar:
   Hintergrund, Textfarbe und der Balken links. Der Balken ist kein Zierrat -
   Farbe allein waere fuer farbfehlsichtige Nutzer kein Unterschied
   (WCAG 1.4.1). Dieselbe Entscheidung stand vorher an der Tab-Leiste, dort
   als Unterstreichung. */
/* KOMPAKT UND FETT statt gross und fett. Hier stand die Begruendung, warum
   die Eintraege GROESSER als der Fliesstext sein sollten: Die Leiste wird
   angesteuert, nicht gelesen, und dafuer zaehle Erkennbarkeit auf einen
   Blick.

   Der zweite Teil davon stimmt weiterhin und traegt die Auszeichnung: volle
   Textfarbe statt gedaempftem Grau, halbfett statt normal. Die GROESSE
   dagegen ist zurueckgenommen - eine Navigationsleiste mit elf Eintraegen ist
   eine Liste, die man ueberfliegt, und 17px auf 26px Zeilenhoehe machten
   daraus eine Aufzaehlung von Ueberschriften. */
.navitem {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-2-5);
  padding: var(--space-2) var(--space-3);
  /* KEIN RAHMEN MEHR AN IRGENDEINEM EINTRAG. Er stand hier, solange der
     aktive Zustand eine Kontur trug; jetzt tragen ihn Flaeche und Schatten,
     und die veraendern die Groesse des Kastens nicht. Die nicht
     ausgewaehlten Eintraege liegen damit voellig flach auf der Leiste - kein
     Rahmen, keine Flaeche, kein Schatten. */
  border-radius: var(--radius-xl);
  color: var(--text);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.006em;
}
.navitem:hover {
  /* HELLER ALS DER GRUND, ABER NICHT WEISS. Weiss waere jetzt die Flaeche des
     ausgewaehlten Eintrags - jeder ueberfahrene Eintrag saehe dann aus, als
     sei er der aktive. */
  background: var(--surface-sidebar-hover);
  color: var(--text-strong);
  text-decoration: none;
}
.navitem svg {
  flex: 0 0 auto;
  /* Zieht mit der Schrift mit: 18px neben 15px Text saehen aus, als gehoerten
     sie nicht zusammen. */
  width: 16px;
  height: 16px;
  /* --icon-muted erreicht auf dem neuen Leistengrund nur 2.96:1 und verfehlt
     damit die 3:1 aus WCAG 1.4.11. Siehe --text-muted-on-sidebar. */
  color: var(--text-muted-on-sidebar);
}
.navitem:hover svg { color: var(--text); }
/* Der Zaehler ungelesener Hinweise an "Mein Kalender" (Kapitel 37). */
.navitem__zahl {
  margin-left: auto;
  min-width: 1.5em;
  padding: 0 var(--space-1);
  border-radius: var(--radius-full);
  background: var(--action);
  color: var(--text-on-action);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
  text-align: center;
}

/* DER AKTIVE EINTRAG IST EINE PILLE MIT KONTUR - so wie im Referenzbild.

   VORHER TRUG ER EINEN BALKEN AM LINKEN RAND. Der war kein Zierrat: Er sollte
   sicherstellen, dass die Auszeichnung nicht allein an der Farbe haengt
   (WCAG 1.4.1). Diese Aufgabe uebernimmt jetzt die Kontur, und sie tut es
   besser - ein Rahmen umschliesst den ganzen Eintrag, waehrend der Balken nur
   an einer Kante stand. Wer keine Farben unterscheidet, sieht einen Kasten,
   den die anderen Eintraege nicht haben.

   Ein Balken zusaetzlich zur Pille waere ausserdem schief geworden: Bei 14px
   Radius liefe er in die Rundung der Ecken hinein.

   DIE KONTUR IN --action UND NICHT LEISER: Gegen die Leiste erreicht sie
   4.56:1. Die naechstleisere Stufe (--blue-400) laege bei 3.08 und damit
   genau auf der Schwelle aus WCAG 1.4.11 - ohne jede Reserve fuer einen
   Bildschirm im Sonnenlicht. */
.navitem[aria-current="page"] {
  /* WEISS AUF GEDECKTEM GRUND, nicht getoent auf hell. Das ist die Umkehrung
     der beiden vorherigen Versuche und der Grund, warum man es jetzt sieht:
     Eine blaue Toenung auf einer fast weissen Leiste ergab 1.10:1 - ein
     Unterschied, den man sucht statt ihn zu bemerken. Weiss auf
     --surface-sidebar sind 1.61:1, und dazu kommt der Schatten. */
  background: var(--surface);
  /* KONTUR PLUS SCHATTEN, und die Kontur ueber `box-shadow` statt `border`:
     Ein Rahmen wuerde den Kasten um zwei Pixel wachsen lassen, und dann
     muessten alle anderen Eintraege einen durchsichtigen mitfuehren, nur damit
     die Liste beim Wechseln nicht ruckelt.

     NOETIG GEWORDEN DURCH DIE AUFHELLUNG: Auf dem frueheren, dunkleren Grund
     trug die weisse Flaeche mit 1.61:1 allein. Jetzt sind es 1.25:1 - zu
     wenig, und die Kontur macht den Unterschied wieder eindeutig. */
  /* EINE DUENNE, HELLGRAUE LINIE - ausdruecklich zurueckgenommen. Sie stand
     kurzzeitig auf --text-placeholder (neutral-500) und erreichte damit
     4.25:1; das trug WCAG 1.4.11 allein, wirkte aber wie ein gezeichneter
     Rahmen statt wie eine Kante. Mit --border-hover (neutral-400) sind es
     2.29:1: sichtbar, aber leise.

     DIE NAECHSTHELLERE STUFE WAERE ZU WENIG. --border-strong (neutral-300)
     kaeme auf 1.32:1 und verschwaende auf dem hellen Grund - dieselbe Falle
     wie in den ersten Anlaeufen.

     Die Kontur traegt damit wieder nur STUETZEND neben Flaeche und Schatten.
     Genau deshalb prueft der Test die Bauweise und nicht eine Zahl. */
  box-shadow: 0 0 0 1px var(--border-pill), var(--shadow-pill);
  /* --action-on-subtle traegt auf Weiss 7.38:1. Die Markenfarbe bleibt als
     zweites Merkmal neben Flaeche und Schatten. */
  color: var(--action-on-subtle);
  font-weight: var(--weight-semibold);
}
.navitem[aria-current="page"] svg { color: var(--action-on-subtle); }

/* --- Bereiche mit Unterpunkten ------------------------------------------

   HIER STAND FRUEHER EINE REITERLEISTE IM SEITENINHALT (`.tabs`, weiter
   unten - die Klasse bleibt, weil sie an anderer Stelle noch gebraucht
   wird). Die Ansichten eines Bereichs stehen jetzt in der Seitenleiste;
   die Begruendung steht in admin/_layout.html.

   `<details>` OHNE JEDE ZEILE JAVASCRIPT, dasselbe Mittel wie bei
   `.settings-group` und `.dayrow`. Auch das eigene Dreieck ist von dort
   uebernommen: Der Standard-Marker sitzt je nach Browser an einer anderen
   Stelle und laesst sich nicht ausrichten. */
.navgroup { display: block; }

/* Der Elternpunkt traegt zusaetzlich `.navitem` und sieht damit aus wie
   jeder andere Eintrag - Symbol und Beschriftung stehen auf derselben
   Kante wie bei den Bereichen ohne Unterpunkte. Genau das ist der Zweck:
   Ob ein Bereich aufklappt, soll man am Dreieck erkennen und nicht daran,
   dass seine Zeile verrutscht ist. */
.navgroup__summary {
  cursor: pointer;
  list-style: none;
}
/* Safari kennt `list-style` an dieser Stelle nicht. */
.navgroup__summary::-webkit-details-marker { display: none; }

/* DAS DREIECK STEHT RECHTS, nicht links wie bei `.settings-group`. Links
   sitzt bereits das Bereichssymbol; ein zweites Zeichen davor haette die
   Beschriftung der aufklappbaren Bereiche gegenueber den uebrigen
   eingerueckt - und dann laege die Leiste auf zwei Kanten.

   `margin-left: auto` statt absoluter Position: Der Elternpunkt ist ein
   Flex-Kasten (`.navitem`), da schiebt sich das Dreieck von selbst an den
   rechten Rand und bleibt bei jeder Beschriftungslaenge dort. */
.navgroup__summary::after {
  content: "";
  flex: 0 0 auto;
  margin-left: auto;
  width: 6px;
  height: 6px;
  border-right: 2px solid var(--text-muted-on-sidebar);
  border-bottom: 2px solid var(--text-muted-on-sidebar);
  transform: rotate(-45deg);
  transition: transform 120ms ease;
}
.navgroup[open] > .navgroup__summary::after { transform: rotate(45deg); }
.navgroup__summary:hover::after { border-color: var(--text); }

/* DIE GRUPPE DER AKTUELLEN SEITE. Sie traegt bewusst KEIN `aria-current` -
   sie ist die Seite nicht, das ist einer ihrer Unterpunkte.

   DAS EIGENTLICHE MERKMAL IST DER OFFENE ZUSTAND, nicht diese Regel: Das
   nach unten gedrehte Dreieck unterscheidet die Gruppe von den
   zugeklappten, und es tut das ohne Farbe (WCAG 1.4.1). Ein Blick auf das
   erste Bildschirmfoto nach dem Umbau hat gezeigt, dass eine Farbnuance am
   Elternpunkt allein nichts leistet: `--text-strong` gegen `--text` ist auf
   dem Leistengrund nicht zu sehen, und ein Kommentar, der "erkennbar"
   behauptet, waere damit eine Absichtserklaerung geblieben.

   Das getoente Symbol kommt deshalb dazu - dasselbe Mittel, mit dem der
   aktive Haupteintrag arbeitet (`.navitem[aria-current] svg`), eine Stufe
   leiser. Es ist ein ZUSAETZLICHES Merkmal neben dem Dreieck, nicht das
   tragende: Wer keine Farben unterscheidet, sieht weiterhin die Drehung. */
.navgroup--aktiv > .navgroup__summary { color: var(--text-strong); }
.navgroup--aktiv > .navgroup__summary svg { color: var(--action-on-sidebar); }

.navsub {
  list-style: none;
  margin: var(--space-1) 0 var(--space-2);
  /* AUSGERICHTET AUF DIE BESCHRIFTUNG DES ELTERNPUNKTS, nicht auf dessen
     linke Kante: Polsterung des Eintrags plus Symbolbreite plus die Fuge
     dazwischen. Ein runder Wert waere einfacher und laege daneben - die
     Einrueckung soll sichtbar von der Beschriftung abhaengen, damit die
     Unterpunkte unter ihr haengen und nicht unter dem Symbol. */
  padding-left: calc(var(--space-3) + 16px + var(--space-2-5));
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.navsub__item {
  display: block;
  padding: var(--space-1-5) var(--space-3);
  border-radius: var(--radius-lg);
  /* Zwei Stufen leiser als der Elternpunkt: kleinere Schrift, gedeckte
     Farbe. --text-muted-on-sidebar traegt auf dem Leistengrund 4.62:1. */
  color: var(--text-muted-on-sidebar);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
  font-weight: var(--weight-medium);
  /* Ein Unterpunkt bricht nicht um: "Termine & Teilnehmer" auf zwei Zeilen
     saehe wie zwei Eintraege aus. */
  white-space: nowrap;
}
.navsub__item:hover {
  background: var(--surface-sidebar-hover);
  color: var(--text-strong);
  text-decoration: none;
}

/* DER AKTIVE UNTERPUNKT - Farbe UND ein Balken.

   NICHT DIE PILLE DES HAUPTEINTRAGS: Flaeche, Kontur und Schatten sind das
   Merkmal der oberen Ebene; sie hier zu wiederholen hiesse, zwei Ebenen mit
   demselben Zeichen auszuweisen, und die Leiste haette zwei gleich laute
   aktive Eintraege.

   DER BALKEN IST KEIN ZIERRAT, sondern das farbunabhaengige Merkmal
   (WCAG 1.4.1) - wer Farben nicht unterscheidet, sieht die Kante. Beim
   Haupteintrag hatte ein Balken keinen Platz, weil er in die Rundung der
   Pille gelaufen waere; hier gibt es keine Pille, und damit ist er wieder
   das einfachste Mittel.

   --action-on-sidebar und nicht --action: gegen den Leistengrund traegt
   ersteres 4.60:1, letzteres nur 2.97:1 - zu wenig fuer Text UND zu wenig
   fuer ein Bedienelement. */
.navsub__item[aria-current="page"] {
  border-left: 2px solid var(--action-on-sidebar);
  /* Die Polsterung um die Balkenbreite verringert, damit die Beschriftung
     nicht um zwei Pixel gegenueber den uebrigen Unterpunkten springt. */
  padding-left: calc(var(--space-3) - 2px);
  border-radius: 0 var(--radius-lg) var(--radius-lg) 0;
  color: var(--action-on-sidebar);
  font-weight: var(--weight-semibold);
}

/* DER FOKUSRING auf dem gedeckten Grund. --action erreicht dort 2.97:1 - zu
   wenig fuer ein Bedienelement (3.0). --action-on-sidebar traegt 4.60:1. Ein
   Ring, den man nicht sieht, nimmt der Tastaturbedienung ihre einzige
   Rueckmeldung.

   HIER STAND BIS SEPTEMBER 2026 AUCH DER FUSS mit Konto, Hilfe, Name und
   Abmelden. Er ist ins Kontomenue oben rechts gezogen (weiter unten) - und
   mit ihm die Ursache von SECURITY.md Punkt 99: Die Fusszeile liess sich
   nicht umbrechen und zog jede Seite auf schmalen Handys auf 434px auf. */
.sidebar :focus-visible { outline-color: var(--action-on-sidebar); }

/* --- Kopfzeile mit dem Kontomenue ---------------------------------------
   Oben rechts ueber dem Inhalt, auf jeder Seite hinter der Anmeldung
   (admin/_kontomenue.html, in beiden Rahmen dieselbe Datei).

   EINE EIGENE ZEILE UND KEINE UEBERLAGERUNG: Rechts oben im Inhalt stehen
   auf vielen Seiten die Aktionen des Seitenkopfs ("Neu anlegen"). Ein
   darueber gelegter Knopf haette sie verdeckt. */
.app-topbar {
  grid-column: 2;
  grid-row: 1;
  display: flex;
  justify-content: flex-end;
  min-width: 0;
  padding: var(--space-4) var(--content-padding) 0;
}
.kontomenue { position: relative; }

/* Der Ausloeser - GEFUELLT IN DER MARKENFARBE (18.9.2026).

   HIER STAND EIN WEISSER KNOPF mit dunklem Text und einem Rand in
   --border-hover. Der Rand war noetig, weil der Knopf unter 900px auf der
   Seitenleiste liegt und eine weisse Flaeche sich dort - vor allem auf dem
   dunkleren Grund des Einstellungsrahmens - kaum absetzte.

   MIT GEFUELLTER FLAECHE ENTFAELLT DIESER GRUND: Blau traegt sich gegen
   jeden dieser Gruende selbst.

   DER RAND IST SEIT DEM 18.9.2026 GANZ WEG. Er stand zuletzt in der Farbe
   der Flaeche und war damit unsichtbar; die Begruendung war, er halte die
   Masse des Knopfs. Das war falsch und ist nachgemessen widerlegt - siehe
   den Kommentar an `border: 0` unten.

   ALLE VIER BESTANDTEILE HALTEN 4.5:1 (nachgerechnet aus dem CSS heraus in
   tests/test_design_tokens.py::test_der_kontomenue_knopf_ist_lesbar):
   Schrift und Dreieck in --text-on-action auf --action (4.77:1) bzw. auf
   --action-hover (6.41:1), und der Buchstabe im Profilbild umgekehrt -
   --action auf --surface, dieselben 4.77:1. */
.kontomenue__knopf {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--control-height);
  padding: 0 var(--space-3);
  /* KEIN RAHMEN. Er stand hier in der Flaechenfarbe und war damit unsichtbar;
     die Begruendung dafuer war, dass der Knopf ohne ihn um 2px schrumpfe.

     NACHGEMESSEN UND WIDERLEGT: `box-sizing: border-box` gilt global (ganz
     oben in dieser Datei), und die Hoehe kommt aus `min-height`, nicht aus
     Inhalt plus Rahmen - sie bleibt bei 44px. Die Breite folgt dem Inhalt;
     die 2px, die der Rahmen belegte, gehen an den Innenraum, nicht an die
     Aussenmasse. Gemessen vorher und nachher bei 1440px und 800px. */
  border: 0;
  border-radius: var(--radius-full);
  background: var(--action);
  color: var(--text-on-action);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
  font-weight: var(--weight-medium);
  cursor: pointer;
  list-style: none;
}
/* Safari kennt `list-style` an dieser Stelle nicht. */
.kontomenue__knopf::-webkit-details-marker { display: none; }
.kontomenue__knopf:hover {
  background: var(--action-hover);
  color: var(--text-on-action);
}
.kontomenue__knopf svg {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  color: var(--text-on-action);
}
/* Das Dreieck zeigt nach unten und dreht sich beim Oeffnen - dasselbe
   Zeichen wie bei den Bereichen der Seitenleiste, damit "klappt auf" an
   beiden Stellen gleich aussieht und nicht allein an einer Farbe haengt. */
.kontomenue__knopf::after {
  content: "";
  flex: 0 0 auto;
  width: 6px;
  height: 6px;
  margin: 0 var(--space-1) 3px var(--space-1);
  border-right: 2px solid var(--text-on-action);
  border-bottom: 2px solid var(--text-on-action);
  transform: rotate(45deg);
  transition: transform 120ms ease;
}
.kontomenue[open] > .kontomenue__knopf::after {
  margin-bottom: -3px;
  transform: rotate(-135deg);
}
/* Ein langer Anzeigename darf die Kopfzeile nicht aufziehen. Gekuerzt wird
   hier und nie im Template (Begruendung in admin/_kontomenue.html). */
.kontomenue__name {
  max-width: 14rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Das aufgeklappte Menue. Ueber der Tour-Karte (40), unter der
   Abmeldewarnung (50): Die Warnung vor dem Abmelden bleibt das Wichtigste
   auf dem Bildschirm. */
.kontomenue__panel {
  position: absolute;
  top: calc(100% + var(--space-2));
  right: 0;
  z-index: 45;
  width: max-content;
  min-width: 15rem;
  max-width: min(22rem, calc(100vw - 2 * var(--space-4)));
  padding: var(--space-2);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}
.kontomenue__konto {
  margin: 0 0 var(--space-1);
  padding: var(--space-2) var(--space-3) var(--space-3);
  border-bottom: 1px solid var(--border);
}
.kontomenue__label {
  display: block;
  color: var(--text-muted);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
}
.kontomenue__account {
  display: block;
  color: var(--text);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
  font-weight: var(--weight-semibold);
  overflow-wrap: anywhere;
}
.kontomenue__liste {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Ein Eintrag - Link oder, beim Abmelden, der Formularknopf.

   DER KNOPF SIEHT AUS WIE DIE LINKS DARUEBER und muss dafuer die Vorgaben
   des blanken `button` (gefuellt, zentriert, 44px) zuruecknehmen. Die
   Klasse ueberstimmt `button`, und `:hover`/`:active` hier ueberstimmen
   `button:hover`/`button:active`. */
.kontomenue__eintrag {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: var(--space-2-5);
  width: 100%;
  min-height: 0;
  padding: var(--space-2) var(--space-3);
  border: 0;
  border-radius: var(--radius);
  background: transparent;
  color: var(--text);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
  font-weight: var(--weight-medium);
  text-align: left;
  text-decoration: none;
}
.kontomenue__eintrag:hover,
.kontomenue__eintrag:active {
  background: var(--surface-hover);
  color: var(--text-strong);
  text-decoration: none;
}
.kontomenue__eintrag svg {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  color: var(--icon-muted);
}
/* Die aktive Seite: Farbe UND Schriftgewicht (WCAG 1.4.1). */
.kontomenue__eintrag[aria-current="page"] {
  color: var(--action-on-subtle);
  font-weight: var(--weight-semibold);
}
.kontomenue__eintrag[aria-current="page"] svg { color: var(--action-on-subtle); }
.kontomenue__neu { margin-left: auto; }
.kontomenue__abmelden {
  margin: var(--space-1) 0 0;
  padding-top: var(--space-1);
  border-top: 1px solid var(--border);
}

/* Die Seitenleiste im Einstellungsmodus. Derselbe Bau, ein anderer Ton:
   Sie soll erkennbar NICHT die Hauptnavigation sein, sonst merkt man den
   Moduswechsel nicht. Ein kraeftiger Farbwechsel waere zu viel - man ist
   immer noch in derselben Anwendung. */
/* DIE EINSTELLUNGSLEISTE TRAEGT DIESELBE FLAECHE WIE DIE HAUPTNAVIGATION.

   Hier stand `background: var(--surface-raised)` - die Leiste war eine Spur
   dunkler als die der Startseite. Der Gedanke dahinter war, den anderen Modus
   sichtbar zu machen. In der Anwendung wirkte es aber nicht wie ein Modus,
   sondern wie ein anderer Bauabschnitt: Wer zwischen Startseite und
   Einstellungen wechselt, sieht denselben Rahmen in zwei Grautoenen, und die
   Frage "bin ich hier woanders?" beantwortet schon der Ruecklink ganz oben.

   DIE KLASSE BLEIBT, obwohl sie nichts mehr faerbt: Sie sagt, in welchem
   Rahmen man steht, und genau darauf pruefen tests/test_admin_calendar.py und
   tests/test_termine_navigation.py. Sie ist damit ein Anker, kein Stil - und
   der naechste Unterschied zwischen den Rahmen haette hier seinen Platz. */
.sidebar--settings { /* erbt .sidebar - siehe oben */ }

/* Der Rueckweg. Abgesetzt und ueber der Liste, nicht als Menuepunkt unter
   anderen: Ein Modus, aus dem man nicht sichtbar herauskommt, ist eine
   Falle. */
.settingsnav__back {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius);
  color: var(--text-muted);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
  font-weight: var(--weight-medium);
}
.settingsnav__back:hover {
  background: var(--surface-content);
  color: var(--text-strong);
  text-decoration: none;
}
.settingsnav__back svg { width: 16px; height: 16px; flex: 0 0 auto; }

/* Ein Abschnitt INNERHALB einer Einstellungsseite. Kein Aufklapper mehr: Die
   Seitenleiste teilt die Einstellungen jetzt in Seiten auf - eine zweite
   Aufteilung innerhalb der Seite waere eine Ebene zu viel. */
.settings-section { margin-bottom: var(--section-gap); }
.settings-section:last-of-type { margin-bottom: 0; }
.settings-section__title {
  margin-bottom: var(--space-1);
  font-size: var(--text-lg);
  line-height: var(--text-lg-lh);
}
.settings-section__hint {
  margin: 0 0 var(--space-4);
  color: var(--text-muted);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
}

/* --- Sidebar auf kleinen Schirmen ---------------------------------------
   Unter 900px wird aus der Spalte eine Kopfleiste. Reines CSS, kein
   JavaScript: ein Ausklappmenue braeuchte Skript, und die Seiten hier
   funktionieren bewusst auch ohne. */
@media (max-width: 900px) {
  .app-shell { grid-template-columns: 1fr; }
  .sidebar {
    position: static;
    height: auto;
    gap: var(--space-4);
    border-right: 0;
    border-bottom: 1px solid var(--border);
  }
  .sidebar__nav {
    flex-direction: row;
    flex-wrap: wrap;
    gap: var(--space-1);
  }
  .sidebar__label { display: none; }
  /* Hier stand eine Ersatzkante fuer den entfallenen Balken. Beides ist
     hinfaellig: Die Kontur der Pille traegt das farbunabhaengige Merkmal in
     beiden Anordnungen, waagerecht wie senkrecht. */

  /* IM SCHMALMODUS LIEGT DIE LEISTE WAAGERECHT und umbricht. Ein
     aufgeklappter Bereich bekommt deshalb eine eigene Zeile: Stuende er
     zwischen den anderen Eintraegen, schoeben seine Unterpunkte die
     nachfolgenden Bereiche nach unten weg, und die Leiste haette je nach
     Zustand eine andere Hoehe an einer anderen Stelle. */
  .navgroup { flex: 1 0 100%; }
  /* Die Unterpunkte ebenfalls waagerecht - senkrecht untereinander waere
     hier eine hohe, schmale Spalte in einer flachen Leiste. */
  .navsub {
    flex-direction: row;
    flex-wrap: wrap;
    gap: var(--space-1);
    padding-left: var(--space-3);
  }
  /* DAS KONTOMENUE TEILT SICH DIE ERSTE ZEILE MIT DEM LOGO, rechtsbuendig.
     Die Seitenleiste ist hier eine Kopfleiste ueber dem Inhalt; eine eigene
     Zeile fuer den Knopf haette das Logo nach unten geschoben.
     Die Hoehe ist auf die Mitte des Logos abgestimmt: Leiste 32px oben,
     Knopf 44px hoch ab 16px. */
  .sidebar { grid-row: 1; }
  .app-topbar {
    grid-column: 1;
    grid-row: 1;
    justify-self: end;
    align-self: start;
    padding: var(--space-4) var(--space-4) 0;
  }
  .app-main { grid-column: 1; grid-row: 2; }
  /* Platz fuer den Knopf rechts neben dem Logo. Das Logo schrumpft unter
     400px auf die Breite seiner Spalte (siehe .brand__logo). */
  .sidebar__brand { padding-right: calc(var(--control-height) + var(--space-8)); }
  /* Nur das Symbol - der vorgelesene Name "Kontomenue" bleibt. */
  .kontomenue__name { display: none; }
  .app-content { padding: var(--content-padding-narrow) var(--space-4); }
}

/* --- Seitenkopf innerhalb des Inhalts ----------------------------------- */
.page-header {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  align-items: flex-start;
  justify-content: space-between;
  margin-bottom: var(--space-6);
}
/* HIER STAND `.page-header--start` (margin-top: --space-10). Der Modifikator
   gab der Begruessung 40px zusaetzlich zum Polster der Inhaltsflaeche, also
   80px bis zur Ueberschrift. Max hat den Anlauf am 18.9.2026 zurueckgenommen:
   Der Gruss stand damit sichtbar zu tief unter der Kopfleiste. Die Startseite
   hat jetzt dieselben 40px wie die uebrigen Seiten - gemessen 100px von der
   Fensterkante statt 140px. Kein Ersatzwert und kein dritter Sonderfall. */

.page-header__text { min-width: 0; }
.page-header__sub {
  max-width: var(--prose-max);
  margin: var(--space-1) 0 0;
  color: var(--text-muted);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
}
.page-header__actions {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
}

/* --- Fusszeile ---------------------------------------------------------- */
.sitefooter {
  padding: var(--space-6) var(--space-8);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
}
.sitefooter nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
}
.sitefooter a { color: var(--text-muted); }
.sitefooter a:hover { color: var(--action-text); }
/* Auf den oeffentlichen Seiten steht die Fusszeile mittig unter der schmalen
   Spalte, hinter der Anmeldung linksbuendig unter dem Inhalt. */
.sitefooter--centered {
  max-width: var(--content-max);
  margin: 0 auto;
}
.sitefooter--centered nav { justify-content: center; }

/* ==========================================================================
   3. KOMPONENTEN
   ========================================================================== */

/* --- Wortmarke ----------------------------------------------------------
   EINE Definition fuer Sidebar UND Anmeldeseiten. Vorher war sie nur in der
   Sidebar zu Hause; sobald sie an zwei Stellen gebraucht wird, gehoert sie in
   eine Komponente - sonst laufen die beiden Fassungen auseinander, und man
   merkt es erst, wenn das Logo kommt.

   Das Quadrat traegt die Markenfarbe als Flaeche mit weisser Ziffer darauf.

   FRUEHER STAND HIER EINE EINSCHRAENKUNG, die es nicht mehr gibt: Das
   Markenrot erreichte als Text nur 4.31:1 und durfte deshalb ausschliesslich
   Flaeche sein - erlaubt war es hier nur, weil fuer Flaechen die 3:1-Schwelle
   aus WCAG 1.4.11 gilt und nicht die 4.5:1 fuer Text. Das Markenblau
   schafft 4.77:1 und traegt beides. Die Wortmarke ist damit nicht mehr die
   Ausnahme, sondern derselbe Ton wie jeder Knopf daneben. */
.brand {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
}
.brand:hover { text-decoration: none; }

/* Die Anzeigebreite steht HIER und nicht am <img>: Die Datei liegt in
   doppelter Groesse vor, damit sie auf hochaufloesenden Schirmen scharf
   bleibt - angezeigt wird sie halb so breit. `height: auto` haelt das
   Seitenverhaeltnis, egal welche Datei das Macro liefert. */
.brand__logo {
  display: block;
  width: 200px;
  height: auto;
}
.brand--gross .brand__logo { width: 260px; }

/* Auf schmalen Schirmen darf die Marke nicht ueber ihren Platz hinauslaufen -
   sie steht dort in einer Kopfzeile ohne feste Breite. */
@media (max-width: 400px) {
  .brand__logo, .brand--gross .brand__logo { width: 100%; max-width: 260px; }
}


/* --- Buttons ------------------------------------------------------------
   Das bare `button`-Element ist die primaere Variante. Absicht: Templates,
   die noch nicht auf die Klassen umgestellt sind, sehen dadurch von sich aus
   richtig aus statt unformatiert. */
button,
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--control-height);
  padding: 0 var(--space-4);
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: var(--action);
  color: var(--text-on-action);
  font-family: inherit;
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
  font-weight: var(--weight-semibold);
  text-decoration: none;
  cursor: pointer;
  /* Nur Farbe animieren. Ein Transform-Effekt auf Buttons wirkt bei einer
     Verwaltungsoberflaeche spielerisch, nicht wertig. */
  transition: background-color 120ms ease, border-color 120ms ease;
}
button:hover,
.btn:hover {
  background: var(--action-hover);
  color: var(--text-on-action);
  text-decoration: none;
}
button:active,
.btn:active { background: var(--action-active); }

button:disabled,
.btn:disabled,
.btn[aria-disabled="true"] {
  background: var(--surface-disabled);
  color: var(--text-muted);
  cursor: not-allowed;
}

.btn svg { width: 16px; height: 16px; flex: 0 0 auto; }

/* Sekundaer: heller Grund mit Rahmen. Fuer alles, was neben einer primaeren
   Aktion steht - zwei gefuellte Knoepfe nebeneinander nehmen sich gegenseitig
   die Aussage. */
.btn--secondary {
  background: var(--surface);
  /* --border-control, nicht --border-strong: Die Flaeche dieses Knopfs ist
     weiss auf weissem Grund, der Rahmen ist also das Einzige, was ihn als
     Bedienelement ausweist - genau der Fall, den WCAG 1.4.11 mit 3:1 meint.
     --border-strong erreichte 1.48:1 und war damit ein dekorativer
     Kartenrand an einer Stelle, an der er eine Aufgabe hatte. */
  border-color: var(--border-control);
  color: var(--text);
}
.btn--secondary:hover {
  background: var(--surface-hover);
  border-color: var(--border-hover);
  color: var(--text-strong);
}
.btn--secondary:active { background: var(--surface-raised); }

/* Ghost: ohne Flaeche, fuer Aktionen in Tabellenzeilen. */
.btn--ghost {
  background: transparent;
  color: var(--text-label);
  border-color: transparent;
}
.btn--ghost:hover {
  background: var(--surface-hover);
  color: var(--text-strong);
}

/* Loeschen. Rot - und seit der Umstellung auf eine blaue Marke ist das
   endlich eine eigene Aussage und nicht mehr dieselbe Farbe wie "Weiter".

   VORHER WAREN BEIDE ROT: --action und --danger-icon zeigten auf denselben
   Wert, weil die Marke rot war. Ein bestaetigender Knopf ("Genehmigung liegt
   vor") sah damit aus wie ein loeschender, und der Unterschied lag allein in
   der Beschriftung. Jetzt tragen die beiden verschiedene Farben, und die
   zurueckhaltendere Form - heller Grund statt gefuellter Flaeche - sagt
   zusaetzlich, dass hier nichts aus Gewohnheit geklickt werden soll. */
.btn--danger {
  background: var(--surface);
  border-color: var(--danger-border);
  color: var(--danger-text);
}
.btn--danger:hover {
  background: var(--danger-bg);
  border-color: var(--danger-icon);
  color: var(--danger-text);
}
.btn--danger-solid {
  background: var(--danger-solid);
  border-color: transparent;
  color: var(--text-on-action);
}

.btn--sm {
  min-height: 32px;
  /* SENKRECHT NICHT MEHR NULL. Solange die Beschriftung in eine Zeile passt,
     bestimmt `min-height` die Hoehe und das Polster faellt nicht auf. Bricht
     sie um - "Als nicht erschienen markieren" auf einem 390px-Schirm - sass
     der Text vorher mit 1px auf der Kante. WAAGERECHT UNVERAENDERT: Ein
     breiterer Knopf wuerde an SECURITY.md Punkt 99 ruehren, ein hoeherer
     nicht. Gemessen auf 390, 360 und 320px. */
  padding: var(--space-1) var(--space-3);
  font-size: var(--text-xs);
}
.btn--block { width: 100%; }

/* Sieht wie ein Link aus, ist aber ein Button. Wird gebraucht, wo ein
   zustandsaendernder Vorgang (Abmelden, Mail erneut senden) als POST-Formular
   laufen MUSS, optisch aber kein Klotz sein soll. */
button.linkish,
.linkish {
  min-height: 0;
  padding: 0;
  border: 0;
  background: none;
  color: var(--action-text);
  font: inherit;
  font-size: inherit;
  font-weight: var(--weight-medium);
  text-decoration: underline;
  cursor: pointer;
}
button.linkish:hover,
.linkish:hover {
  background: none;
  color: var(--action-text-hover);
}

/* --- Karten -------------------------------------------------------------

   RAHMEN UND SCHATTEN ZUSAMMEN, und das war bis hierher ausdruecklich
   verboten. Die Begruendung fuer die Ruecknahme steht bei --shadow in
   tokens.css; kurz: Der Eindruck, unsere Karten seien schwaecher konturiert
   als eine Referenzoberflaeche, lag nicht am Rahmen - der ist dort auf einen
   Wert in einem Kanal derselbe - sondern am fehlenden Schatten.

   DER SCHATTEN TRAEGT HIER NICHT DIE KANTE, sondern den Abstand zum Blatt.
   Die Kante bleibt der Rahmen. Deshalb --shadow und nicht --shadow-lg: Wer
   den kraeftigen nimmt, bekommt genau das "schwer", das die alte Regel
   verhindern wollte. */
.card {
  padding: var(--card-padding);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
}
/* Fuer Tabellen: der Rahmen sitzt an der Karte, die Tabelle randet ohne
   eigenen Abstand hinein. */
.card--flush { padding: 0; overflow: hidden; }
/* DER RAHMEN DES WOCHENRASTERS - der fuenfte Anlauf an dieser Stelle.

   Vorher, der Reihe nach: Mindesthoehe an der Karte mit gedehnten Zeilen
   (unterste Zeile abgeschnitten); an der Tabelle (Tabellen verteilen
   Ueberhoehe auf ihre Zeilen - drei Zeilen wurden je 172px hoch); an der
   Karte ohne Dehnung (Raster hoerte mittendrin auf); an der Karte,
   `calc(100vh - 20rem)`, mit abgeschalteten Fortsetzungszeilen (Hoehe hing
   am Fenster, nicht am Inhalt).

   JETZT: Der ZEILENBEREICH unter dem Tageskopf ist mindestens so hoch wie
   eine Tagesspalte der Terminvorschau auf der Startseite -
   `--preview-entries-height` (tokens.css), derselbe Wert wie dort und wie
   dort eine MINDESThoehe. Kurze Regeln werden bis dahin mit abgeschalteten
   Zeilen aufgefuellt, nicht darueber hinaus; laengere wachsen.

   WIE, OHNE DIE HOEHE DES TAGESKOPFS ZU KENNEN (sie haengt an Schrift und
   Umbruch): Der Tabellenkasten ist ein Raster aus zwei Zeilen.
     1. Zeile, `auto`: die Tabelle selbst.
     2. Zeile, `1fr`: die Fortsetzung (`weekgrid__fort`). Ihre Tabelle liegt
        absolut darin und traegt deshalb nichts zur Hoehe bei; was nicht
        passt, schneidet `overflow: hidden` ab.
     Der MASSSTAB (`weekgrid__mass`, unsichtbar) liegt ueber beiden Zeilen:
     der Tageskopf noch einmal - dasselbe Makro, also dieselbe Hoehe - plus
     ein Block in Rahmenhoehe. Ein Element ueber einer `fr`-Zeile gibt dieser
     genau das, was ihm nach den uebrigen Zeilen fehlt: Die Fortsetzung wird
     so hoch wie `--preview-entries-height` minus die echten Zeilen - oder
     null, wenn die Regel laenger ist.

   Gemessen in Chromium (tests/test_standort_browser.py): Zeilenbereich =
   Hoehe einer leeren Tagesspalte der Startseite. */
.card--raster { display: flex; flex-direction: column; }
.card--raster > .table-wrap { flex: 1 0 auto; }
/* DER KASTEN ROLLT IN SICH, DIE SEITE NICHT (21.9.2026).
   `overflow-y: auto` statt `hidden`, gedeckelt auf --raster-max-height: Der
   Kartenkopf und die Wochennavigation darueber bleiben stehen, waehrend die
   Zeitzeilen darunter durchlaufen.

   DER TAGESKOPF BLEIBT KLEBEN. Ohne das rollen die Wochentage nach oben weg,
   und ab der dritten Zeile weiss niemand mehr, welche Spalte der Mittwoch
   ist. `position: sticky` an den Kopfzellen und nicht am `thead`: Eine
   Tabellenzeile laesst sich in mehreren Browsern nicht kleben, die Zelle
   schon.

   `min-height` bleibt, was es war: Kurze Regeln fuellen den Kasten bis
   --preview-entries-height auf. Ober- und Untergrenze sind jetzt beide da. */
.table-wrap--raster {
  display: grid;
  grid-template-rows: auto 1fr;
  max-height: var(--raster-max-height);
  overflow-y: auto;
}
.table-wrap--raster > .weekgrid thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--surface);
}
/* Die Ecke oben links ueberdeckt beim Rollen die Uhrzeiten - sie klebt
   deshalb in beide Richtungen. */
.table-wrap--raster > .weekgrid thead th:first-child { z-index: 3; }
/* DIE HEUTE-SPALTE BEHAELT IHRE FARBE AUCH IM KLEBENDEN KOPF.
   Ohne diese Zeile gewinnt die Regel darueber (Spezifitaet 0,3,1) gegen
   `.weekgrid__today` (0,1,0), und der Kopf des heutigen Tages wird weiss -
   die Spalte zerfiele in einen weissen Kopf und einen blauen Rumpf. Genau
   das misst tests/test_standort_browser.py, und genau daran ist die erste
   Fassung dieser Regel am 21.9.2026 aufgefallen. */
.table-wrap--raster > .weekgrid thead th.weekgrid__today {
  background: var(--action-subtle);
}
.table-wrap--raster > .weekgrid:not(.weekgrid--mass) { grid-row: 1; grid-column: 1; }
.weekgrid__fort { grid-row: 2; grid-column: 1; position: relative; overflow: hidden; }
.weekgrid__fort > .weekgrid--fort { position: absolute; top: 0; left: 0; }
.weekgrid__mass { grid-row: 1 / 3; grid-column: 1; visibility: hidden; pointer-events: none; }
.weekgrid__mass-rahmen { height: var(--preview-entries-height); }
/* Die Karte auf den Seiten vor der Anmeldung - siehe --auth-card-padding. */
.card--auth { padding: var(--auth-card-padding); }
/* Zwei Karten untereinander brauchen eine Fuge. Ohne sie stossen ihre
   Raender aneinander und lesen sich als EIN Kasten mit einem Strich darin -
   gerade dort, wo zwei Karten bewusst zwei Dinge sind ("was gefunden wurde"
   und "was das Loeschen bewirkt"). Als Nachbarschaftsregel und nicht als
   `margin-bottom` an jeder Karte: so bleibt die letzte Karte eines
   Abschnitts ohne haengenden Abstand nach unten. */
.card + .card { margin-top: var(--card-gap); }
/* ABER NICHT IN EINEM RASTER. Die Regel oben gilt fuer KARTEN UNTEREINANDER;
   in einem Grid ist die zweite Karte ebenfalls "die naechste Schwester" und
   bekam denselben Aussenabstand - obendrauf, wo das Raster schon `gap` hat.
   Sichtbar wurde das als Hoehenunterschied: Die zweite Karte streckte sich
   auf die Reihenhoehe MINUS ihres eigenen Aussenabstands und war damit
   gemessene 24px flacher als die erste (338 gegen 314 Pixel) - genau der
   Wert von --card-gap. Zweimal wurde an der falschen Stelle gesucht, weil
   die Ursache nicht in der Karte lag, sondern in einer Regel fuer einen
   ganz anderen Fall. */
.duo > .card + .card { margin-top: 0; }
.card__header {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: baseline;
  justify-content: space-between;
  padding: var(--space-4) var(--card-padding);
  border-bottom: 1px solid var(--border);
}
.card__title {
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
  font-weight: var(--weight-semibold);
  color: var(--text-strong);
}
/* Der Paketname traegt die Karte: Er ist die Ueberschrift, nicht eine
   Beschriftung neben anderen. Nur Groesse und Gewicht - Farbe und Abstaende
   erbt er von .card__title. */
.card__title--gross {
  font-size: var(--text-lg);
  line-height: var(--text-lg-lh);
  font-weight: var(--weight-bold);
}
.card__meta {
  color: var(--text-muted);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
}
/* Fuer .card--flush mit Kopfzeile: die Karte selbst hat dann keine Polsterung,
   der Inhalt braucht sie aber trotzdem. */
.card__body { padding: var(--card-padding); }
.card__footer {
  padding: var(--space-3) var(--card-padding);
  border-top: 1px solid var(--border);
  background: var(--surface-sunken);
}

/* --- Abrechnung: Abschnitt mit Aktion rechts -----------------------------
   NACH DEM VORBILD DER CLAUDE-ABRECHNUNGSSEITE (Vorlage vom 18.9.2026): Jeder
   Abschnitt traegt seine Aktion RECHTS auf Hoehe der Zeile, nicht als Knopf
   unten links in der Karte. Die Vorlage trennt ihre Abschnitte ganz ohne
   Kartenkonturen, allein durch Weissraum - hier bleibt die Karte, weil eine
   helle Flaeche ohne Rahmen anders wirkt als eine dunkle.

   KEIN NEUER BAUSTEIN, WO EINER REICHT: Die Kopfzeile ist `.card__header`,
   die bereits `space-between` traegt; `.rowactions` macht die Knopfgruppe
   rechtsbuendig. Neu ist nur die Zeile hier - eine Angabe links, ihre Aktion
   rechts, mit Luft dazwischen. */
.billrow {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3) var(--space-6);
  align-items: center;
  justify-content: space-between;
  padding: var(--card-padding);
}
.billrow + .billrow { border-top: 1px solid var(--border); }

/* DER GEMEINSAME BLOCK: eine Flaeche fuer Tarif, Zahlung, Rechnungen und
   Kuendigung. Die Abschnittsueberschriften liegen DARIN - eine Ueberschrift
   ausserhalb gehoerte optisch zu nichts und zerlegte den Block wieder.

   Die Linie oben trennt sie vom vorigen Abschnitt; die erste Ueberschrift
   braucht keine, weil ueber ihr die Kartenkante liegt. */
.billblock__titel {
  margin: 0;
  padding: var(--space-6) var(--card-padding) var(--space-2);
  border-top: 1px solid var(--border);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
  font-weight: var(--weight-semibold);
  color: var(--text-label);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
/* Nach einer Ueberschrift folgt die Zeile ohne zweite Linie - sonst staenden
   zwei Striche mit zwei Pixeln Abstand uebereinander. */
.billblock__titel + .billrow { border-top: 0; }
/* Die Rechnungstabelle sitzt buendig im Block; ihre Kopfzeile traegt bereits
   eine eigene Linie. */
.billblock .table-wrap + .billblock__titel { border-top: 1px solid var(--border); }
.billrow__text { min-width: 0; }
/* DER TEXTTEIL NIMMT DEN PLATZ, DER UEBRIG BLEIBT - `flex: 1` statt einer
   Mindestbreite am Balken.

   GEMESSEN AM 19.9.2026, weil der erste Versuch danebenlag: Eine
   `min-width: min(28rem, 100%)` am Balken wirkte nicht, denn `100%` bezieht
   sich auf den Flex-Teil, und der war selbst nur 268 px breit - so breit wie
   sein laengster Text. Der Balken schrumpfte mit. Mit `flex: 1` wird der
   Textteil so breit wie die Zeile es zulaesst (942 px), und der Balken
   darin ebenso.

   `min-width: 0` bleibt daneben stehen: Ohne sie waechst ein Flex-Element
   nicht unter die Breite seines Inhalts zurueck, und eine lange Zeile
   schoebe die Aktion rechts aus der Karte. */
.billrow__text { flex: 1 1 auto; }
.billrow__text > p { margin: 0; }
.billrow__text > p + p { margin-top: var(--space-1); }

/* Der Tarifkopf: Name gross, Beschreibung darunter, Aktion rechts. */
.billhead__name {
  margin: 0;
  font-size: var(--text-lg);
  line-height: var(--text-lg-lh);
  font-weight: var(--weight-bold);
  color: var(--text-strong);
}

/* DIE RECHNUNGSZEILEN ATMEN: In der Vorlage steht zwischen zwei Zeilen
   deutlich mehr Luft als in unseren Datentabellen (12px). Das gilt NUR hier -
   die uebrigen Tabellen der Verwaltung sind Arbeitslisten, in denen Dichte
   ein Vorteil ist. */
.table--billing tbody td { padding-top: var(--space-4); padding-bottom: var(--space-4); }

/* --- Tabellen ----------------------------------------------------------- */
/* Breite Tabellen scrollen in ihrem eigenen Kasten statt die Seite zu
   sprengen. Zusammen mit `min-width: 0` auf .app-main ist das der Grund,
   warum eine lange Beschreibung das Layout nicht verschiebt. */
.table-wrap {
  /* Bezugspunkt fuer die Jetzt-Linie im Wochenraster. Sie liegt IM
     scrollenden Kasten und laeuft deshalb beim waagerechten Scrollen mit dem
     Raster mit, statt darueber stehenzubleiben. */
  position: relative;
  overflow-x: auto;
}

table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
}
caption {
  padding: var(--space-3) var(--card-padding);
  color: var(--text-muted);
  font-size: var(--text-xs);
  text-align: left;
}
/* DIE WAAGERECHTE ZELLPOLSTERUNG WAR --space-4 (16px) und ist --space-6
   (24px). Auch das ist gemessen und nicht geschaetzt: In der Referenz liegt
   die erste Tabellenspalte 38 Punkte von der Kartenkante entfernt, bei uns
   lag sie 18.

   NOCH NICHT AUF 38: Am --card-padding in tokens.css steht der Einwand, dass
   eine Datentabelle mit 40px Zellrand auseinanderfaellt - und er ist nicht
   erledigt, sondern nur noch nicht faellig. 24px ergeben in einer randlosen
   Karte 26 Punkte Gesamtabstand und damit genau den Kopf-Abstand der
   Referenz. Ob 32 noch tragen, entscheidet der Blick auf 900 und 768 Punkte
   Breite, nicht der Wunsch nach Symmetrie. */
thead th {
  padding: var(--space-3) var(--cell-padding-x);
  background: var(--surface-sunken);
  border-bottom: 1px solid var(--border);
  color: var(--text-label);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  text-align: left;
  white-space: nowrap;
}
tbody td {
  /* Dieselbe waagerechte Polsterung wie `thead th` - sonst stuende die
     Ueberschrift neben ihrer Spalte. Die Begruendung steht am Token. */
  padding: var(--space-3) var(--cell-padding-x);
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}
/* Letzte Zeile ohne Trenner - sonst liegt eine Linie direkt auf dem
   Kartenrand und wirkt wie ein Doppelstrich. */
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover { background: var(--surface-hover); }

/* --- Verbrauchsbalken ----------------------------------------------------
   EINE KLASSE JE FUENFERSCHRITT STATT EINER BREITE IM MARKUP.

   Der naheliegende Weg waere `style="width: 68%"` am Balken gewesen. Er
   scheitert an der eigenen CSP: `style-src 'self'` ohne 'unsafe-inline'
   (app/core/middleware.py). In strengen Browsern bliebe der Balken damit
   leer - und zwar lautlos, ohne Fehlermeldung, auf einer Seite, die
   Verbrauch anzeigen soll.

   Einundzwanzig Regeln sind der Preis dafuer. Die Genauigkeit von fuenf
   Prozent genuegt fuer eine Anzeige, neben der die exakte Zahl als Text
   steht; wer sie genauer braucht, liest die Zahl.

   ROLE="IMG" MIT ARIA-LABEL steht in der Vorlage: Ein leeres <div> ist fuer
   einen Screenreader nichts. Die Beschriftung nennt den genauen Prozentwert,
   nicht den gerundeten. */
.meter {
  height: 10px;
  margin: var(--space-3) 0 var(--space-2);
  border-radius: 999px;
  background: var(--surface-raised);
  overflow: hidden;
}
.meter__bar {
  height: 100%;
  border-radius: inherit;
  background: var(--action);
}
/* FARBSCHWELLEN (19.9.2026): ab 70 % verbraucht gelb, ab 90 % rot. Die Stufe
   faellt serverseitig in `plan.balken()` - im Markup steht nur die fertige
   Klasse, damit sich die Warnung nicht im Browser wegschalten laesst.

   BEIDE FARBEN SIND BESTEHENDE SEMANTISCHE FLAECHEN-TOKENS: --warning-accent
   traegt schon die Leiste am Warnbanner, --danger-solid den gefuellten
   Loeschen-Knopf. Eine Rampenstufe direkt zu nennen, verbietet
   tests/test_design_tokens.py zu Recht - sie ueberlebte einen Dark Mode
   nicht.

   KEIN ZUSAETZLICHES MERKMAL NOETIG: Der Balken traegt daneben immer den
   Prozentsatz als Text (`role="img"` mit aria-label), die Farbe ist also
   nicht der einzige Traeger der Aussage. */
.meter__bar--ok   { background: var(--meter-ok); }
.meter__bar--warn { background: var(--meter-warn); }
.meter__bar--crit { background: var(--meter-crit); }

/* Der kleine Balken in einer Tabellenzeile: flacher und schmaler als der
   grosse oben, damit die Zeile eine Zeile bleibt und nicht zum Abschnitt
   wird. Die Breite ist gedeckelt - ueber die ganze Spalte gezogen konkurrierte
   er mit dem Zahlenwert rechts um die Aufmerksamkeit. */
.meter--sm {
  height: 6px;
  max-width: 18rem;
  margin: var(--space-2) 0 0;
}
.meter__bar--0 { width: 0%; }
.meter__bar--5 { width: 5%; }
.meter__bar--10 { width: 10%; }
.meter__bar--15 { width: 15%; }
.meter__bar--20 { width: 20%; }
.meter__bar--25 { width: 25%; }
.meter__bar--30 { width: 30%; }
.meter__bar--35 { width: 35%; }
.meter__bar--40 { width: 40%; }
.meter__bar--45 { width: 45%; }
.meter__bar--50 { width: 50%; }
.meter__bar--55 { width: 55%; }
.meter__bar--60 { width: 60%; }
.meter__bar--65 { width: 65%; }
.meter__bar--70 { width: 70%; }
.meter__bar--75 { width: 75%; }
.meter__bar--80 { width: 80%; }
.meter__bar--85 { width: 85%; }
.meter__bar--90 { width: 90%; }
.meter__bar--95 { width: 95%; }
.meter__bar--100 { width: 100%; }

/* DIE GANZE ZEILE ALS ZIEL - mit dem Link, den die Zeile ohnehin hat.

   Der vorhandene <a> bekommt eine unsichtbare Flaeche ueber die ganze Zeile.
   Es bleibt bei EINEM Link mit EINEM href: Die Tastatur erreicht ihn wie
   bisher, "in neuem Tab oeffnen" funktioniert, und ein Screenreader liest
   genau ein Ziel vor. Ein `onclick` auf dem <tr> waere nichts davon - es
   waere ein Klickfeld, das nur die Maus kennt.

   OHNE SKRIPT und ohne zweites Ziel in der Zeile: Gaebe es in einer weiteren
   Zelle einen Knopf, laege er unter dieser Flaeche und waere nicht mehr
   erreichbar. Diese Tabelle hat keinen - wer einen ergaenzt, muss ihn
   `position: relative` geben, damit er oben liegt.

   DER PREIS: Text in den uebrigen Zellen laesst sich nicht mehr mit der Maus
   markieren. Bei einer Liste, die man oeffnet statt abschreibt, ist das der
   guenstigere Tausch.

   `position: relative` auf <tr> koennen aeltere Browser nicht; dort bleibt
   schlicht der normale Link in der ersten Spalte. Nichts geht verloren. */
tr.row--link { position: relative; }
.rowlink::after {
  content: "";
  position: absolute;
  inset: 0;
}

/* Die Termine eines Intensivkurses: drei Spalten, die sich den Platz teilen.
   Ohne Vorgabe bekam "Verschieben oder absagen …" als laengster Text den
   meisten Raum, waehrend der Termin - die Angabe, die gelesen wird -
   zusammengedrueckt wurde. */
.table--plan .col--plan-termin { width: 46%; }
.table--plan .col--plan-stand  { width: 18%; }
.table--plan .col--plan-aktion { width: 36%; }

th.num, td.num { text-align: right; white-space: nowrap; }
td.num { font-variant-numeric: tabular-nums; }
td.description {
  max-width: 24rem;
  color: var(--text-muted);
}
td.actions { white-space: nowrap; text-align: right; }
/* Aktionsspalte einer Tabellenzeile. `flex-wrap` statt `nowrap`: Die Knoepfe
   tragen jetzt Rahmen und ausgeschriebene Beschriftungen ("Als nicht
   erschienen markieren") - in einer Zeile erzwungen wuerden sie die Tabelle
   ueber den Bildschirm schieben. */
.rowactions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
  justify-content: flex-end;
}
.rowactions .inline-form { margin: 0; }

/* Ein Formular, das in einer Zeile mit anderen Bedienelementen steht -
   Rechtezuweisung, Zugang sperren, Passwort erzeugen.

   FRUEHER GALT DIESE REGEL NUR INNERHALB VON `.rowactions`. Die
   Team-Verwaltung braucht dieselbe Form ausserhalb einer Tabellenzeile;
   dort stuende das Formular sonst als Block unter dem Knopf statt daneben. */
.inline-form {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
  margin: 0 var(--space-2) var(--space-2) 0;
}
/* Ein Auswahlfeld in einer Zeile nimmt seine eigene Breite, nicht die volle.

   Formularfelder stehen im Projekt auf `width: 100%` - richtig fuer ein
   Formular, in dem jedes Feld eine eigene Zeile hat. Hier steht daneben ein
   Knopf, und mit voller Breite rutscht der unter das Feld. Im Browser
   nachgesehen: Bei acht Bereichen wurden daraus acht doppelte Zeilen. */
.inline-form select,
.inline-form input {
  width: auto;
  min-width: 12rem;
}
/* Ausserhalb einer Tabellenzeile stehen dieselben Knoepfe links. */
.rowactions--start { justify-content: flex-start; margin-bottom: var(--space-4); }

.cell-strong { font-weight: var(--weight-medium); color: var(--text-strong); }

/* Das einmalig angezeigte Zufallspasswort (admin/team/created.html).

   GROESSER UND MIT LUFT ZWISCHEN DEN ZEICHEN, weil es abgeschrieben oder
   vorgelesen wird - genau die zwei Faelle, in denen `l` und `1` und `O` und
   `0` sich rächen. Der Monospace-Stack des Projekts unterscheidet sie; die
   zusaetzliche Laufweite macht daraus lesbaren Abstand.

   `user-select: all` markiert bei einem Klick den ganzen Wert. Wer ihn
   kopiert, soll nicht das letzte Zeichen verlieren. */
.passwort {
  display: inline-block;
  padding: var(--space-2) var(--space-3);
  background: var(--surface-sunken);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: var(--text-base);
  letter-spacing: 0.05em;
  user-select: all;
}
.cell-empty { color: var(--text-muted); }

/* Die Wiederherstellungscodes der Zwei-Faktor-Anmeldung.

   MEHRSPALTIG UEBER `grid` UND NICHT UEBER `columns`: Diese Liste wird
   ausgedruckt und abgeschrieben. Ein `column-count`-Fluss ordnet die Codes
   SPALTENWEISE (1-5 links, 6-10 rechts); wer sie abhakt, liest aber
   zeilenweise und verliert dabei die Reihenfolge. Das Raster fuellt Zeile
   fuer Zeile, so wie man es liest.

   `auto-fit` mit Mindestbreite statt fester Spaltenzahl: Auf einem Handy
   steht die Liste einspaltig, ohne dass ein Code umbricht - ein umgebrochener
   Wiederherstellungscode wird falsch abgeschrieben. */
.codelist {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: var(--space-2);
  margin: var(--space-4) 0;
  padding: 0;
  list-style: none;
}
/* EIN CODE BRICHT NIE UM. Mit 13rem Mindestbreite tat er es - "CO6L-HVXC-
   ZCEO-" in der ersten und "HJYP" in der zweiten Zeile. Wer das abschreibt,
   verliert den Bindestrich oder haelt die zweite Zeile fuer einen eigenen
   Code, und beim Einloesen kommt "Der Code stimmt nicht" heraus. Deshalb
   beides: genug Mindestbreite fuer 19 Zeichen UND der ausdrueckliche
   Riegel gegen den Umbruch - die Schriftgroesse kann sich aendern, die
   Anforderung nicht. */
.codelist .passwort {
  display: block;
  white-space: nowrap;
  text-align: center;
}

/* Der durchgestrichene Originalpreis neben einem Aktionspreis.

   ER IST GEDAEMPFT UND NICHT ROT: Ein reduzierter Preis ist keine Warnung.
   Die Aufmerksamkeit gehoert der Zahl, die gilt - der alte Preis ist nur
   der Bezugspunkt dafuer.

   Die Durchstreichung selbst kommt aus dem <s>-Element und nicht von hier:
   Sie traegt die Bedeutung ("das war der Preis"), und eine Bedeutung, die
   nur im Stylesheet steht, fehlt jedem, der die Seite ohne Stylesheet
   liest. */
.price-struck {
  color: var(--text-muted);
  margin-right: var(--space-2);
}

/* Die Formulare der drei Kalender - Regel, Theorietermin, Kurs.

   DAS PROBLEM, GEMESSEN: Die Felder tragen `--field-max` (512px), die
   Uhrzeiten 176px und die Zahlen 128px. Untereinander in einer 1104px
   breiten Karte ergibt das eine schmale Spalte links und zwei Drittel leere
   Flaeche rechts - bei einem Formular mit acht Feldern eine lange Strecke,
   auf der das Auge nichts findet.

   ZWEI SPALTEN, ABER NICHT ERZWUNGEN: `auto-fit` mit Mindestbreite legt die
   Felder nebeneinander, solange Platz ist, und stapelt sie auf dem Handy
   wieder - dasselbe Mittel wie bei `.codelist`. Die Breiten der Felder
   selbst bleiben unangetastet: Ein Datumsfeld ueber die halbe Karte waere
   keine Verbesserung, sondern ein Versprechen ueber seinen Inhalt, das nicht
   stimmt (siehe --field-max in tokens.css).

   NUR HIER UND NICHT GLOBAL. Andere Formulare der Verwaltung sind kuerzer
   oder haben Felder, die nebeneinander nichts gewinnen; eine globale
   Umstellung waere eine Entscheidung fuer vierzig Seiten statt fuer diese
   drei. */
.kalenderform {
  display: grid;
  /* `min(22rem, 100%)` UND NICHT `22rem`: Eine feste Mindestbreite kann eine
     Grid-Spur nicht unterschreiten - auf einem 320px-Schirm blieben die
     352px stehen und zogen die Seite auf 393px auf. Gemessen, bevor es
     jemandem auffaellt: SECURITY.md Punkt 99 war genau dieser Fehler an
     anderer Stelle. Mit `min(...)` schrumpft die Spur auf die verfuegbare
     Breite, sobald sie kleiner ist als das Wunschmass. */
  grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr));
  gap: 0 var(--space-8);
  align-items: start;
}
/* Was ueber die ganze Breite gehoert: Abschnitte, Hinweise und die
   Knopfzeile. Ein Abschnittstitel in der zweiten Spalte saehe aus wie eine
   Ueberschrift fuer das Feld daneben. */
.kalenderform > .settings-section,
.kalenderform > .form-actions,
.kalenderform > .hint,
.kalenderform > .banner,
.kalenderform > fieldset {
  grid-column: 1 / -1;
}
/* Innerhalb eines Abschnitts gilt dasselbe Raster - dort stehen die Felder. */
.kalenderform .settings-section {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr));
  gap: 0 var(--space-8);
  align-items: start;
}
.kalenderform .settings-section > .settings-section__title,
.kalenderform .settings-section > .settings-section__hint {
  grid-column: 1 / -1;
}

/* Der Zusammensteller eines Pakets: eine Tabellenzeile je waehlbarer
   Leistung, mit Auswahlkaestchen, Preismodus und Betrag.

   DIE FELDER SIND HIER SCHMALER ALS SONST. `--field-max` ist auf ein
   einzeln stehendes Formularfeld ausgelegt; in einer Tabellenzelle neben
   zwei weiteren Spalten wuerde es die Zeile sprengen. */
.itemtable select,
.itemtable input[type="text"] {
  max-width: 12rem;
  min-height: auto;
}
.itemtable td { vertical-align: middle; }

/* Die Ziele einer Rabattaktion in einer Tabellenzelle.

   EINE LISTE UND KEIN KOMMAGETRENNTER TEXT: Bei vier Zielen ist die Liste
   laenger, bei einem gleich lang, und in beiden Faellen laesst sie sich
   ueberfliegen. Der Einzug der Browservorgabe waere in einer Tabellenzelle
   allerdings zu gross - er schoebe den Text unter die Spaltenueberschrift
   daneben. */
.targetlist {
  margin: 0;
  padding-left: var(--space-4);
}

/* --- Kennzahlen-Zeile --------------------------------------------------
   Fuer Beschriftung-Wert-Paare (Kontingente, Zusammenfassungen). Bewusst
   KEINE Tabelle: zwei Kennzahlen sind keine Datenreihe, und eine Tabelle
   braechte Spaltenkoepfe mit, die hier nichts zu beschriften haetten. Ein
   <dl> sagt genau das aus, was es ist. */
.datarow {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  align-items: baseline;
  justify-content: space-between;
  padding: var(--space-3) var(--card-padding);
  border-bottom: 1px solid var(--border);
  font-size: var(--text-sm);
}
.datarow:last-child { border-bottom: 0; }
/* Ein Wert ohne Leerzeichen, der trotzdem umbrechen muss - eine lange
   Kennung oder Adresse in einer Datenzeile. Solche Werte enthalten keine
   Stelle, an der ein Browser von sich aus umbricht; ohne diese Regel
   schieben sie die Karte auf schmalen Fenstern auf. */
.datarow dd.breakable {
  font-variant-numeric: normal;
  overflow-wrap: anywhere;
  word-break: break-word;
  font-weight: var(--weight-normal);
}
.datarow dt { margin: 0; color: var(--text-muted); }
.datarow dd {
  margin: 0;
  color: var(--text-strong);
  font-weight: var(--weight-medium);
  /* Ziffern gleicher Breite: sonst tanzen die Werte uebereinander. */
  font-variant-numeric: tabular-nums;
}

/* --- Abschnitte innerhalb einer Seite ---------------------------------- */
.section { margin-top: var(--section-gap); }
.section__title { margin-bottom: var(--space-2); }
.section__intro {
  max-width: var(--prose-max);
  margin-bottom: var(--space-4);
  color: var(--text-muted);
  font-size: var(--text-sm);
  line-height: var(--text-base-lh);
}

/* --- Aufzaehlung ------------------------------------------------------- */
.list {
  margin: 0;
  padding-left: var(--space-6);
  color: var(--text-muted);
  font-size: var(--text-sm);
  line-height: var(--text-base-lh);
}
.list li + li { margin-top: var(--space-2); }
.list ::marker { color: var(--text-muted); }

/* --- Inline-Code -------------------------------------------------------
   Kommt in den Anleitungstexten haeufig vor (Domains, Header, Endpunkte).
   `em` statt `rem` fuer die Groesse, damit der Kasten in einer kleineren
   Umgebung mitschrumpft statt aus der Zeile zu ragen. */
code {
  padding: 1px var(--space-1);
  background: var(--surface-sunken);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: 0.875em;
  overflow-wrap: anywhere;
}
/* Im Codeblock traegt der <pre> den Kasten - das <code> darin nicht noch
   einen zweiten. */
pre code { padding: 0; background: none; border: 0; font-size: inherit; }

/* --- Formulare ---------------------------------------------------------- */
.field { margin-bottom: var(--space-4); }
.field:last-of-type { margin-bottom: 0; }

/* Eine Gruppe von Ankreuzfeldern mit gemeinsamer Ueberschrift (Standorte
   im Team, Fuehrerscheinklassen). OHNE den Browserrahmen: Die Gruppe steht
   ohnehin in einer Karte, ein zweiter Kasten darin waere nur Linie. Die
   Ueberschrift sieht aus wie ein gewoehnliches Feldlabel. */
fieldset.fieldset {
  border: 0;
  margin: 0 0 var(--space-4);
  padding: 0;
  min-width: 0;
}
.fieldset__legend {
  padding: 0;
  margin-bottom: var(--space-2);
  color: var(--text);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
  font-weight: var(--weight-medium);
}
fieldset.fieldset label.checkbox { margin-bottom: var(--space-2); }

label {
  display: block;
  margin-bottom: var(--space-1);
  color: var(--text);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
  font-weight: var(--weight-medium);
}

/* WARUM EINE AUSSCHLUSSLISTE UND KEINE AUFZAEHLUNG DER TYPEN.
   Hier stand `input[type="text"], input[type="email"], input[type="password"],
   input[type="number"]` - und genau daran ist es gescheitert: `tel`, `date`
   und `time` fehlten. Diese Felder bekamen deshalb weder Breite noch Rahmen
   noch Schrift des Systems. Sichtbar wurde das als "die Telefonnummer ist
   schmaler als die anderen" und "das Datumsfeld sieht altmodisch aus" - zwei
   Symptome derselben Auslassung.

   Eine Aufzaehlung ist die falsche Bauweise fuer diese Regel: Sie muss bei
   JEDEM neuen Feldtyp erweitert werden, und wer das vergisst, sieht keinen
   Fehler, sondern ein Feld, das ein bisschen anders aussieht. Die Umkehrung
   nennt stattdessen die wenigen Typen, die KEIN Textfeld sind - und diese
   Liste ist abgeschlossen.

   `:where()` haelt die Spezifitaet bei (0,0,1). Ohne es kaeme die
   :not()-Kette auf (0,5,1) und wuerde spaeter Regeln ueberstimmen, die sie
   nicht ueberstimmen soll - etwa `label.checkbox input`. */
input:where(:not([type="checkbox"], [type="radio"], [type="hidden"],
                 [type="submit"], [type="button"], [type="file"], [type="range"],
                 [type="color"], [type="image"], [type="reset"])),
select,
textarea {
  width: 100%;
  /* Nicht breiter als lesbar - siehe --field-max in tokens.css. Ein
     Datumsfeld ueber die halbe Bildschirmbreite sieht nicht grosszuegig aus,
     sondern kaputt. */
  max-width: var(--field-max);
  min-height: var(--control-height);
  padding: var(--space-2) var(--space-3);
  background: var(--surface);
  /* --border-control statt --border: bei einem Eingabefeld IST der Rahmen das
     Bedienelement. Er erreicht deshalb 3:1 (WCAG 1.4.11), waehrend ein
     dekorativer Kartenrand das nicht muss. */
  border: 1px solid var(--border-control);
  border-radius: var(--radius);
  color: var(--text);
  font-family: inherit;
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
  transition: border-color 120ms ease, box-shadow 120ms ease;
}
textarea { min-height: 6rem; resize: vertical; line-height: var(--text-base-lh); }

/* Enger fuer Angaben mit bekannter Laenge.

   NACH TYP UND NICHT NACH KLASSE, aus demselben Grund, aus dem die Regel
   darueber eine Ausschlussliste ist: Eine Klasse muss jede Vorlage einzeln
   setzen, und wer sie vergisst, sieht keinen Fehler - nur ein Feld, das ein
   bisschen zu breit ist. Der Typ steht ohnehin im Markup, weil er die
   Tastatur und die Browser-Pruefung steuert; die Breite haengt sich daran und
   kann nicht vergessen werden.

   Gemessen war das Feld "Anmeldung moeglich ab ... Monaten" 512px breit fuer
   eine zweistellige Zahl. */
input[type="date"],
input[type="time"] { max-width: var(--field-max-sm); }
input[type="number"] { max-width: var(--field-max-xs); }

input::placeholder, textarea::placeholder { color: var(--text-placeholder); }

/* Fokus: Rahmen UND Ring. Der Ring allein waere bei einem Feld mit hellem
   Rahmen zu leise, der Rahmen allein zu unauffaellig. */
input:where(:not([type="checkbox"], [type="radio"], [type="hidden"],
                 [type="submit"], [type="button"], [type="file"], [type="range"],
                 [type="color"], [type="image"], [type="reset"])):focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: none;
  border-color: var(--action);
  box-shadow: 0 0 0 3px var(--focus-halo);
}

/* Feld mit Fehler. `aria-invalid` statt einer Klasse: dasselbe Attribut, das
   Screenreader auswerten, steuert hier die Darstellung - so kann die eine
   nicht ohne die andere gesetzt werden. */
input[aria-invalid="true"],
textarea[aria-invalid="true"],
select[aria-invalid="true"] {
  border-color: var(--danger-icon);
  background: var(--danger-bg);
}
input[aria-invalid="true"]:focus-visible,
textarea[aria-invalid="true"]:focus-visible {
  box-shadow: 0 0 0 3px var(--danger-focus-halo);
}

.field-error {
  display: flex;
  align-items: flex-start;
  gap: var(--space-1);
  margin: var(--space-1) 0 0;
  color: var(--danger-text);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
  font-weight: var(--weight-medium);
}
.field-error svg {
  flex: 0 0 auto;
  width: 15px;
  height: 15px;
  /* Auf die erste Textzeile ausgerichtet, nicht auf die Oberkante des
     Kastens - bei zweizeiligen Meldungen sonst sichtbar zu hoch. */
  margin-top: 2px;
}
.field-hint {
  margin: var(--space-1) 0 0;
  max-width: var(--prose-max);
  color: var(--text-muted);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
}

/* Sammelmeldung am Kopf eines Formulars (app/static/js/form-validate.js).
   Sie ist eine Live-Region und steht deshalb DAUERHAFT im Dokument, auch
   ohne Text - eine erst im Fehlerfall eingefuegte Region entgeht manchen
   Screenreadern. Sichtbar wird sie ueber :empty und nicht ueber `hidden`:
   Ein `hidden`-Element bleibt auch dann stumm, wenn es spaeter Text bekommt. */
.form-status {
  margin: 0 0 var(--space-4);
  color: var(--danger-text);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
  font-weight: var(--weight-medium);
}
.form-status:empty { display: none; }
.optional {
  color: var(--text-muted);
  font-weight: var(--weight-normal);
}

label.checkbox {
  display: flex;
  gap: var(--space-2);
  align-items: flex-start;
  font-weight: var(--weight-normal);
  font-size: var(--text-sm);
}
label.checkbox input {
  /* 13x13 ist die Browservorgabe und stammt aus einer Zeit anderer
     Bildschirme. Bei 15px Text daneben wirkt das Kaestchen wie ein
     Druckfehler, und der Haken darin ist kaum zu erkennen.

     DIE GROESSE STEHT AM NATIVEN ELEMENT und nicht an einem nachgebauten:
     Ein selbstgebautes Kaestchen (verstecktes input plus ::before) muesste
     Fokus, Tastatur, Zwischenzustand und die Ansage an Screenreader
     nachbilden - vier Dinge, die das echte Element mitbringt. accent-color
     faerbt es in der Markenfarbe, ohne etwas davon aufzugeben. */
  width: 1.05rem;
  height: 1.05rem;
  min-height: 0;
  /* Auf die MITTE DER ERSTEN TEXTZEILE ausgerichtet. `align-items: baseline`
     traegt bei einem ersetzten Element nicht - der Browser legt dessen
     Grundlinie an die Unterkante, und das Kaestchen sitzt dann sichtbar zu
     tief, sobald es groesser als die Vorgabe ist. */
  margin: calc((var(--text-sm-lh) - 1.05rem) / 2) 0 0;
  accent-color: var(--action);
}

.form-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: center;
  margin-top: var(--space-6);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border);
}
/* Ohne Trennlinie - fuer kurze Formulare (Anmelden, Link anfordern), bei denen
   ein Strich ueber dem einzigen Knopf mehr Gewicht hat als der Knopf selbst. */
.form-actions--plain { padding-top: 0; border-top: 0; }

/* --- Einstellungen in Kategorien ----------------------------------------
   Aufklappbare Bereiche auf Basis von <details>. Die Begruendung steht in
   app/templates/admin/_settings.html; hier nur die Darstellung.

   `list-style: none` und das eigene Dreieck: Der Standard-Marker sitzt je
   nach Browser an einer anderen Stelle und laesst sich nicht ausrichten. Das
   `::before` sitzt, wo es soll, und dreht sich beim Aufklappen. */
.settings-group {
  border-bottom: 1px solid var(--border);
}
.settings-group:last-of-type { border-bottom: 0; }

.settings-group__summary {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2) var(--space-3);
  padding: var(--space-4) 0 var(--space-4) var(--space-6);
  position: relative;
  cursor: pointer;
  list-style: none;
}
/* Safari kennt `list-style` an dieser Stelle nicht. */
.settings-group__summary::-webkit-details-marker { display: none; }

.settings-group__summary::before {
  content: "";
  position: absolute;
  left: var(--space-1);
  top: calc(var(--space-4) + 0.35em);
  width: 6px;
  height: 6px;
  border-right: 2px solid var(--text-muted);
  border-bottom: 2px solid var(--text-muted);
  transform: rotate(-45deg);
  transition: transform 120ms ease;
}
.settings-group[open] > .settings-group__summary::before {
  transform: rotate(45deg);
}
.settings-group__summary:hover::before { border-color: var(--text); }

/* --- Der aufklappbare Chatverlauf (Termindetail) ------------------------

   DIESELBE BAUWEISE WIE `.settings-group`, bis hin zum eigenen Dreieck: Der
   Standard-Marker eines <details> sitzt je nach Browser woanders und laesst
   sich nicht ausrichten. Eine zweite Bauform fuer dieselbe Geste waere ein
   Unterschied ohne Bedeutung - wer eines der beiden bedient hat, soll das
   andere nicht neu lernen muessen.

   AUFGEKLAPPT WIRD ER NICHT VON SELBST. Er ist die Absicherung fuer den
   Zweifelsfall, nicht das, was man zuerst liest; stuende er offen, waere die
   Zusammenfassung darueber ueberfluessig. */
.chatlog { margin-top: var(--card-gap); }

.chatlog__summary {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2) var(--space-3);
  padding: var(--space-4) var(--card-padding) var(--space-4) var(--space-8);
  position: relative;
  cursor: pointer;
  list-style: none;
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
  font-weight: var(--weight-medium);
}
.chatlog__summary::-webkit-details-marker { display: none; }

.chatlog__summary::before {
  content: "";
  position: absolute;
  left: var(--card-padding);
  top: calc(var(--space-4) + 0.35em);
  width: 6px;
  height: 6px;
  border-right: 2px solid var(--text-muted);
  border-bottom: 2px solid var(--text-muted);
  transform: rotate(-45deg);
  transition: transform 120ms ease;
}
.chatlog[open] > .chatlog__summary::before { transform: rotate(45deg); }
.chatlog__summary:hover::before { border-color: var(--text); }

.chatlog__body {
  padding: var(--space-2) var(--card-padding) var(--space-4);
  border-top: 1px solid var(--border);
  background: var(--surface-sunken);
}
/* Jede Wortmeldung eine eigene Zeile, umbrechend. `overflow-wrap` statt
   `word-break`: Ein langes Wort soll umbrechen, ein normaler Satz aber an
   den Wortgrenzen bleiben. */
.chatlog__line {
  margin: 0 0 var(--space-2);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
  overflow-wrap: anywhere;
}
.chatlog__line:last-child { margin-bottom: 0; }

/* WER GESPROCHEN HAT, als eigenes Element vor dem Text.

   NICHT ALS PRAEFIX IM TEXT SELBST: Der Text stammt von einem fremden
   Besucher. Schriebe er "Chatbot: ..." in seine Nachricht, saehe seine eigene
   Zeile aus wie eine Antwort der Fahrschule - eine Faelschung, die zwar
   nichts ausfuehrt, aber genau das verkehrt, wozu die Ansicht da ist. Als
   getrenntes Element ist die Zuordnung nicht nachbaubar.

   --text-label auf --surface-sunken ist ein bereits nachgerechnetes Paar
   (siehe tests/test_design_tokens.py, "Tabellenkopf"). */

/* Trenner zwischen zwei Chats desselben Besuchs (SECURITY.md Kapitel 25). */
.chatlog__trenner {
  margin: var(--space-3, .75rem) 0 var(--space-2, .5rem);
  font-size: var(--text-sm, .875rem);
  font-weight: 600;
  color: var(--text-muted);
  border-bottom: 1px solid var(--border);
}

.chatlog__speaker {
  color: var(--text-label);
  font-weight: var(--weight-medium);
}

/* Ein Randstreifen an den Wortmeldungen des Besuchers.

   EIN STREIFEN UND KEINE ZWEITE TEXTFARBE: Die Unterscheidung ist eine
   Gliederung, keine Bedeutung - und eine Textfarbe weniger ist eine
   Kombination weniger, die im Dark Mode nachgerechnet werden muss. Der
   Streifen ist dekorativ (--border), die Zuordnung steht ohnehin im Text
   davor und bleibt damit auch ohne Farbe erhalten (1.4.1).

   Die Antwortzeilen bekommen denselben Einzug ohne Streifen, damit die
   Textkante durchlaeuft und die Spalte nicht bei jeder Zeile springt. */
.chatlog__line--user {
  border-left: 2px solid var(--border-strong);
  padding-left: var(--space-3);
}
.chatlog__line--assistant { padding-left: calc(var(--space-3) + 2px); }

.settings-group__title {
  color: var(--text-strong);
  font-size: var(--text-base);
  line-height: var(--text-base-lh);
  font-weight: var(--weight-semibold);
}
.settings-group__hint {
  color: var(--text-muted);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
}
/* Der Rumpf steht auf derselben Kante wie die Ueberschrift, nicht weiter
   eingerueckt: Eine zweite Stufe macht aus zwei Ebenen scheinbar drei. */
.settings-group__body {
  padding: 0 0 var(--space-6) var(--space-6);
}

/* --- Aufklappbare Terminzeile (Theorieunterricht) -----------------------
   Dieselbe Technik wie bei den Einstellungen: <details>/<summary>, kein
   Skript. Der Unterschied ist die Optik - hier soll es wie eine Tabellenzeile
   aussehen, die sich oeffnet, nicht wie ein Abschnitt.

   Das Grid im <summary> uebernimmt die Spaltenausrichtung, die vorher die
   Tabelle geleistet hat. Eine Tabelle geht hier nicht: Ein <details> darf
   nicht in einem <tbody> stehen. */
.dayrow { border-bottom: 1px solid var(--border); }
.dayrow:last-child { border-bottom: 0; }

/* EINE Rasterdefinition fuer Kopfzeile UND Datenzeilen. Zwei getrennte
   waeren zwei Wahrheiten: Wer eine Spalte verbreitert und die andere Stelle
   vergisst, bekommt eine Ueberschrift, die neben ihrer Spalte steht - und
   das faellt beim Lesen des Quelltexts nicht auf, sondern erst im Browser.

   Die erste Spalte ist breiter als frueher (9rem), weil Datum und Wochentag
   jetzt nebeneinander stehen: "03.09.2026 Donnerstag" braucht den Platz.

   SECHS SPALTEN seit September 2026: "Frei" steht als eigene Spalte neben
   der Belegung - das gruene Abzeichen ist eine eigene Aussage und keine
   Beigabe zur Zahl -, und der STANDORT hat seine eigene bekommen, statt als
   Anhaengsel in der Datumsspalte zu sitzen (Begruendung in der Vorlage).
   Die Spuren sind dabei neu austariert worden, damit die Zeile die Breite
   weiterhin ausnutzt und nicht die letzte Spalte allen Zugewinn bekommt.
   Belegung und Frei sind schmal (Zahl bzw. ein
   Abzeichen), der Status bekommt den Rest. */
.dayrow__head,
.dayrow__summary {
  display: grid;
  grid-template-columns: minmax(12rem, 1.1fr) minmax(7rem, 0.8fr) minmax(8rem, 1fr) minmax(6rem, 0.7fr) minmax(6rem, 0.7fr) minmax(10rem, 1.7fr);
  gap: var(--space-3);
  align-items: baseline;
  padding: var(--space-3) var(--card-padding) var(--space-3) var(--space-8);
  position: relative;
  cursor: pointer;
  list-style: none;
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
}
.dayrow__summary::-webkit-details-marker { display: none; }

/* Sieht aus wie eine Tabellenkopfzeile und ist keine: Ein <details> darf
   nicht in einem <tbody> stehen (siehe die Begruendung in der Vorlage), also
   uebernimmt ein Raster die Ausrichtung. Die Optik folgt trotzdem `thead th`,
   damit die Liste nicht wie ein Fremdkoerper neben den echten Tabellen der
   Oberflaeche steht. */
.dayrow__head {
  background: var(--surface-sunken);
  border-bottom: 1px solid var(--border);
  color: var(--text-label);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.dayrow__summary:hover { background: var(--surface-hover); }

/* Das Dreieck sitzt in der linken Polsterung, damit die Spalten aller Zeilen
   auf einer Kante beginnen - mit einem Marker im Textfluss waere die erste
   Spalte je nach Zustand verschoben. */
.dayrow__summary::before {
  content: "";
  position: absolute;
  left: var(--space-3);
  top: calc(var(--space-3) + 0.45em);
  width: 6px;
  height: 6px;
  border-right: 2px solid var(--text-muted);
  border-bottom: 2px solid var(--text-muted);
  transform: rotate(-45deg);
  transition: transform 120ms ease;
}
.dayrow[open] > .dayrow__summary::before { transform: rotate(45deg); }
.dayrow[open] > .dayrow__summary { background: var(--surface-sunken); }

.dayrow__date { color: var(--text-strong); font-weight: var(--weight-medium); }
.dayrow__count { font-variant-numeric: tabular-nums; }
.dayrow__none { color: var(--text-muted); }

/* Der Wochentag als Beigabe zum Datum - EINE Klasse fuer beide Listen, die
   Theorietermine zeigen (diese hier und die Tabelle auf der Startseite).

   DIE REIHENFOLGE IST DATUM ZUERST, und das ist kein Geschmack: In einer nach
   Datum sortierten Liste fluchten die Datumsangaben dann untereinander und
   man liest die Spalte hinunter. Vorangestellt schoeben die Wochentage sie
   auseinander - "Dienstag," und "Donnerstag," sind verschieden lang, und das
   Datum begaenne in jeder Zeile woanders. Die Startseite machte es
   anderthalb Versionen lang genau so.

   Nicht .hint: Das ist eine Klasse fuer erklaerende Absaetze und bringt eine
   max-width fuer Fliesstext mit, die hier nichts zu suchen hat. Sie stand
   vorher hier und musste an Ort und Stelle wieder zurechtgebogen werden. */
.weekday-note {
  margin-left: var(--space-1);
  color: var(--text-muted);
  font-weight: var(--weight-normal);
}

/* Der Standort unter dem Datum, seit ein Kalendertag an mehreren Orten
   stattfinden kann.

   EINE EIGENE ZEILE UND KEIN ANHANG AN DEN WOCHENTAG: In der schmalen
   Datumsspalte brach "Dienstag · Zweigstelle Nord" mitten im Satz um - gemessen
   bei 1440px, die Zeile wurde dadurch hoeher als alle anderen und der Punkt
   blieb am Ende der ersten Zeile haengen. Der Umbruch ist jetzt gewollt und
   sitzt an der richtigen Stelle.

   Nur dort, wo ein Ort steht - fuer Fahrschulen ohne Standorte aendert sich
   an der Zeilenhoehe nichts. */
/* DIE ORTSSPALTE. `.dayrow__ort` stand hier frueher fuer den Ort als
   Anhaengsel neben dem Wochentag - seit der Standort eine eigene Spalte hat,
   waere das eine tote Regel, und tote Regeln erklaeren spaeter niemandem
   mehr, warum sie da sind. */
.dayrow__loc {
  min-width: 0;
  overflow-wrap: anywhere;
}

.dayrow__body {
  padding: var(--space-4) var(--card-padding) var(--space-6) var(--space-8);
  background: var(--surface-sunken);
  border-top: 1px solid var(--border);
}
.dayrow__body table { background: var(--surface); }
.dayrow__empty { color: var(--text-muted); font-size: var(--text-sm); }
.dayrow__more { margin: var(--space-4) 0 0; }

/* Auf kleinen Schirmen untereinander - fuenf Spalten auf einem Telefon waeren
   fuenf Streifen. */
@media (max-width: 900px) {
  .dayrow__summary { grid-template-columns: 1fr; gap: var(--space-1); }
  /* Die Kopfzeile faellt weg, sobald die Spalten untereinander stehen: Fuenf
     Ueberschriften ueber einem einspaltigen Stapel beschriften nichts mehr.
     `display: none` und nicht `visibility: hidden` - sie soll auch aus der
     Vorlesereihenfolge verschwinden, weil sie dort dasselbe Problem hat. */
  .dayrow__head { display: none; }
}

/* --- Banner / Hinweise --------------------------------------------------
   Eine Komponente fuer alle vier Typen. Icon plus Flaeche plus Textfarbe -
   nicht nur eingefaerbter Text, damit die Aussage nicht ausschliesslich an
   der Farbe haengt (WCAG 1.4.1). */
.banner {
  display: flex;
  /* DIE AKTION DARF AUF EINE EIGENE ZEILE. Ohne das zog "Bestaetigungsmail
     erneut senden" jede Seite hinter der Anmeldung auf 403px auf: Die Aktion
     war starr (`flex: 0 0 auto`), ihr Knopftext brach nicht um, und damit
     hatte das Banner eine Mindestbreite, die kein Handy hat. Gemessen auf
     390, 360 und 320px - SECURITY.md Punkt 99, zweite Ursache neben dem
     frueheren Fuss der Seitenleiste. */
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: flex-start;
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-6);
  border: 1px solid var(--info-border);
  border-radius: var(--radius);
  background: var(--info-bg);
  color: var(--info-text);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
}
.banner svg {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  margin-top: 1px;
  color: var(--info-icon);
}
/* DIE BASIS IST KLEINER ALS DER TEXT, und das ist der Teil, der den Umbruch
   steuert: Mit `auto` waere die Basis die volle Textbreite, und bei
   erlaubtem Umbruch (siehe .banner) laege die Aktion IMMER auf einer eigenen
   Zeile - auch auf einem 1440px-Schirm, wo reichlich Platz ist. Mit 18rem
   schrumpft der Text zuerst; erst wenn auch das nicht mehr reicht, rueckt
   die Aktion eine Zeile tiefer. Gemessen auf 1440, 1100, 900, 768, 390, 360
   und 320px. */
.banner__text { flex: 1 1 18rem; min-width: 0; margin: 0; }
.banner__text strong { color: inherit; font-weight: var(--weight-semibold); }
/* Schrumpfen erlaubt, aber erst nach dem Text (`flex-basis: auto`, kein
   Wachsen): Auf breiten Schirmen steht die Aktion wie bisher rechts neben
   dem Text, auf schmalen bricht sie um und ihr eigener Text mit ihr.
   `min-width: 0` ist der Teil, den man vergisst - ohne ihn bleibt die
   Mindestbreite eines Flex-Elements sein Inhalt, und dann hilft das
   Umbrechen der Zeile nichts. */
.banner__action { flex: 0 1 auto; min-width: 0; margin: 0; }
.banner__action .linkish { color: inherit; }

.banner--ok {
  background: var(--success-bg);
  border-color: var(--success-border);
  color: var(--success-text);
}
.banner--ok svg { color: var(--success-icon); }

/* HINWEIS - WEISS MIT LEISTE, nicht mehr bernsteingelb getoent.

   WAS SICH AENDERT UND WARUM: Die getoente Flaeche plus gelber Rahmen war
   der lauteste Baustein der Oberflaeche; auf der Startseite standen bis zu
   drei davon untereinander, und die Meldung, die wirklich draengt (der
   Fehler daneben), ging darin unter. Jetzt traegt die Farbe nur noch die
   Leiste, das Symbol und die fette Einleitung - der Fliesstext steht in
   --text und ist damit besser lesbar als zuvor (14.63:1 statt 6.84:1).

   DAS SYMBOL WIRD DABEI KRAEFTIGER, nicht leiser: --warning-icon zeigt auf
   --amber-700 und erreicht auf Weiss 5.02:1 statt der frueheren 3.07:1 auf
   der Toenung.

   NICHT UEBERNOMMEN: Ein Orange-Ton haette moderner gewirkt, liegt aber nur
   17 Grad neben der Gefahrenfarbe (Bernstein: 29 Grad). Warnung und Fehler
   stehen auf der Startseite nebeneinander - sie muessen unterscheidbar
   bleiben. Gemessen in tests/test_design_tokens.py.

   BADGE UND TOUR-HINWEIS BLEIBEN GETOENT (weiter unten): Eine Pille ohne
   Flaeche waere keine Pille, und im kleinen Format traegt eine Leiste
   nichts. */
.banner--warn {
  background: var(--surface);
  border-color: var(--border);
  border-left: 3px solid var(--warning-accent);
  color: var(--text);
}
.banner--warn .banner__text strong { color: var(--warning-strong); }
.banner--warn svg { color: var(--warning-icon); }

.banner--error {
  background: var(--danger-bg);
  border-color: var(--danger-border);
  color: var(--danger-text);
}
.banner--error svg { color: var(--danger-icon); }

/* --- Badge -------------------------------------------------------------- */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 2px var(--space-2);
  border-radius: var(--radius-full);
  background: var(--info-bg);
  border: 1px solid var(--info-border);
  color: var(--info-text);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
  font-weight: var(--weight-medium);
  white-space: nowrap;
}
/* Die Fuehrerscheinklasse. Dieselben Farben wie .badge, nur mit kraeftigerem
   Rand und Gewicht: Sie ist das Unterscheidende zwischen zwei gleichnamigen
   Paketen und soll sich vom Zustandsabzeichen daneben abheben, ohne eine
   eigene Bedeutungsfarbe zu beanspruchen. */
.badge--klasse {
  border-color: var(--info-text);
  font-weight: var(--weight-bold);
}
.badge--ok {
  background: var(--success-bg);
  border-color: var(--success-border);
  color: var(--success-text);
}
.badge--warn {
  background: var(--warning-bg);
  border-color: var(--warning-border);
  color: var(--warning-text);
}
.badge--error {
  background: var(--danger-bg);
  border-color: var(--danger-border);
  color: var(--danger-text);
}
/* "Auf Anfrage" in der Leistungs- und Paketliste (SECURITY.md Kapitel 31).

   KEINE ZUSTANDSFARBE. Gruen, Bernstein und Rot sagen in dieser Tabelle
   nichts - aber sie wuerden hier eine Wertung andeuten ("Achtung" oder "in
   Ordnung"), und eine Preisentscheidung ist weder das eine noch das andere.
   Es ist ein neutraler Hinweis, und deshalb traegt er die neutralen
   Flaechen- und Randtokens. */
.badge--quiet {
  background: var(--surface-sunken);
  border-color: var(--border);
  color: var(--text-muted);
}
/* DIE ART EINES THEORIETERMINS - keine Zustandsfarbe. Gruen, Bernstein und
   Rot sagen in derselben Zeile schon "frei", "Anfrage offen" und "abgesagt";
   eine Terminart darf keine davon tragen. Beide Paare sind vorhandene Tokens:
   das Blau des ausgewaehlten Tages (.daypick__item, 6.60:1 Text auf Flaeche)
   und das Tuerkis-Trio der Kalenderfarben (tokens.css, --cal-callback-*).
   Keine eigenen Farbwerte. */
.badge--kurs {
  background: var(--action-subtle);
  border-color: var(--action);
  color: var(--action-on-subtle);
}
.badge--zusatz {
  background: var(--cal-callback-bg);
  border-color: var(--cal-callback-edge);
  color: var(--cal-callback-text);
}

/* --- Leerzustand -------------------------------------------------------
   Kein blosser Satz: Symbol, Erklaerung, naechster Schritt. Ein Leerzustand
   ist die erste Seite, die ein neuer Nutzer sieht - wenn dort nur "keine
   Eintraege" steht, ist die Oberflaeche an ihrer wichtigsten Stelle stumm. */
.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: var(--space-12) var(--space-6);
}
.empty__icon {
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  margin-bottom: var(--space-4);
  /* --radius UND NICHT --radius-lg, und das ist eine Korrektur: Dieser
     Kasten stand auf --radius-lg und ist damit von 10 auf 12px
     mitgewandert, als der Kartenradius stieg. Auf 48x48 ist das
     proportional ein VIEL groesserer Bogen als 12px auf einer 270px hohen
     Karte - nebeneinander fotografiert wirkte das Symbolkaestchen runder
     als die Karte, in der es steht.

     DIE REGEL DAHINTER steht bei den Radien in tokens.css: Ein Kasten IN
     einer Karte traegt einen kleineren Bogen als sie. */
  border-radius: var(--radius);
  background: var(--surface-sunken);
  border: 1px solid var(--border);
  color: var(--icon-muted);
}
.empty__icon svg { width: 24px; height: 24px; }
.empty__title {
  margin: 0 0 var(--space-2);
  font-size: var(--text-lg);
  line-height: var(--text-lg-lh);
  font-weight: var(--weight-semibold);
  color: var(--text-strong);
}
.empty__text {
  max-width: 32rem;
  margin: 0 0 var(--space-6);
  color: var(--text-muted);
  font-size: var(--text-sm);
  line-height: var(--text-base-lh);
}

/* --- Nur fuer Screenreader ----------------------------------------------
   Text, der gelesen und nicht gesehen wird. `display: none` und
   `visibility: hidden` taugen dafuer NICHT - beide nehmen den Text auch der
   Vorlesefunktion weg. Der Knoten bleibt deshalb im Layout, nur auf einem
   Pixel ohne Ausdehnung. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --- Tab-Leiste EINES Bereichs ------------------------------------------
   Nicht zu verwechseln mit der frueheren obersten Tab-Leiste, die durch die
   Seitenleiste ersetzt wurde (siehe admin/_layout.html). Diese hier gliedert
   einen Bereich in wenige, feststehende Ansichten und waechst nicht mit.

   Der aktive Zustand traegt Textfarbe UND Unterkante - Farbe allein waere
   fuer farbfehlsichtige Nutzer kein Unterschied (WCAG 1.4.1). Dieselbe
   Entscheidung wie beim Navigationseintrag mit seinem Balken. */
.tabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  margin-bottom: var(--section-gap);
  border-bottom: 1px solid var(--border);
}
.tab {
  padding: var(--space-3) var(--space-4);
  margin-bottom: -1px;
  border-bottom: 2px solid transparent;
  color: var(--text-muted);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
  font-weight: var(--weight-medium);
}
.tab:hover { color: var(--text-strong); text-decoration: none; }
.tab[aria-current="page"] {
  border-bottom-color: var(--action);
  color: var(--action-text);
  font-weight: var(--weight-semibold);
}

/* Umschalter zwischen zwei gleichrangigen Gegenstaenden (Beratung/Rueckruf).
   Segmentiert und nicht als Tab-Leiste: Eine zweite Leiste unmittelbar unter
   der ersten laese sich als deren Fortsetzung, und dann waeren "Regeln" und
   "Rückrufe" scheinbar dasselbe. */
.switch {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 2px;
  margin-bottom: var(--space-6);
  padding: 2px;
  background: var(--surface-sunken);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.switch__item {
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
  font-weight: var(--weight-medium);
}
.switch__item:hover { color: var(--text-strong); text-decoration: none; }
.switch__item[aria-current="page"] {
  background: var(--surface);
  border: 1px solid var(--border-strong);
  padding: calc(var(--space-2) - 1px) calc(var(--space-4) - 1px);
  color: var(--text-strong);
  font-weight: var(--weight-semibold);
}

/* --- Zwei gleichrangige Spalten (Startseite) ----------------------------
   Zwei Vorschauen nebeneinander, untereinander sobald es eng wird. `1fr 1fr`
   und nicht `auto auto`: Die beiden sollen gleich breit bleiben, auch wenn
   in der einen Woche mehr steht als in der anderen - sonst waere die Breite
   eine Aussage ueber die Auslastung, die niemand so gemeint hat. */
.duo {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--card-gap);
  margin-bottom: var(--section-gap);
}
@media (max-width: 1100px) {
  .duo { grid-template-columns: 1fr; }

  /* DIE ZELLPOLSTERUNG GEHT HIER ZURUECK AUF DEN ALTEN WERT (16px).

     WARUM BEI 1100 UND NICHT BEI 900, wo die Seitenleiste umbricht: Der
     Schwellenwert ist eine Aussage ueber die verfuegbare TABELLENBREITE, und
     die ist gemessen. Die Leistungsliste (sechs Spalten, darunter ein
     Freitextfeld) lief auf 1100 Punkten schon vorher 43px ueber; mit 24px
     Zellrand waeren es 139 geworden. Auf 900 waere die Umschaltung also zu
     spaet gekommen - sie haette die Verschlechterung genau auf der Breite
     stehen gelassen, auf der die meisten Laptops arbeiten.

     Gemessen nach der Umstellung: 1440 kein Ueberlauf, 1100/900/768 genau
     die Werte von vor dieser Aenderung. Der grosszuegige Zellrand gilt
     damit dort, wo Platz dafuer ist, und nirgends sonst.

     Als Token und nicht als Regel je Tabelle - sonst waere es die zweite
     Stelle, an der eine Zahl steht, die zusammenpassen muss. */
  :root { --cell-padding-x: var(--cell-padding-x-narrow); }
}

/* EINE KARTE ALLEIN NIMMT DIE GANZE BREITE.

   Seit die Startseite je Ausschnitt prueft, kann genau eine Vorschau uebrig
   bleiben - ein Mitarbeiter, dem nur die Rueckrufe zugewiesen sind, sah eine
   halbbreite Karte und daneben nichts. Das sah nicht nach "hier steht nur
   eines", sondern nach "hier fehlt etwas".

   ALS MODIFIKATOR AUS DEM TEMPLATE UND NICHT ALS `:has(> :only-child)`. Die
   Bedingung waere kuerzer, aber `:has` kommt im Projekt bisher nirgends vor;
   eine neue Selektorart fuer einen Sonderfall einzufuehren heisst, sie
   kuenftig ueberall mitdenken zu muessen. Das Template kennt die Zahl
   ohnehin - es baut die Liste.

   DIE ZWEISPALTIGE REGEL BLEIBT UNANGETASTET: Wer beide Kalender sieht,
   bekommt exakt dasselbe wie vorher. Diese Regel greift nur, wenn das
   Template die Klasse setzt. */
.duo--einzeln { grid-template-columns: 1fr; }

/* --- Wochenvorschau (nur Anschauen) -------------------------------------
   SIEBEN SPALTEN WIE IM ARBEITSRASTER, nur ohne Zeitachse. Untereinander war
   es eine Liste, die man Zeile fuer Zeile liest; nebeneinander ist es eine
   Woche, die man auf einen Blick sieht - und genau das soll eine Startseite
   leisten.

   Warum trotzdem nicht das Raster selbst: Das traegt eine Zeitachse und
   braucht dadurch 56rem; zwei davon nebeneinander passen auf keinen
   Bildschirm. Die Vorschau stapelt die Termine eines Tages einfach
   untereinander - bei zwei, drei Terminen am Tag ist die Uhrzeit im Kaestchen
   Information genug. */
.preview__days {
  /* Bezugspunkt fuer die Jetzt-Linie, die ueber alle sieben Spalten geht. */
  position: relative;
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  list-style: none;
  margin: 0;
  padding: 0;
}
.preview__day {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
  padding: var(--space-3) var(--space-2);
  border-left: 1px solid var(--border);
}
.preview__day:first-child { border-left: 0; }
/* Heute bekommt eine Flaeche - in einer Reihe aus sieben gleichen Spalten
   ist "die erste" keine Auskunft, die Markierung ist es. */
.preview__day--today { background: var(--action-subtle); }
.preview__day--today .preview__weekday { color: var(--action-on-subtle); }
.preview__day--today .preview__date { color: var(--text-muted-on-subtle); }

/* Bereits vergangene Tage der laufenden Woche.

   NUR DER SPALTENKOPF WIRD ZURUECKGENOMMEN, nicht die Termine darin. Ein
   Termin, der stattgefunden hat, ist weiterhin eine Auskunft - wer am Sonntag
   auf die Woche sieht, will die zwoelf Montagstermine lesen koennen und nicht
   erraten. Zurueckgenommen wird deshalb, was nur der Orientierung dient: der
   Wochentag.

   KEINE DECKKRAFT, SONDERN EIN EIGENER FARBTON. `opacity` liesse sich nicht
   nachrechnen - der Wert haengt davon ab, was darunter liegt. --text-muted
   erreicht auf --surface gemessene 4.76:1 und bleibt damit Fliesstext, den
   man lesen kann, statt eines Grauschleiers. */
.preview__day--past .preview__weekday { color: var(--text-muted); }

.preview__date {
  color: var(--text-muted);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
  text-align: center;
}
.preview__weekday {
  display: block;
  color: var(--text-strong);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
  font-weight: var(--weight-semibold);
  /* "Heute" und "Morgen" sind laenger als "Di" - in einer 1fr-Spalte darf das
     die Spalte nicht aufziehen. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.preview__daynum { display: block; }

.preview__entries {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
  /* MEHR HOEHE, DAMIT DREI TERMINE NEBENEINANDER PASSEN, ohne dass die
     Spalte gedraengt wirkt. Gemessen: Ein Kaestchen ist 62px hoch, drei
     davon mit Fugen 194px - bei 208px verfuegbarer Hoehe klebte das an der
     Grenze. 320px tragen vier Termine samt Jetzt-Linie.

     `min-height` und nicht `height`: Eine Spalte mit sechs Terminen darf
     weiterwachsen, statt zu ueberlaufen.

     Der Wert steht als Token in tokens.css, weil das Wochenraster ihn als
     Mindesthoehe seines Zeilenbereichs mitbenutzt. */
  min-height: var(--preview-entries-height);
}
/* Der Tag ohne Termine. Ein Gedankenstrich OBEN und nicht in der Mitte:
   Bei 320px hohen Spalten schwebte ein zentriertes Zeichen im Nichts und sah
   aus, als fehle etwas. Oben stehen die Striche aller leeren Tage auf einer
   Linie mit dem ersten Termin der belegten Tage - das liest sich als
   "hier ist nichts" und nicht als "hier ist etwas kaputt". */
.preview__free {
  align-self: center;
  color: var(--text-muted);
  font-size: var(--text-sm);
  text-align: center;
}

/* --- Die Jetzt-Linie ----------------------------------------------------
   Zwei Auspraegungen, eine Gestalt. Wo sie sitzt und warum sie in der
   Vorschau anders gerechnet wird als im Wochenraster, steht in
   static/js/now-line.js.

   ABSOLUT POSITIONIERT, weil sie ueber die ganze Breite geht und damit ueber
   Spalten hinweg, die sie nicht enthaelt. Die Hoehe setzt das Skript ueber
   ein Constructable Stylesheet - Inline-Stile waeren durch die CSP gesperrt.

   IN DER AKTIONSFARBE. Hier stand vorher Rot, mit der Begruendung, die Linie
   duerfe nicht mit einem Termin verwechselt werden - Beratung ist violett,
   Rueckruf blaugruen. Das Argument war richtig und die Umsetzung zu laut: Ein
   zwei Pixel breiter roter Balken quer durch die Woche las sich wie eine
   Warnung. Eine Haarlinie in der Farbe, die die Oberflaeche ohnehin fuehrt,
   ist als Markierung erkennbar, ohne sich vorzudraengen.

   1px statt 2. Gegen die getoente Flaeche der heutigen Spalte
   (--action-subtle) erreicht --action 4.26:1 und bleibt damit deutlich ueber
   den 3:1 aus WCAG 1.4.11. */
.nowline {
  position: absolute;
  left: 0;
  right: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  height: 0;
  border-top: 1px solid var(--action);
  /* Sie liegt UEBER den Terminen und darf sie nicht anklickbar machen. */
  pointer-events: none;
}
/* Im Wochenraster steht die Breite am Element (so breit wie die Tabelle,
   damit die Linie beim waagerechten Scrollen mitlaeuft) - dort darf `right`
   nicht mitreden. */
.nowline--raster { right: auto; }

/* IM RASTER UEBERNIMMT DIE UHRZEIT DIE ZEITACHSE, statt sich davorzulegen.
   "Ganz links" ist dort die Spalte, in der ohnehin Uhrzeiten stehen - eine
   Pille davor ueberdeckte die Zeilenbeschriftung nur halb, und aus "09:00"
   wurde ")9:00".

   Dieselbe Breite, dieselbe Ausrichtung und dasselbe Polster wie
   .weekgrid__time, damit die Uhrzeit auf derselben Kante steht wie die
   Zeilenbeschriftungen darueber und darunter.

   DIE BESCHRIFTUNG DER EIGENEN ZEILE WIRD AUSGEBLENDET, nicht ueberdeckt.
   Ueberdecken war der erste Versuch: eine weisse Flaeche hinter der Uhrzeit.
   Das ging schief, weil die Linie irgendwo IN der Zeile sitzt und die
   Beschriftung an deren Oberkante - die Flaeche schnitt "09:00" waagerecht
   durch. Welche Zeile es trifft, sagt das Skript ueber eine Regel im
   erzeugten Stylesheet; `visibility` und nicht `display`, damit die
   Spaltenbreite erhalten bleibt. */
.nowline--raster .nowline__zeit {
  width: 5.5rem;
  padding: 0 var(--space-3) 0 0;
  border-radius: 0;
  /* DECKEND, damit die Linie nicht durch die Ziffern laeuft - ohne Grund
     stand ein Strich mitten durch "09:45". Der frueher befuerchtete Schaden
     tritt nicht mehr ein: Die Beschriftung dieser Zeile ist ohnehin
     ausgeblendet, es gibt hier nichts zu verdecken. Nebenbei beginnt die
     Linie damit erst am Raster statt schon in der Zeitachse. */
  background: var(--surface);
  color: var(--action-text);
  text-align: right;
}
.nowline__zeit {
  /* GANZ LINKS, am Anfang der Zeile. Sie stand ueber der heutigen Spalte, was
     zusaetzlich zeigte, welcher Tag heute ist - das sagt die Spalte aber
     ohnehin selbst durch ihre Toenung. Links steht die Uhrzeit dort, wo im
     Wochenraster die Zeitachse laeuft, und liegt nicht mitten im Raster.

     KEIN `translateY(-50%)`. Es stand hier, um die Marke auf die Kante zu
     heben - `align-items: center` auf der null Pixel hohen Linie tut das
     aber bereits. Beides zusammen verschob sie um die GANZE Hoehe statt um
     die halbe: gemessene 14px ueber der Linie statt 7,5. Sichtbar wurde es,
     weil sie im Spaltenkopf das Datum ueberdeckte. */
  padding: 1px var(--space-1);
  border-radius: var(--radius-sm);
  background: var(--action);
  color: var(--text-on-action);
  font-size: 0.6875rem;
  line-height: 1.2;
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}


/* Der Hinweis unter einem leeren Raster. Er ersetzt es nicht - sonst waere
   der leere Kasten hoeher als der gefuellte daneben. */
.preview__note {
  margin: 0;
  padding: var(--space-3) var(--card-padding);
  border-top: 1px solid var(--border);
  color: var(--text-muted);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
}

/* GLEICHE HOEHE, UNABHAENGIG VOM FUELLSTAND. Grid streckt seine Kinder von
   sich aus auf die Hoehe der Reihe - damit sind beide Karten gleich hoch. Was
   fehlte, war die Verteilung INNERHALB der Karte: Ohne sie sass die Fusszeile
   der kuerzeren Karte in der Mitte statt unten. */
.duo > .card { display: flex; flex-direction: column; }
.duo > .card > .preview__days { flex: 1; }

/* Ein Termin in der Vorschau. Kein Link: Die Startseite verwaltet nicht.
   Deshalb auch keine Hover-Farbe - was nicht klickt, soll nicht so aussehen. */
.chip {
  display: block;
  min-width: 0;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  border-left: 3px solid transparent;
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
}
.chip strong { display: block; font-variant-numeric: tabular-nums; }
/* Der Name darf die Spalte nicht sprengen; abgeschnitten mit Auslassung ist
   besser als umgebrochen - sieben Spalten mit unterschiedlich hohen
   Kaestchen laesst sich nicht mehr vergleichen. Der volle Name steht in der
   Liste des Bereichs. */
.chip__name {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.chip__state { display: block; font-weight: var(--weight-medium); }

/* DER STANDORT AM TERMIN - dezent, unter dem Namen.
   Dieselbe gedaempfte Farbe wie jede andere Nebenangabe der Oberflaeche
   (--text-muted) und eine Stufe kleiner als der Name: Er beantwortet die
   Frage "wo", die man nur stellt, wenn es mehrere Orte gibt - und draengt
   sich sonst nicht vor den Namen, der die Hauptsache ist. */
.chip__ort {
  display: block;
  color: var(--text-muted);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chip--beratung {
  background: var(--cal-consultation-bg);
  border-left-color: var(--cal-consultation-edge);
  color: var(--cal-consultation-text);
}
.chip--rueckruf {
  background: var(--cal-callback-bg);
  border-left-color: var(--cal-callback-edge);
  color: var(--cal-callback-text);
}
/* Die uebrigen Einzeltermin-Kalender (SECURITY.md Kapitel 37) - im
   persoenlichen Kalender stehen sie gemischt neben den beiden oberen. */
.chip--online {
  background: var(--cal-online-bg);
  border-left-color: var(--cal-online-edge);
  color: var(--cal-online-text);
}
.chip--probetraining,
.chip--besichtigung {
  background: var(--cal-branche-bg);
  border-left-color: var(--cal-branche-edge);
  color: var(--cal-branche-text);
}
/* Im persoenlichen Kalender IST das Kaestchen ein Link - anders als auf der
   Startseite. Unterstrichen beim Ueberfahren, wie jeder Link. */
.chip--link:hover .chip__name { text-decoration: underline; }
.chip--link:hover { text-decoration: none; }
/* Der Kalendername im Kaestchen: die Farbe ist nie die einzige Auskunft. */
.chip__art {
  display: block;
  font-weight: var(--weight-semibold);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Das Kuerzel der zustaendigen Person (Kapitel 37) - auf der Startseite, im
   Raster und auf der Terminseite dieselbe runde Marke. */
.chip__zugewiesen,
.appt__zugewiesen,
.kuerzel {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.6em;
  padding: 0 var(--space-1);
  border-radius: var(--radius-full);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  color: var(--text);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
  font-weight: var(--weight-semibold);
}
.chip__zugewiesen { margin-top: var(--space-1); }
.kuerzel { margin-right: var(--space-1); }
.chip--cancelled {
  background: var(--surface-sunken);
  border-left-color: var(--border-strong);
  color: var(--text-muted);
}
.chip--cancelled .chip__name { text-decoration: line-through; }

/* Unter 900px wird aus dem Raster wieder eine Liste: Sieben Spalten auf einem
   Telefon waeren sieben Streifen von je 40 Pixeln. Dann steht das Datum
   wieder links neben den Terminen. */
@media (max-width: 900px) {
  .preview__days { grid-template-columns: 1fr; }
  .preview__day {
    display: grid;
    grid-template-columns: 5rem 1fr;
    align-items: baseline;
    border-left: 0;
    border-top: 1px solid var(--border);
    padding: var(--space-3) var(--card-padding);
  }
  .preview__day:first-child { border-top: 0; }
  .preview__date { text-align: left; }
  .preview__free { text-align: left; }
}

/* --- Tagesauswahl vor der Uhrzeitliste ----------------------------------
   Eine Reihe aus Links, kein zweites Auswahlfeld: Das braeuchte JavaScript,
   um die Uhrzeiten nachzuladen. Die Links sind gewoehnliche GETs mit `?tag=`
   und funktionieren ohne Skript.

   `aria-current="true"` und nicht "page": Der aktive Tag ist eine Auswahl
   innerhalb dieser Seite, nicht eine andere Seite. */
.daypick__label {
  display: block;
  margin-bottom: var(--space-1);
  color: var(--text);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
  font-weight: var(--weight-medium);
}
.daypick {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}
.daypick__item {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-full);
  color: var(--text);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
  white-space: nowrap;
}
.daypick__item:hover {
  background: var(--surface-hover);
  border-color: var(--border-hover);
  text-decoration: none;
}
.daypick__item[aria-current="true"] {
  background: var(--action-subtle);
  /* Rand in --action (Flaeche, 3:1 genuegt), Text in --action-on-subtle. */
  border-color: var(--action);
  color: var(--action-on-subtle);
  font-weight: var(--weight-semibold);
}
/* Die Zahl freier Zeiten je Tag - sie beantwortet die Frage, an welchem Tag
   ueberhaupt noch etwas geht, bevor man ihn anklickt. */
.daypick__count {
  color: var(--text-muted);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
}
.daypick__item[aria-current="true"] .daypick__count { color: var(--action-on-subtle); }

/* --- Der Kalender-Baustein (admin/_kalender.html) ------------------------
   EINE Komponente fuer "Beratungstermin eintragen", "Rueckruf eintragen" und
   "Zusatztermin anlegen". Alles hier haengt am Praefix `.kalender` und wirkt
   nirgends sonst; Farben, Abstaende und Radien sind ausschliesslich Tokens.

   KOMPAKT: ein Textfeld in der Breite eines Datumsfelds (--field-max-sm) mit
   einem Kalendersymbol am rechten Rand. Der Kalender ist ein Aufklappfenster
   darunter - absolut positioniert, er schiebt also nichts auf der Seite weg.
   `.kalender--oben` (gesetzt von kalender.js) klappt es nach oben, wenn unten
   im Fenster kein Platz ist.

   DREI ZUSTAENDE EINES TAGES, DREI MERKMALE - nie nur die Farbe:
     waehlbar       Rahmen in Markenblau   (--action, 4.77:1 - ueber den 3:1
                    fuer Bedienelemente) + dunkler Text
     gewaehlt       volle Aktionsflaeche   (--text-on-action auf --action 4.77:1)
     nicht waehlbar kein Rahmen, leiser Text, kein Zeiger
   "Heute" ist ein Punkt unter der Zahl und kombinierbar mit jedem Zustand.
   Gewaehlt heisst im Link-Modus aria-current="date" (der Server kennt
   `?tag=`), im Feld-Modus aria-pressed="true" (kalender.js setzt es beim
   Klick und beim Tippen um) - dasselbe Attribut, das Screenreader lesen. */
.kalender__legende {
  display: block;
  margin-bottom: var(--space-1);
  color: var(--text);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
  font-weight: var(--weight-medium);
}
.kalender__feld {
  position: relative;
  max-width: var(--field-max-sm);
}
/* Die allgemeine Feldregel gibt Rahmen, Hoehe und Fokus; hier nur der Platz
   fuer das Symbol und Ziffern gleicher Breite. */
.kalender__eingabe {
  padding-right: var(--control-height);
  font-variant-numeric: tabular-nums;
}
.kalender__oeffnen {
  position: absolute;
  top: 0;
  right: 0;
  width: var(--control-height);
  min-height: var(--control-height);
  height: var(--control-height);
  padding: 0;
  border: 0;
  background: none;
  color: var(--icon-muted);
}
.kalender__oeffnen:hover,
.kalender__oeffnen:active,
.kalender__oeffnen[aria-expanded="true"] {
  background: none;
  color: var(--action-text);
}
.kalender__oeffnen svg { width: 18px; height: 18px; }

.kalender__popup {
  position: absolute;
  z-index: 20;   /* ueber dem Formular, unter der Abmeldewarnung (40/50) */
  top: calc(100% + var(--space-1));
  left: 0;
  width: 18.5rem;
  max-width: calc(100vw - 2 * var(--space-4));
  padding: var(--space-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}
.kalender--oben .kalender__popup {
  top: auto;
  bottom: calc(100% + var(--space-1));
}

/* Die Pfeile liegen auf der Hoehe der Monatsueberschrift, links und rechts
   davon - eine Zeile statt zwei. */
.kalender__blaettern {
  position: absolute;
  top: var(--space-3);
  left: var(--space-3);
  right: var(--space-3);
  display: flex;
  justify-content: space-between;
  pointer-events: none;
}
.kalender__pfeil {
  pointer-events: auto;
  width: 2rem;
  min-height: 2rem;
  height: 2rem;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: none;
  color: var(--text);
  font-size: var(--text-lg);
  line-height: 1;
  font-weight: var(--weight-medium);
}
.kalender__pfeil:hover {
  background: var(--surface-hover);
  border-color: var(--border);
  color: var(--text);
}
.kalender__pfeil:active { background: var(--surface-raised); }
.kalender__pfeil:disabled {
  background: none;
  border-color: transparent;
  color: var(--text-placeholder);
  opacity: 0.5;
}

/* DIE TABELLE IST EIN MONATSBLATT, KEINE DATENTABELLE. Die allgemeinen
   Tabellenregeln (Zellpolsterung 24px, Kopf in Grossbuchstaben, Zeilen-Hover,
   Trennlinien) sind fuer Listen gedacht und werden hier zurueckgenommen -
   mit hoeherer Spezifitaet, nicht mit !important. */
.kalender__monat {
  width: 100%;
  border-collapse: separate;
  border-spacing: 2px;
  table-layout: fixed;
}
.kalender__titel {
  padding: 0 0 var(--space-2);
  min-height: 2rem;
  line-height: 2rem;
  color: var(--text-strong);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  text-align: center;
}
.kalender__monat thead th {
  padding: 0 0 var(--space-1);
  background: none;
  border: 0;
  color: var(--text-label);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
  font-weight: var(--weight-medium);
  text-transform: none;
  letter-spacing: 0;
  text-align: center;
}
.kalender__monat tbody td {
  padding: 0;
  border: 0;
  vertical-align: middle;
  text-align: center;
}
.kalender__monat tbody tr:hover { background: none; }

.kalender__tag {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0;
  width: 100%;
  /* 36px: gross genug fuer den Finger (WCAG 2.5.8 verlangt 24px), und
     sieben davon passen in das Fenster von 18.5rem. */
  min-height: 2.25rem;
  margin: 0;
  padding: 0;
  border: 1px solid var(--border-control);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  font-size: var(--text-sm);
  line-height: 1.1;
  font-weight: var(--weight-medium);
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  /* `color` GEHOERT MIT IN DEN UEBERGANG. Ohne es springt die Schrift beim
     Waehlen sofort auf --text-on-action (weiss), waehrend die Flaeche 120ms
     braucht, um blau zu werden - weisse Schrift auf noch weissem Grund, der
     Tag sieht fuer einen Moment LEER aus.

     NUR IM FELD-MODUS SICHTBAR (Theorietag, Kurstag, Termin verschieben):
     Dort setzt kalender.js `aria-pressed` per Klick. Im Link-Modus
     (Beratung, Rueckruf) steht `aria-current="date"` schon beim Rendern, da
     laeuft gar kein Uebergang - deshalb ist es nie jemandem aufgefallen.

     Derselbe Fehler steckte im Zeit-Baustein (.zeit__wert) und wurde dort am
     18.9.2026 behoben; hier ist er der letzte seiner Art. */
  transition: background-color 120ms ease, border-color 120ms ease,
              color 120ms ease;
}
/* "Hier gibt es freie Termine": der Rand in der Markenfarbe - dieselbe
   Variable wie Knoepfe, Links und Fokusring (tokens.css), kein eigener
   Wert. Nur waehlbare Tage sind Link oder Knopf; ein nicht waehlbarer ist
   ein <span> und bleibt davon unberuehrt. */
a.kalender__tag,
button.kalender__tag { border-color: var(--action); }
/* HEUTE BLEIBT, WIE ES WAR: neutraler Rand, dazu der Punkt - auch wenn an
   diesem Tag etwas frei ist. Gewaehlt (weiter unten) schlaegt das. */
a.kalender__tag--heute,
button.kalender__tag--heute { border-color: var(--border-control); }
a.kalender__tag:hover,
button.kalender__tag:hover {
  background: var(--action-subtle);
  border-color: var(--action);
  color: var(--action-on-subtle);
  text-decoration: none;
}
.kalender__info {
  color: var(--text-muted);
  font-size: var(--text-xs);
  line-height: 1;
  font-weight: var(--weight-normal);
}
a.kalender__tag:hover .kalender__info { color: var(--action-on-subtle); }

.kalender__tag[aria-current="date"],
.kalender__tag[aria-pressed="true"],
.kalender__tag[aria-current="date"]:hover,
.kalender__tag[aria-pressed="true"]:hover {
  background: var(--action);
  border-color: var(--action);
  color: var(--text-on-action);
  font-weight: var(--weight-semibold);
}
.kalender__tag[aria-current="date"] .kalender__info { color: var(--text-on-action); }

.kalender__tag--aus {
  border-color: transparent;
  background: none;
  color: var(--text-placeholder);
  font-weight: var(--weight-normal);
  cursor: default;
}

/* Heute: ein Punkt unter der Zahl, in der Farbe des Textes - auf der
   gewaehlten Flaeche also weiss. */
.kalender__tag--heute::after {
  content: "";
  position: absolute;
  bottom: 2px;
  left: 50%;
  width: 4px;
  height: 4px;
  margin-left: -2px;
  border-radius: var(--radius-full);
  background: currentColor;
}

.kalender__tag:focus-visible,
.kalender__pfeil:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--focus-halo);
  border-color: var(--action);
}

/* --- Der Zeit-Baustein (admin/_zeit.html) --------------------------------

   FELD UND SYMBOLKNOPF WIE BEIM KALENDER DARUEBER, Zeile fuer Zeile: gleiche
   Feldbreite (--field-max-sm, im Token-Kommentar ausdruecklich fuer "Datum,
   Uhrzeit"), gleicher Symbolknopf, gleiches Aufklappfenster, gleiche
   Markenfarbe. Nur die Auswahl selbst ist eine andere - dort ein
   Monatsblatt, hier zwei Walzen.

   DAS EINRASTEN MACHT CSS, NICHT DAS SKRIPT: `scroll-snap-type: y mandatory`
   auf der Walze, `scroll-snap-align: center` auf jedem Knopf. Das Skript
   liest nur ab, was in der Mitte steht. So bleibt das Rad fluessig, auch
   wenn JavaScript gerade beschaeftigt ist.

   DIE HOEHE IST EINE UNGERADE ZAHL VON ZEILEN (5 x 2rem = 10rem). Bei einer
   geraden gaebe es keine echte Mitte, und der Markierungsstreifen laege
   zwischen zwei Knoepfen. Das Polster oben und unten betraegt genau zwei
   Zeilen, damit auch der erste und der letzte Wert in die Mitte rollen
   koennen - ohne es blieben 00 und 23 unerreichbar. */
.zeit__legende {
  display: block;
  margin-bottom: var(--space-1);
  color: var(--text);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
  font-weight: var(--weight-medium);
}
.zeit__feld {
  position: relative;
  max-width: var(--field-max-sm);
}
/* Die allgemeine Feldregel gibt Rahmen, Hoehe und Fokus; hier nur der Platz
   fuer das Symbol und Ziffern gleicher Breite. */
.zeit__eingabe {
  padding-right: var(--control-height);
  font-variant-numeric: tabular-nums;
}
.zeit__oeffnen {
  position: absolute;
  top: 0;
  right: 0;
  width: var(--control-height);
  min-height: var(--control-height);
  height: var(--control-height);
  padding: 0;
  border: 0;
  background: none;
  color: var(--icon-muted);
}
.zeit__oeffnen:hover,
.zeit__oeffnen:active,
.zeit__oeffnen[aria-expanded="true"] {
  background: none;
  color: var(--action-text);
}
.zeit__oeffnen svg { width: 18px; height: 18px; }

.zeit__popup {
  position: absolute;
  z-index: 20;   /* ueber dem Formular, unter der Abmeldewarnung (40/50) */
  top: calc(100% + var(--space-1));
  left: 0;
  width: 11rem;   /* wie das Feld: zwei schmale Spalten brauchen nicht mehr */
  max-width: calc(100vw - 2 * var(--space-4));
  padding: var(--space-2);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}
.zeit--oben .zeit__popup {
  top: auto;
  bottom: calc(100% + var(--space-1));
}
.zeit__kopf {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-1);
  margin-bottom: var(--space-1);
}
.zeit__spaltentitel {
  color: var(--text-label);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
  font-weight: var(--weight-medium);
  text-align: center;
}
.zeit__picker {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-1);
}
/* Der Streifen, der die Mitte zeigt. Ueber beiden Walzen, ohne Klickziel zu
   sein - dieselbe Technik wie bei den Blaetterpfeilen des Kalenders. */
.zeit__marke {
  position: absolute;
  /* UNTER DEN ZIFFERN, NICHT DARUEBER. Ohne die beiden z-index deckte dieser
     Streifen genau den Wert zu, den er hervorheben soll: Er ist positioniert,
     die Walzen daneben waren es nicht - und positionierte Elemente werden
     zuletzt gezeichnet. Im Bild fehlte dadurch die mittlere Ziffer; drei
     Messungen meldeten sie brav als vorhanden, dunkel und 32px hoch.
     `pointer-events: none` bleibt trotzdem stehen: Es schuetzt die Klicks,
     falls jemand die Reihenfolge spaeter wieder aendert. */
  z-index: 0;
  top: calc(50% - 1rem);
  left: 0;
  right: 0;
  height: 2rem;
  border-top: 1px solid var(--border-control);
  border-bottom: 1px solid var(--border-control);
  border-radius: var(--radius-sm);
  background: var(--surface-sunken);
  pointer-events: none;
}
.zeit__walze {
  /* Positioniert, damit die Ziffern UEBER dem Markierungsstreifen liegen -
     siehe .zeit__marke. */
  position: relative;
  z-index: 1;
  /* Fuenf Zeilen zu 2rem: eine echte Mitte, und der Picker bleibt so hoch
     wie ein Monatsblatt schmal ist. */
  height: 10rem;
  overflow-y: auto;
  overscroll-behavior: contain;
  scroll-snap-type: y mandatory;
  /* Zwei Zeilen Polster, damit auch der erste und der letzte Wert mittig
     einrasten koennen. */
  padding: 4rem 0;
  scrollbar-width: none;   /* der Streifen zeigt die Auswahl, nicht der Balken */
}
.zeit__walze::-webkit-scrollbar { display: none; }
.zeit__wert {
  display: block;
  width: 100%;
  height: 2rem;
  min-height: 2rem;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--text-muted);
  font-size: var(--text-sm);
  line-height: 2rem;
  font-weight: var(--weight-normal);
  font-variant-numeric: tabular-nums;
  text-align: center;
  cursor: pointer;
  scroll-snap-align: center;
  /* `color` GEHOERT MIT IN DEN UEBERGANG: Ohne es schaltet die Schrift beim
     Waehlen sofort um, waehrend die Flaeche noch laeuft - der Knopf sah fuer
     einen Moment leer aus. Am Screenshot gesehen, nicht an einem Test. */
  transition: color 120ms ease, font-weight 120ms ease;
}
.zeit__wert:hover {
  background: none;
  color: var(--action-on-subtle);
}
/* DER WERT IN DER MITTE, und das heisst NICHT "gewaehlt".

   Ohne diese Regel tragen alle 84 Werte dieselbe Farbe, und die Walze hat
   keine Hierarchie - am Screenshot sah der Streifen dadurch leer aus. Es ist
   KEINE Kontrastfrage: --text-muted auf --surface-sunken erreicht 4.55:1 und
   damit die Textschwelle. Es fehlte die Auszeichnung der Mitte.

   --text UND NICHT --action: Blau hiesse "ausgewaehlt", und bei leerem Feld
   ist nichts ausgewaehlt. Nachgerechnet waere Blau mit 4.56:1 ohnehin nicht
   lesbarer als das Grau; --text kommt auf 13.98:1. Das Attribut setzt
   zeit.js, weil CSS "in der Mitte" nicht kennt. */
.zeit__wert[data-mitte="true"] {
  color: var(--text);
  font-weight: var(--weight-medium);
}
/* Der gewaehlte Wert steht im Streifen und traegt die Markenfarbe - kein
   eigener Kasten, der Streifen ist schon da. */
.zeit__wert[aria-pressed="true"],
.zeit__wert[aria-pressed="true"]:hover {
  background: none;
  color: var(--action-text);
  font-weight: var(--weight-semibold);
}
.zeit__wert:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--focus-halo);
  border-radius: var(--radius-sm);
}

.kalender__alle {
  margin: var(--space-2) 0 0;
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
}

/* --- Wochenwechsel ------------------------------------------------------ */
.weeknav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-4);
}
.weeknav__label {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: baseline;
}
.weeknav__label span { color: var(--text-muted); font-size: var(--text-sm); }
.weeknav__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
/* Der Knopf "Diese Woche" ist auf der laufenden Woche ein <span> ohne Ziel -
   ein Link, der auf die Seite fuehrt, auf der man steht, ist eine Sackgasse
   mit Vorlesefunktion. */
.weeknav__actions [aria-disabled="true"] {
  background: var(--surface-sunken);
  border-color: var(--border);
  color: var(--text-muted);
  cursor: default;
}

/* --- Wochenraster -------------------------------------------------------
   EINE ECHTE TABELLE: Zeilen sind Uhrzeiten, Spalten sind Tage. Damit liest
   ein Screenreader zu jedem Eintrag von selbst "Mittwoch, 09:00" vor, ohne
   dass dieselbe Angabe in jedem Kaestchen stehen muss.

   `table-layout: fixed` und sieben gleiche Spalten: Ohne das bekommt der Tag
   mit dem laengsten Namen die breiteste Spalte, und die Woche saehe je nach
   Buchungslage anders aus. */
.weekgrid {
  /* `fixed` IST die gleichmaessige Verteilung: Der Browser richtet sich dann
     nach den angegebenen Breiten und teilt den Rest zu gleichen Teilen auf
     die uebrigen Spalten auf. Nur die Zeitspalte traegt eine Breite, die
     sieben Tage bekommen danach exakt dasselbe. Ohne `fixed` bemaesse er die
     Spalten nach ihrem Inhalt - ein Dienstag mit drei Terminen waere dann
     breiter als ein leerer Mittwoch, und die Woche saehe jeden Tag anders
     aus. */
  table-layout: fixed;
  /* Schmaler als die Inhaltsspalte darf das Raster nicht werden, sonst
     quetschen sich sieben Tage auf Briefmarkenbreite. Darunter scrollt es im
     Kasten der Tabelle (.table-wrap) - dieselbe Loesung wie bei den Listen. */
  min-width: 56rem;
}

/* SENKRECHTE TRENNLINIEN ZWISCHEN DEN TAGEN. Ohne sie ist das Raster eine
   Flaeche mit Kaestchen darin; die Zuordnung "welcher Termin gehoert zu
   welchem Tag" muss man dann von der Kopfzeile herunterzaehlen. Mit ihnen ist
   es ein Kalender.

   Am linken Rand jeder Zelle und nicht rechts: So bekommt die letzte Spalte
   keine Linie an der Aussenkante, wo schon der Kartenrand sitzt. Die
   Zeitspalte traegt ihre eigene, kraeftigere - sie trennt die Achse vom
   Inhalt und ist keine Tagesgrenze. */
.weekgrid thead th + th,
.weekgrid tbody td { border-left: 1px solid var(--border); }
.weekgrid tbody td:first-of-type,
.weekgrid thead th:nth-child(2) { border-left: 1px solid var(--border-strong); }
.weekgrid__time { border-right: 1px solid var(--border-strong); }
.weekgrid thead th {
  padding: var(--space-3) var(--space-2);
  text-align: center;
  text-transform: none;
  letter-spacing: normal;
  white-space: normal;
}
.weekgrid__weekday {
  display: block;
  color: var(--text-strong);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
}
.weekgrid__date {
  display: block;
  color: var(--text-muted);
  font-weight: var(--weight-normal);
}
/* Der heutige Tag als Spalte, nicht nur als Kopfzeile: Beim Blick auf
   Donnerstag will man wissen, ob das der heutige ist, ohne nach oben zu
   sehen. */
.weekgrid__today { background: var(--action-subtle); }
/* Das Datum in der getoenten Spalte braucht einen dunkleren Grauton als
   in den sechs weissen daneben - siehe --text-muted-on-subtle. */
.weekgrid__today .weekgrid__date { color: var(--text-muted-on-subtle); }

/* DIE BREITE MUSS AN DER KOPFZEILE STEHEN, nicht nur an der Zeile darunter.
   `table-layout: fixed` nimmt die Spaltenbreiten aus der ERSTEN Zeile und
   ignoriert alles Spaetere. Die Kopfzelle der Zeitachse trug keine Breite -
   also verteilte der Browser alle acht Spalten gleich, und die Zeitachse war
   mit gemessenen 157.75px so breit wie ein ganzer Tag, obwohl "09:00"
   darin steht. Die sieben Tage bekamen entsprechend weniger, und das Raster
   sah aus, als draenge es sich nach links. */
.weekgrid thead th:first-child,
.weekgrid__time {
  width: 5.5rem;
  padding: var(--space-3) var(--space-3);
  color: var(--text-muted);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  font-variant-numeric: tabular-nums;
  text-align: right;
  vertical-align: top;
  white-space: nowrap;
}
.weekgrid__cell {
  padding: var(--space-2);
  vertical-align: top;
}
/* EINE ZEILENHOEHE FUER ALLE, ob etwas darin steht oder nicht. Ohne sie war
   eine leere Zeile 46px hoch und eine mit Kaestchen 69 - dieselbe halbe
   Stunde in zwei Hoehen, und das Raster sah aus, als haetten manche
   Zeitschienen mehr Zeit als andere.

   Gerechnet aus dem Kaestchen selbst (--slot-height) plus der Polsterung der
   Zelle. Eine feste Zahl liefe beim naechsten Mass auseinander.

   `height` und nicht `min-height`: Tabellenzellen ignorieren `min-height`,
   und `height` wirkt an einer Tabellenzeile ohnehin als Mindestmass - eine
   Zelle mit zwei Terminen waechst weiterhin. */
.weekgrid tbody tr {
  /* + 1px: die Zeilenlinie der Tabelle. Eine Zelle MIT Kaestchen wurde
     Kaestchen + Polsterung + Linie hoch, eine leere nur so hoch wie diese
     Vorgabe - 1px Unterschied, der bei nebeneinander liegenden
     Standort-Rastern Zeile um Zeile auflief (gemessen: 68 gegen 69px). */
  height: calc(var(--slot-height) + 2 * var(--space-2) + 1px);
}
/* GLEICHE HOEHE JE UHRZEIT UEBER ALLE STANDORT-RASTER. Nebeneinander
   liegende Raster sind getrennte Tabellen; eine Zeile mit zwei Terminen
   wuchs nur in ihrer eigenen, und alles darunter stand versetzt. Der Server
   kennt die vollste Zelle jeder Uhrzeit ueber alle Raster und setzt diese
   Klasse an die Zeile in JEDEM Raster (admin_appointments.py, `_rows_for`).
   N Kaestchen: N mal --slot-height, dazwischen je --space-1 (der Abstand
   unter .appt/.freeslot), plus die Zellpolsterung. */
.weekgrid tbody tr.weekgrid__zeile--2 { height: calc(2 * var(--slot-height) + 1 * var(--space-1) + 2 * var(--space-2) + 1px); }
.weekgrid tbody tr.weekgrid__zeile--3 { height: calc(3 * var(--slot-height) + 2 * var(--space-1) + 2 * var(--space-2) + 1px); }
.weekgrid tbody tr.weekgrid__zeile--4 { height: calc(4 * var(--slot-height) + 3 * var(--space-1) + 2 * var(--space-2) + 1px); }
.weekgrid tbody tr.weekgrid__zeile--5 { height: calc(5 * var(--slot-height) + 4 * var(--space-1) + 2 * var(--space-2) + 1px); }
.weekgrid tbody tr.weekgrid__zeile--6 { height: calc(6 * var(--slot-height) + 5 * var(--space-1) + 2 * var(--space-2) + 1px); }

/* LEER UND NICHTS EINZUTRAGEN (`_rows_for`): kein Pluszeichen, kein
   Termin. Zurueckgenommen - dieselbe Flaeche wie ein Tabellenkopf. Jedes
   weisse Feld hat damit einen sichtbaren Grund (Pluszeichen oder Termin).

   DIE HEUTE-SPALTE BLEIBT DURCHGEHEND GETOENT, auch in diesen Feldern und in
   der Fortsetzung. Vorher gewann hier das Grau, und die Spalte zerfiel in
   blaue und graue Stuecke - genau das, was `.weekgrid__today` verhindern
   soll (man soll den heutigen Tag erkennen, ohne nach oben zu sehen).
   Verloren geht dabei nichts: Ein nicht graues Feld traegt ohnehin ein
   Pluszeichen oder einen Termin. */
.weekgrid__cell--aus { background: var(--surface-sunken); }
.weekgrid__cell--aus.weekgrid__today { background: var(--action-subtle); }

/* Die Fortsetzung beginnt ohne eigene Oberkante - die letzte Zeile des
   Rasters traegt keine Unterlinie (tbody tr:last-child td), also bekommt die
   erste Fortsetzungszeile eine. */
.weekgrid--fort tbody tr:first-child > * { border-top: 1px solid var(--border); }
/* Kein Zeilen-Hover im Raster: Er faerbte sieben Tage gleichzeitig ein und
   sagte damit nichts. */
.weekgrid tbody tr:hover { background: none; }

/* Ein Termin im Raster. Blockelement statt Zeile: In einer Zelle koennen
   mehrere stehen - Beratung und Rueckruf zur selben Zeit schliessen sich
   nicht aus, sie belegen verschiedene Kalender. */
/* EIN KAESTCHEN, EINE HOEHE - gleich, ob ein Termin darin steht oder ein
   freier Platz. Gemessen schwankten die Termine zwischen 52 und 94px: Der
   Name brach um, und ein Zustand ("storniert") kam als dritte Zeile dazu. Im
   Raster sah das aus, als haetten die Tage verschiedene Zeitraster.

   ZWEI ZEILEN, FEST: oben die Art und - falls vorhanden - der Zustand, unten
   der Name. Beide abgeschnitten statt umbrechend.

   DAS RASTER ORDNET, OHNE DIE VORLAGE ANZUFASSEN: Im Markup steht der
   Zustand NACH dem Namen, angezeigt wird er daneben. `grid-area` setzt jedes
   Stueck an seinen Platz, unabhaengig von der Reihenfolge im Dokument - die
   Vorlage bleibt, wie sie ist, und die Vorlesereihenfolge ebenfalls. */
.appt {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-content: start;
  gap: 0 var(--space-1);
  height: var(--slot-height);
  overflow: hidden;
  margin-bottom: var(--space-1);
  padding: var(--space-2);
  border-radius: var(--radius-sm);
  border-left: 3px solid transparent;
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
}
.appt:last-child { margin-bottom: 0; }
.appt:hover { text-decoration: none; }
.appt__kind {
  grid-area: 1 / 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: var(--weight-semibold);
}
.appt__name {
  /* Ueber beide Spalten, damit der Name die volle Breite hat. */
  grid-area: 2 / 1 / 3 / 3;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text);
}
/* Der Zustand als kompaktes Abzeichen. Ein volles `.badge` waere in einem
   Kaestchen von 100px Breite zu schwer; die Form ist dieselbe, nur kleiner -
   damit "storniert" hier und "storniert" in der Liste erkennbar dasselbe
   sind. */
.appt__state {
  grid-area: 1 / 2;
  justify-self: end;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  /* ZWEI PIXEL SENKRECHT, vorher keine: Der Text sass mit 1px auf der
     Unterkante seines eigenen Rahmens - bei 13px Schrift sieht das aus wie
     ein Druckfehler. Die Hoehe waechst dadurch von 19 auf 23px und bleibt
     weit unter den 52px des Terminkaestchens; die Zeilenhoehen des
     Wochenrasters rechnen ohnehin nur mit --slot-height, --space-1 und
     --space-2 (weiter oben) und aendern sich nicht.

     NACHGEMESSEN, UND DIE ZAHL SIEHT KRUMM AUS: Zwischen Glyphen und Rahmen
     liegen oben 4px und unten 1px, obwohl das Polster auf beiden Seiten 2px
     ist. Das ist KEIN Fehler und nicht nachzujustieren - die Zeilenhoehe
     betraegt 18px, der Glyphenkasten misst 16px, und diese zwei Pixel
     verteilt der Browser nach der Schriftmetrik, nicht symmetrisch. Wichtig
     ist, was vorher fehlte: Der Text sitzt nicht mehr auf dem Rahmen auf. */
  padding: 2px var(--space-1);
  border-radius: var(--radius-sm);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  color: var(--text-muted);
  font-weight: var(--weight-medium);
}

.appt--beratung {
  background: var(--cal-consultation-bg);
  border-left-color: var(--cal-consultation-edge);
  color: var(--cal-consultation-text);
}
.appt--rueckruf {
  background: var(--cal-callback-bg);
  border-left-color: var(--cal-callback-edge);
  color: var(--cal-callback-text);
}
.appt--online {
  background: var(--cal-online-bg);
  border-left-color: var(--cal-online-edge);
  color: var(--cal-online-text);
}
.appt--probetraining,
.appt--besichtigung {
  background: var(--cal-branche-bg);
  border-left-color: var(--cal-branche-edge);
  color: var(--cal-branche-text);
}
/* Das Kuerzel steht in der oberen Zeile rechts - dort, wo sonst der Zustand
   steht. HAT DER TERMIN BEIDES (storniert, nicht erschienen), GEWINNT DER
   ZUSTAND DEN PLATZ: In einem 100px breiten Kaestchen ist "storniert" die
   wichtigere Auskunft, und ein zweites Abzeichen ueberdeckte den Namen
   (gesehen im Browser, 24.9.2026). Das Kuerzel wird dann nur optisch
   ausgeblendet - der vorgelesene Satz "Zugewiesen an ..." bleibt, und die
   Terminseite nennt die Person ohnehin. */
.appt__zugewiesen {
  grid-area: 1 / 2;
  justify-self: end;
}
.appt__state ~ .appt__zugewiesen {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* Storniert und vergangen: gedaempft, aber nicht unsichtbar. Ein stornierter
   Termin bleibt sichtbar, weil die Zeile Auslastung dokumentiert - und weil
   sonst niemand merkt, dass an dieser Stelle etwas abgesagt wurde.
   Durchstreichung UND blasse Flaeche, damit die Aussage nicht allein an der
   Saettigung haengt. */
.appt--cancelled {
  background: var(--surface-sunken);
  border-left-color: var(--border-strong);
  color: var(--text-muted);
}
.appt--cancelled .appt__name {
  color: var(--text-muted);
  text-decoration: line-through;
}
.appt--past { opacity: 0.72; }

/* Ein freier Slot. Zurueckhaltend, bis man ihn ansteuert: Ein Raster, in dem
   jedes freie Feld um Aufmerksamkeit ruft, macht die gebuchten Termine
   unlesbar - und die sind der Grund, warum jemand hersieht. */
.freeslot {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  /* Dieselbe Hoehe wie ein Termin - das ist der Sinn der Sache. */
  height: var(--slot-height);
  margin-bottom: var(--space-1);
  padding: var(--space-1) var(--space-2);
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
}
.freeslot:last-child { margin-bottom: 0; }
.freeslot svg { width: 12px; height: 12px; flex: 0 0 auto; }
.freeslot__kind { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.freeslot:hover { text-decoration: none; }
.freeslot--beratung:hover,
.freeslot--beratung:focus-visible {
  background: var(--cal-consultation-bg);
  border-color: var(--cal-consultation-edge);
  border-style: solid;
  color: var(--cal-consultation-text);
}
.freeslot--rueckruf:hover,
.freeslot--rueckruf:focus-visible {
  background: var(--cal-callback-bg);
  border-color: var(--cal-callback-edge);
  border-style: solid;
  color: var(--cal-callback-text);
}

/* --- Pagination -------------------------------------------------------- */
.pagination {
  display: flex;
  gap: var(--space-3);
  align-items: center;
  justify-content: space-between;
  padding: var(--space-3) var(--card-padding);
  border-top: 1px solid var(--border);
  background: var(--surface-sunken);
  font-size: var(--text-sm);
}
.pagination__status { color: var(--text-muted); font-size: var(--text-xs); }
.pagination__spacer { flex: 1 1 auto; }

/* --- Hilfstext --------------------------------------------------------- */
.hint {
  max-width: var(--prose-max);
  color: var(--text-muted);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
}
/* Gleichmaessige Abstaende zwischen direkten Kindern - fuer Karten, deren
   Inhalt aus Ueberschrift, Hinweis und Formular besteht, ohne dass jedes
   Element seinen Abstand selbst mitbringen muss. */
.stack > * + * { margin-top: var(--space-4); }
/* Das Banner bringt hier keinen eigenen Abstand mit, sonst summiert sich der
   Zwischenraum. */
.stack > .banner { margin-bottom: 0; }

/* --- Vorwarnung vor der selbsttaetigen Abmeldung -----------------------
   Fest am unteren Rand, damit sie auch dann sichtbar ist, wenn der Nutzer
   weit unten in einem langen Formular steht - eine Warnung, die man
   wegscrollen kann, ist keine. `[hidden]` blendet den Kasten ohne
   JavaScript vollstaendig aus. */
.session-warning {
  position: fixed;
  left: 50%;
  bottom: var(--space-6);
  transform: translateX(-50%);
  z-index: 50;
  width: min(32rem, calc(100% - var(--space-8)));
  padding: var(--space-4);
  /* BLAU UND MIT EIGENEN TOKENS, nicht mehr --warning-*: Die Warnfarbe
     gehoert den Bannern mit Handlungsbedarf, und eine Aenderung dort soll
     diesen Kasten nicht mitnehmen (Begruendung und Messwerte in tokens.css,
     SECURITY.md Kapitel 26, J). */
  background: var(--session-notice-bg);
  border: 1px solid var(--session-notice-border);
  border-left: 3px solid var(--session-notice-accent);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}
.session-warning[hidden] { display: none; }
.session-warning__text {
  margin: 0 0 var(--space-3);
  font-size: var(--text-sm);
  line-height: var(--text-base-lh);
}
/* Der Rahmen des Sekundaerknopfs traegt auf der blauen Toenung nicht:
   --border-control erreicht dort 2.78:1 und verfehlt WCAG 1.4.11. */
.session-warning__stay {
  margin: 0;
  border-color: var(--session-notice-control-border);
}

/* --- Einfuehrungstour ----------------------------------------------------
   Die Karte steht OHNE JavaScript fest unten rechts - dieselbe Bauweise wie
   die Abmeldewarnung darueber, eine Stufe darunter (z-index 40 statt 50):
   Die Warnung vor dem Abmelden ist wichtiger als der naechste Tour-Schritt.

   static/js/tour.js rueckt die Karte neben das hervorgehobene Element. Die
   Position setzt es ueber ein Constructable Stylesheet (Klasse
   `tour-karte--am-ziel`), nie ueber `style=` - die CSP laesst keinen
   Inline-Stil zu. Faellt das Skript aus, bleibt die Karte, wo sie ist. */
.tour-karte {
  position: fixed;
  right: var(--space-6);
  bottom: var(--space-6);
  z-index: 40;
  width: min(24rem, calc(100% - var(--space-8)));
  padding: var(--space-4);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-left: 3px solid var(--action);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  font-size: var(--text-sm);
  line-height: var(--text-base-lh);
}
.tour-karte--kompakt { width: min(20rem, calc(100% - var(--space-8))); }
.tour-karte__kopf {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  margin: 0 0 var(--space-1);
  color: var(--text-muted);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
}
.tour-karte__titel {
  margin: 0 0 var(--space-2);
  font-size: var(--text-lg);
  line-height: var(--text-lg-lh);
  font-weight: var(--weight-semibold);
  color: var(--text-strong);
}
.tour-karte__text,
.tour-karte__fakt,
.tour-karte__status { margin: 0 0 var(--space-2); }
.tour-karte__fakt { color: var(--text-muted); }
.tour-karte__hinweis {
  margin: 0 0 var(--space-2);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--warning-border);
  border-radius: var(--radius);
  background: var(--warning-bg);
  color: var(--warning-text);
}
.tour-karte__pause { margin: var(--space-2) 0 0; text-align: right; }
.tour-aktionen {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
  margin-top: var(--space-3);
}
.tour-aktionen form { margin: 0; }

/* Das hervorgehobene Element: Rahmen UND Halo, damit die Markierung nicht
   allein an einer Farbe haengt (WCAG 1.4.1) - dieselben Tokens wie der
   Fokusring. */
.tour-ziel {
  outline: 3px solid var(--focus-ring);
  outline-offset: 4px;
  border-radius: var(--radius);
  box-shadow: 0 0 0 8px var(--focus-halo);
}

.tour-willkommen { border-left: 3px solid var(--action); }
.tour-willkommen__titel {
  margin: 0 0 var(--space-2);
  font-size: var(--text-lg);
  line-height: var(--text-lg-lh);
}
.tour-willkommen__text { margin: 0; max-width: 60ch; }


@media (max-width: 900px) {
  /* Schmal: die Karte als Leiste am unteren Rand, nie neben dem Ziel. */
  .tour-karte,
  .tour-karte--kompakt {
    right: var(--space-4);
    left: var(--space-4);
    bottom: var(--space-4);
    width: auto;
  }
}

/* ==========================================================================
   4. TEXTBLOECKE MIT EIGENEN REGELN

   Drei Sonderfaelle, die sich nicht als Standardkomponente fassen lassen, weil
   ihr Inhalt vorformatiert ist oder vollstaendig markierbar sein muss.
   ========================================================================== */

/* Der Einbettungscode muss vollstaendig markierbar sein und darf die Seite
   nicht sprengen - deshalb scrollt er in seinem eigenen Kasten.
   `user-select: all` markiert mit einem Klick die ganze Zeile: sie soll als
   Ganzes kopiert werden, ein halb markiertes <script>-Tag ist unbrauchbar. */
.embed-code {
  overflow-x: auto;
  padding: var(--space-3);
  background: var(--surface-sunken);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: var(--text-xs);
  user-select: all;
}
.embed-code code { white-space: pre; }

/* Rechtstext. `font-family: inherit` hebt die Monospace-Vorgabe von <pre>
   auf: der Text ist Prosa und keine Auszeichnung - nur der Zeilenumbruch der
   Quelle soll erhalten bleiben, nicht deren Schreibmaschinenoptik. */
.legal-text {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-height: 60vh;
  overflow-y: auto;
  padding: var(--space-4);
  background: var(--surface-sunken);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-family: inherit;
  font-size: var(--text-sm);
  line-height: var(--text-base-lh);
}

/* Pruefsumme. Steht in einer Kopfzeile und darf sie nicht sprengen, muss aber
   vollstaendig markierbar bleiben - jemand vergleicht sie mit seinem Nachweis. */
.hash {
  overflow-wrap: anywhere;
  user-select: all;
  padding: 0;
  background: none;
  border: 0;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-muted);
}

/* --- Markenfarbe mit Live-Vorschau (/admin/clo) ----------------------
   Die Vorschau zeigt, was das Chatfenster spaeter zeigt: oben Weiss, unten
   die helle Variante der gewaehlten Farbe. Den Wert der Variable setzt
   app/static/js/widget-farbe.js ueber die CSSOM - der Vorgabewert hier ist
   die Ableitung aus dem Markenblau und gilt, solange niemand etwas anfasst
   (und in jedem Browser ohne konstruierbare Stylesheets). */
.farbwahl {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.farbwahl__feld {
  width: 3.5rem;
  height: 2.5rem;
  padding: 0.15rem;
  flex: none;
}
.farbwahl__wert {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--text-muted);
  line-height: 2.5rem;
}
.farbwahl__vorschau {
  flex: 1 1 14rem;
  min-width: 12rem;
  max-width: 22rem;
  min-height: 6rem;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--space-2);
  padding: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  /* Der Rueckfallwert ist die Oberflaechenfarbe und nicht etwa ein festes
     Hellblau: Wo das Skript die Variable nicht setzen kann, soll die
     Vorschau ungetoent bleiben statt eine Farbe zu behaupten. */
  background: linear-gradient(
    180deg,
    var(--surface) 0%,
    var(--surface) 38%,
    var(--fs-vorschau-weich, var(--surface)) 100%
  );
}
.farbwahl__blase {
  align-self: flex-start;
  max-width: 85%;
  margin: 0;
  padding: 0.4rem 0.6rem;
  border-radius: var(--radius);
  background: var(--surface-sunken);
  font-size: var(--text-sm);
  color: var(--text);
}
/* Die Knopfflaeche im Widget - gefuellt, heller Text. Der Rueckfallwert ist
   ein neutrales Dunkelgrau: Ohne Skript behauptet die Vorschau keine
   Markenfarbe, bleibt aber lesbar. */
.farbwahl__knopf {
  align-self: flex-start;
  margin: 0;
  padding: 0.35rem 0.7rem;
  border-radius: var(--radius);
  background: var(--fs-vorschau-knopf, var(--farbwahl-vorgabe));
  color: var(--text-on-action);
  font-size: var(--text-sm);
  font-weight: 600;
}
.farbwahl__zeile {
  margin: 0;
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  font-size: var(--text-sm);
  color: var(--text-muted);
}

/* --- Logo-Groesse: fuenf Stufen (SECURITY.md Kapitel 33, 22.9.2026) -------
   FUENF RADIOKNOEPFE IN EINER REIHE, jeder mit einem Balken darueber, dessen
   Hoehe die Stufe andeutet. Der Balken ist reine Andeutung und kein Massstab:
   Was wirklich herauskommt, zeigt die Vorschau daneben - sofort, ohne
   Speichern (app/static/js/chatbot-live.js).

   WARUM UEBERHAUPT EIN BALKEN: Fuenf Woerter nebeneinander ("Klein",
   "Mittel", ...) sind eine Liste; fuenf verschieden hohe Balken sind eine
   Skala. Der Unterschied kostet hier zwoelf Zeilen CSS und spart beim Lesen
   einen Gedanken.

   "AUTOMATISCH" BEKOMMT EINEN ANDEREN BALKEN als die vier Stufen - gepunktet
   statt gefuellt. Es ist keine Groesse zwischen "Klein" und "Mittel",
   sondern eine Regel; ein gefuellter Balken haette es in die Skala
   eingereiht, in die es nicht gehoert. */
.logowahl {
  margin: 0;
  padding: 0;
  border: 0;
}
.logowahl__titel {
  padding: 0;
  margin-bottom: var(--space-2);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text);
}
.logowahl__reihe {
  display: flex;
  align-items: flex-end;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.logowahl__stufe {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
  flex: 1 1 4.5rem;
  min-width: 4.5rem;
  padding: var(--space-2) 0.35rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  cursor: pointer;
  text-align: center;
}
.logowahl__stufe:hover {
  /* --surface-hover, nicht --neutral-50 direkt: Ein Dark Mode definiert nur
     die semantische Schicht neu (tokens.css). Eine Rampenstufe in einer
     Komponente ueberlebt das und bliebe hell auf dunklem Grund. */
  background: var(--surface-hover);
}
/* Der eigentliche Radioknopf bleibt sichtbar und bedienbar - ausgeblendet
   waere er fuer Tastatur und Screenreader ein Rueckschritt. Er steht unter
   dem Namen, weil die Reihenfolge Balken -> Name -> Knopf von oben nach
   unten gelesen wird. */
.logowahl__stufe input[type="radio"] {
  margin: 0;
  order: 3;
}
/* DIE AKTIONSFARBE, NICHT DIE MARKENFARBE. `--brand` gibt es nicht mehr
   (tokens.css sagt warum), und die Markenfarbe der FAHRSCHULE gehoert hier
   ohnehin nicht hin: Sie faerbt das Chatfenster, nicht die Verwaltung. Was
   gerade ausgewaehlt ist, ist ein Zustand der Oberflaeche - also --action.
   --text-muted-on-subtle steht auf --action-subtle bei 6.77:1. */
.logowahl__stufe:has(input:checked) {
  border-color: var(--action);
  background: var(--action-subtle);
}
.logowahl__stufe:has(input:checked) .logowahl__name {
  color: var(--action-on-subtle);
  font-weight: var(--weight-semibold);
}
.logowahl__stufe:has(input:checked) .logowahl__marke {
  background: var(--action);
}
.logowahl__stufe:has(input:checked) .logowahl__marke[data-logowahl-marke="auto"] {
  background: none;
  border-color: var(--action);
}
.logowahl__marke {
  display: block;
  width: 2.2rem;
  background: var(--text-muted);
  border-radius: 2px;
  order: 1;
}
/* `:has()` FEHLT IN AELTEREN BROWSERN - dann bleibt der ausgewaehlte Eintrag
   optisch wie die anderen. Das ist hinnehmbar und kein Informationsverlust:
   Der Radioknopf darunter ist sichtbar und zeigt die Auswahl selbst. Die
   Hervorhebung ist Zugabe, nicht die einzige Auskunft. */
.logowahl__marke[data-logowahl-marke="auto"] {
  height: 1.4rem;
  background: none;
  border: 2px dotted var(--text-muted);
}
.logowahl__marke[data-logowahl-marke="klein"] { height: 0.5rem; }
.logowahl__marke[data-logowahl-marke="mittel"] { height: 0.9rem; }
.logowahl__marke[data-logowahl-marke="gross"] { height: 1.4rem; }
.logowahl__marke[data-logowahl-marke="sehr_gross"] { height: 2rem; }
.logowahl__name {
  order: 2;
  font-size: var(--text-sm);
  color: var(--text);
}

/* Logo-Vorschau auf /admin/clo (SECURITY.md Kapitel 24). Das Logo ist bis
   512 Pixel gross; in der Karte steht es auf hoechstens 96 Pixel Hoehe, so
   wie es im Chatfenster ungefaehr erscheint. */
.logo-vorschau {
  display: block;
  max-height: 96px;
  max-width: 100%;
  width: auto;
  height: auto;
}

/* ==========================================================================
   Die Summenzeile eines Pakets
   ==========================================================================
   SIE STEHT UNTER DEN POSITIONEN, aus denen sie sich ergibt - wie bei einer
   Rechnung. Abgesetzt durch einen eigenen Grund und eine Oberlinie, damit
   das Auge sie als Abschluss liest und nicht als weitere Zeile der Tabelle.

   Die Ersparnis steht als Abzeichen daneben und nicht im Fliesstext: Sie ist
   eine Auszeichnung des Preises, keine Erlaeuterung. */
.paket-summe {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2);
  padding: var(--space-3) var(--card-padding);
  border-top: 1px solid var(--border-strong);
  background: var(--surface-sunken);
}
.paket-summe__label {
  color: var(--text-label);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
}
.paket-summe__wert {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-2);
  margin-inline-start: auto;
}
.paket-summe__betrag {
  font-size: var(--text-lg);
  line-height: var(--text-lg-lh);
  color: var(--text-strong);
}

/* ==========================================================================
   Der digitale Mitarbeiter: Einrichten mit lebender Vorschau
   ==========================================================================
   KEINE EINZIGE FARBANGABE HIER. Die Bausteine daneben (.card, .banner,
   .hint) bringen ihre Farben mit; was hier steht, ist ausschliesslich
   Anordnung. So kann diese Ergaenzung die Farbregel gar nicht verletzen. */

/* Die linke Spalte waechst mit ihrem Inhalt, die rechte klebt - sonst
   scrollte das Chatfenster weg, sobald jemand unten im Formular arbeitet.
   `align-items: start` ist dafuer Voraussetzung: In einem Raster mit
   gestreckten Zellen hat `position: sticky` nichts, woran es haften koennte.

   ===================================================================
   DIE BREITE DER RECHTEN SPALTE IST EINE FUNKTIONALE ANGABE
   ===================================================================
   Das Widget hat einen eigenen Haltepunkt: Unter 640px Dokumentbreite
   (VOLLBILD_BIS in app/static/js/chat-widget.js) schaltet es auf seine
   bildschirmfuellende Handy-Fassung - randlos, ohne Radius und Schatten.
   Im Rahmen zaehlt die Breite DES RAHMENS, nicht die des Browserfensters.

   GEMESSEN AM 17.9.2026: Bei `1fr 1fr` war die Spalte bei 1440px
   Fensterbreite 540px breit, der Rahmen darin 490px - das Widget kippte ins
   Vollbild und fuellte den Rahmen. Die Karte um den Rahmen kostet rund
   50px, deshalb braucht die Spalte etwa 720px, damit im Rahmen sicher mehr
   als 640px ankommen.

   ZWEI SPALTEN ERST AB 1500px: Bei 1440px blieben links nur ~360px, und
   die Einstellungen waeren unbenutzbar gequetscht. Darunter stehen beide
   untereinander - dann hat der Rahmen die volle Inhaltsbreite und liegt
   erst recht ueber der Schwelle. Das Widget rendert also in BEIDEN Faellen
   seinen gewohnten Desktop-Zustand.

   Die Aufteilung selbst ist bewusst nicht ausgefeilt - Weissraum und
   Proportionen kommen in der geplanten Design-Runde ueber Widget und
   Verwaltung an die Reihe. Hier zaehlt nur, dass das Widget nicht mehr
   faelschlich ins Vollbild kippt. */
/* ==========================================================================
   DIE WERKBANK DES DIGITALEN MITARBEITERS
   ==========================================================================
   HIER STAND BIS ZUM 20.9.2026 EIN ZWEISPALTIGES KARTENRASTER: links ein
   Formular in einer Karte, rechts die Vorschau in einer zweiten, darunter
   zwei weitere. Fuenf gleich gewichtete Kaesten - und der Gegenstand der
   Seite, das Chatfenster selbst, war einer davon.

   DAS NEUE VERHAELTNIS IST ABSICHTLICH UNGLEICH: eine schlanke Steuerspalte
   und daneben die Vorschau, die den Rest nimmt. Bei 1600px Fensterbreite
   sind das rund 380 zu 1100 Pixel, also etwa 1:3. Wer die Seite oeffnet,
   sieht zuerst, was seine Besucher sehen.

   UNTER 1100px STAPELT ES, und zwar mit der Vorschau OBEN: Auf einem
   schmalen Schirm ist die Frage "wie sieht es aus" dieselbe, nur die
   Anordnung kann es nicht sein. */
/* SEIT 1.10.2026 (Kapitel 86) WIE DER TEAM-CHAT: links eine Leiste mit der
   Liste der Bereiche und genau einem Bereich darunter, rechts die Buehne
   mit dem Chatfenster. Unter 1101px steht alles untereinander, die Buehne
   oben; darueber der feste Rahmen weiter unten (`.clowerk` ab 1101px). */
.clowerk {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-8);
}
.clowerk__buehne { order: 1; }
.clowerk__leiste { order: 2; }

/* --- Die Steuerspalte: eine Liste, keine Kaesten ------------------------
   KEIN RAHMEN, KEIN SCHATTEN, KEINE KOPFZEILE. Vier Einstellungen sind
   kein Stapel aus vier Kaesten, sondern eine gefuehrte Liste - dasselbe,
   was das Einstellungs-Fenster mit `.einst-zeile` tut.

   WARUM TROTZDEM EIGENE KLASSEN UND NICHT `.einst-zeile`: Die dortige
   Zeile ist WAAGERECHT (Beschriftung links, Schalter rechts). Das traegt
   einen Schalter, aber kein Textfeld mit fuenf Zeilen und keinen
   Farbwaehler mit drei Saetzen Erklaerung. Hier steht alles untereinander -
   dieselbe Haltung, andere Richtung. */

.steuer__titel {
  margin: 0 0 var(--space-4);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
  font-weight: var(--weight-semibold);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.steuer__eintrag {
  padding: var(--space-5) 0;
  border-top: 1px solid var(--border);
}
.steuer__eintrag--erster { padding-top: 0; border-top: 0; }

.steuer__label {
  display: block;
  margin-bottom: var(--space-2);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
  font-weight: var(--weight-semibold);
}

/* OHNE `--prose-max`: In einer 24rem-Spalte waere die Grenze wirkungslos
   und die Regel nur eine Behauptung. */
.steuer__hinweis {
  margin: var(--space-2) 0 0;
  color: var(--text-muted);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
}

.steuer__fuss {
  padding-top: var(--space-5);
  border-top: 1px solid var(--border);
}

/* IN DER SCHMALEN SPALTE STEHT DIE FARBVORSCHAU UNTER DEM WAEHLER, nicht
   daneben. Ihre Vorgabe (`flex: 1 1 14rem`) stammt aus einer Karte, die
   doppelt so breit war; hier quetschte sie sich neben Farbfeld und
   Hexwert an den Spaltenrand. Eine volle Zeile ist ausserdem das, was sie
   zeigt: ein Ausschnitt des Chatfensters. */
.steuer__eintrag .farbwahl__vorschau {
  flex-basis: 100%;
  max-width: none;
}

.steuer__zeile {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}

/* --- Die Vorschau ------------------------------------------------------ */
.clowerk__buehne {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-width: 0;
}

.live__kopf {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
}

.live__titel {
  margin: 0;
  font-size: var(--text-lg);
  line-height: var(--text-lg-lh);
  font-weight: var(--weight-semibold);
}

/* EINE LEISE ZEILE STATT EINER KARTE. Der Verbrauch gehoert nicht zur
   Einrichtung - man sieht ihn nach, ohne etwas einstellen zu wollen. Er
   steht hier trotzdem, weil der Satz "Testfragen verbrauchen echte
   Credits" ohne Zahl daneben in der Luft haengt. Das Ganze steht unter
   "Nutzung". */
.live__zahlen {
  color: var(--text-muted);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
}

/* DAS RUBRUM ist das, was frueher die Kartenueberschrift war. Es steht in
   derselben kleinen Schrift wie die Zahlen und traegt die Betonung nur
   ueber das Gewicht - eine zweite Schriftgroesse waere hier eine zweite
   Ueberschrift, und genau die sollte verschwinden. */
.live__rubrum {
  font-weight: 600;
  color: var(--text);
}

/* DIE FUSSNOTE erbt die Groesse der Zahlenzeile und rueckt nur knapp
   darunter: Sie gehoert zu den Zahlen, nicht zur Vorschau. */
.live__fussnote {
  margin-top: var(--space-1);
  color: var(--text-muted);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
  max-width: var(--prose-max);
}

/* DIE BUEHNE IST SO HOCH WIE DAS FENSTER PLUS RAND, nicht hoeher. Das
   Chatfenster laesst sich nicht strecken - es ist 425x704 und bleibt es.
   Eine Buehne, die darueber hinauswaechst, gewinnt keinen Platz fuer das
   Gespraech, sondern nur leere Flaeche. Breit darf sie dagegen sein: Dort
   sitzt das Fenster rechts unten, so wie auf einer echten Website. */
.live__buehne {
  padding: var(--space-4);
  background: var(--surface-sunken);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}

/* DIE HOEHE FOLGT DER PANELFORMEL DES WIDGETS, nicht dem Augenmass: Die
   Panelhoehe ist `min(704px, 100vh - 96px)` - im Rahmen also die
   Rahmenhoehe minus 96. 800px ergeben genau die 704px, die das Widget auch
   auf einer echten Kundenseite hat. */
.vorschau-fenster {
  display: block;
  width: 100%;
  height: 800px;
  border: 0;
  border-radius: var(--radius);
  background: var(--surface);
}

/* Aufklappbare Randnotizen - zugeklappt eine Zeile. Weder die Pruefliste
   noch die Einbettung sind taegliche Vorgaenge; als offene Kaesten waren
   sie zwei Abschnitte, die man ueberliest. */
.live__fuss {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4) var(--space-8);
}

.notiz {
  flex: 1 1 20rem;
  padding-top: var(--space-3);
  border-top: 1px solid var(--border);
}
.notiz > summary {
  cursor: pointer;
  color: var(--text-muted);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
}
.notiz > summary:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}
.notiz ul {
  margin: var(--space-3) 0 0;
  padding-left: 1.2em;
}
.notiz ul,
.notiz p {
  color: var(--text-muted);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
}
.notiz p { margin: var(--space-3) 0 0; }

/* Die gerahmte Seite selbst (admin/chatbot_vorschau.html). Sie traegt nichts
   als Flaeche: Das Widget positioniert sich darin selbst, fest in der
   unteren rechten Ecke DIESES Dokuments. Volle Hoehe, damit die Ecke auch
   wirklich unten liegt. */
.vorschau-rahmen {
  min-height: 100vh;
  margin: 0;
  padding: var(--card-gap);
}

.vorschau-rahmen__hinweis { margin: 0; }

/* Die Logoflaeche: Vorschau, Aufforderung und Knopf untereinander, linksbuendig.
   `justify-items: start` verhindert, dass der Label-Knopf ueber die ganze
   Breite laeuft - er soll wie jeder andere Knopf aussehen. */
.logofeld {
  display: grid;
  gap: var(--card-gap);
  justify-items: start;
}

.logofeld__ziehen { margin: 0; }

/* DER 2FA-QR-CODE BLEIBT DUNKEL AUF HELL (Dark Mode, B5 a): Ein Telefon liest
   ihn sonst nicht. Um das Bild eine helle Flaeche als Schatten - sie aendert
   das Layout nicht und ist im hellen Modus weiss auf weiss. */
.qr-flaeche {
  display: inline-block;
  background: var(--immer-hell);
  border-radius: var(--radius);
  box-shadow: 0 0 0 var(--space-3) var(--immer-hell);
}

/* Das Dunkel-Logo steht in seiner Vorschau auf dunklem Grund - so, wie es im
   dunklen Chatfenster erscheint. */
.logofeld--dunkel .logo-vorschau {
  background: var(--logo-vorschau-dunkel);
  padding: var(--space-2);
  border-radius: var(--radius);
}

/* Waehrend eine Datei ueber der Flaeche schwebt. Gestrichelt statt gefaerbt:
   eine Umrandung sagt dasselbe und braucht keinen Farbwert. */
.logofeld--ueber { outline: 2px dashed currentColor; outline-offset: 4px; }

/* Das runde Profilbild im Kontomenue (Kontomenue-Umbau, 17.9.2026).

   EIN BUCHSTABE STATT EINES BILDES: Es gibt in diesem Projekt keine
   Profilfotos, und eines einzufuehren hiesse Upload, Zuschnitt, Speicherort
   und Loeschfrist - fuer ein Symbol, das der Anfangsbuchstabe genauso
   traegt.

   FARBEN AUS DER SEMANTISCHEN SCHICHT, nicht aus der Rampe: `--action` ist
   das Markenblau (#0570E4 ueber --blue-500), `--text-on-action` das Weiss
   darauf. Der direkte Griff auf `--blue-500` waere ein Verstoss gegen die
   Schichtung - tests/test_design_tokens.py haelt das fest, und es hat mich
   hier erwischt. */
.kontomenue__avatar {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: var(--radius-full);
  /* UMGEKEHRT SEIT DER BLAUEN FLAECHE (18.9.2026): Ein blauer Kreis auf
     blauem Grund waere unsichtbar. Weisser Kreis, blauer Buchstabe - und
     damit dieselben 4.77:1, nur andersherum. */
  background: var(--surface);
  color: var(--action-text);
  font-size: var(--text-sm);
  line-height: 1;
  font-weight: var(--weight-semibold);
  /* Der Buchstabe sitzt optisch mittig, auch wenn die Schrift Unterlaengen
     mitbringt. */
  text-transform: uppercase;
}

/* Die Trennlinien zwischen den Gruppen des Menues. `hr` und kein `border`
   an der Liste: Es ist eine inhaltliche Trennung, und genau dafuer gibt es
   das Element - ein Screenreader kuendigt es als solches an. */
.kontomenue__trenner {
  margin: var(--space-2) 0;
  border: 0;
  border-top: 1px solid var(--border);
}

/* =========================================================================
   DAS EINSTELLUNGS-FENSTER (Kapitel 29)
   =========================================================================
   Sieben Reiter in einem Fenster ueber der Seite - statt sieben Seiten mit
   eigener Navigation. Die Aufteilung folgt dem Entwurf: Leiste links,
   Inhalt rechts, Suchfeld oben in der Leiste.

   WARUM `position: fixed` UND KEIN `<dialog>`: Das native Element bringt
   eine eigene Fokusfalle und einen ::backdrop mit, dessen Farbe sich nur
   ueber ein Pseudoelement setzen laesst - und im Firefox jener Jahre noch
   uneinheitlich. Das Muster hier ist in beiden Faellen dasselbe Markup, mit
   oder ohne Skript, und das ist mehr wert als das eingebaute Verhalten.

   KEINE HOEHENANGABE IN VIEWPORT-EINHEITEN OHNE DECKEL. `min(640px, 86vh)`
   haelt das Fenster auf einem kleinen Laptop innerhalb des Bildschirms und
   auf einem grossen ruhig - ein Dialog, der bei jeder Fensterhoehe eine
   andere Form hat, wirkt wie ein Fehler. */
.einst {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
}

/* GESCHLOSSEN HEISST WEG - UND DIESE ZEILE IST DER GRUND, WARUM ES SIE GIBT.

   `display` aus einem Autoren-Stylesheet schlaegt das `display: none`, das
   der Browser an `[hidden]` haengt - unabhaengig von der Spezifitaet, weil
   das Browser-Stylesheet die schwaechste aller Ebenen ist. Die Regel
   darueber machte das geschlossene Fenster damit wieder sichtbar: ein
   bildschirmfuellender, durchsichtiger Kasten ueber JEDER Verwaltungsseite,
   der jeden Klick abfing.

   GEFUNDEN HAT ES DIE SUITE, nicht das Auge: Zwoelf Browser-Tests, die mit
   dem Fenster nichts zu tun haben, liefen ploetzlich in eine Zeitueberschreitung
   beim Klicken. Auf dem Bildschirm sah die Seite normal aus.

   `!important`, damit keine spaetere Regel dasselbe noch einmal anrichtet. */
.einst[hidden] { display: none !important; }

/* Der Grund - ein <div> und kein Knopf.

   ALS KNOPF ZOG ER DIE KNOPFFARBEN AN: `button:hover` setzt die Markenfarbe
   und ist spezifischer als diese Regel (Element plus Pseudoklasse schlaegt
   eine Klasse). Wer mit der Maus aus dem Fenster fuhr, sah statt der
   abgedunkelten Seite eine bildschirmfuellende blaue Flaeche. Die
   Begruendung steht ausfuehrlich in admin/dialog/_rahmen.html.

   `cursor: default` UND NICHT `pointer`: Der Klick hier ist eine
   Bequemlichkeit, kein angebotener Weg - ein Zeigefinger ueber der ganzen
   Seite verspraeche mehr, als hier steht. */
.einst__grund {
  position: absolute;
  inset: 0;
  background: var(--overlay);
  cursor: default;
}

/* GROESSER, UND DIE SCHRIFT DARIN EINE STUFE KLEINER (19.9.2026).

   Das ist eine Bewegung in zwei Richtungen, und sie gehoert zusammen: Bei
   1040x640 mit 15px-Text stand der Inhalt dicht an den Kanten und wirkte
   gedraengt - nicht weil zu viel darin stand, sondern weil die Schrift den
   Raum fuellte, den sonst der Abstand haette. Mehr Flaeche allein haette nur
   groessere Absaetze ergeben; kleinere Schrift allein haette gequetscht
   ausgesehen. Zusammen entsteht Luft.

   DIE SCHRIFTGROESSEN KOMMEN AUS DER BESTEHENDEN SKALA (tokens.css), je eine
   Stufe tiefer: --text-xl -> --text-lg fuer die Reiterueberschrift,
   --text-base -> --text-sm fuer Abschnittstitel, --text-sm -> --text-xs fuer
   Zeilen und Hinweise. Keine neue Zahl. */
.einst__fenster {
  position: relative;
  display: grid;
  grid-template-columns: 268px 1fr;
  width: min(1180px, 100%);
  height: min(760px, 90vh);
  /* Eine Stufe kleiner als die Seite dahinter. Ueber den Rahmen und nicht
     ueber jede Regel einzeln: Was drinnen keine eigene Groesse setzt - der
     Text in Tabellenzellen etwa - folgt damit von selbst. */
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
  overflow: hidden;
  border-radius: var(--radius-lg);
  background: var(--surface);
  /* Der Schatten traegt den Abstand allein - kein Rahmen dazu, siehe
     tokens.css. */
  box-shadow: var(--shadow-lg);
}

/* --- Die Leiste --------------------------------------------------------- */
.einst__leiste {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-5) var(--space-4);
  overflow-y: auto;
  background: var(--surface-sidebar);
  border-right: 1px solid var(--border);
}

.einst__suche {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0 var(--space-2);
  border: 1px solid var(--border-control);
  border-radius: var(--radius);
  background: var(--surface);
}
.einst__suche svg { width: 15px; height: 15px; flex: 0 0 auto; color: var(--icon-muted); }
/* DAS FELD TRAEGT KEINEN EIGENEN RAHMEN - der Kasten drumherum hat einen.
   Zwei Rahmen ineinander waeren eine doppelte Kante, und der Fokusring
   saesse am falschen der beiden. */
.einst__sucheingabe {
  flex: 1;
  min-width: 0;
  /* DIE KLEINE BEDIENHOEHE, nicht die volle: Das Feld steht ueber einer
     Liste kurzer Eintraege und war mit 44px das groesste Element der
     Leiste - Begruendung am Token in tokens.css.

     `min-height` MUSS MIT. Die gemeinsame Regel fuer Eingabefelder weiter
     oben in dieser Datei setzt `min-height: var(--control-height)`; nur
     `height` zu setzen laesst das Feld auf 44px stehen, ohne dass etwas
     rot wird - nachgemessen am 19.9.2026, die Zahl stand im Browser. */
  height: var(--control-height-sm);
  min-height: var(--control-height-sm);
  /* Den Innenabstand traegt der Kasten drumherum. */
  padding: 0;
  border: 0;
  /* `type="search"` bringt in WebKit eine eigene Zeichnung mit - sie legte
     einen zweiten, hellen Kasten in den Suchkasten. */
  -webkit-appearance: none;
  appearance: none;
  background: none;
  color: var(--text);
  font: inherit;
}
/* DER FOKUS WIRD AM KASTEN GEZEIGT, NICHT AM FELD.

   Die gemeinsame Regel fuer Eingabefelder legt bei Fokus einen 3px breiten
   Ring (--focus-halo) um das FELD. Hier sitzt das Feld aber in einem Kasten,
   der seinen eigenen Ring bekommt - zusammen ergab das einen Kasten im
   Kasten, gemessen am 19.9.2026 (`box-shadow: rgb(220,228,251) 0 0 0 3px`
   am Feld). Ein Bedienelement zeigt seinen Fokus einmal. */
.einst__sucheingabe:focus { outline: none; box-shadow: none; }
.einst__suche:focus-within {
  border-color: var(--focus-ring);
  box-shadow: var(--focus-halo);
}

.einst__gruppe {
  margin: var(--space-3) var(--space-3) 0;
  color: var(--text-muted-on-sidebar);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
  font-weight: var(--weight-semibold);
}

.einst__nav { display: flex; flex-direction: column; gap: var(--space-1); }

/* Der Eintrag. DREI MERKMALE IM AKTIVEN ZUSTAND wie in der Hauptnavigation:
   Hintergrund, Textfarbe und Schriftschnitt. Ein Unterschied allein in der
   Farbe traegt nicht durch eine Sehschwaeche. */
.einst__eintrag {
  display: flex;
  align-items: center;
  gap: var(--space-2-5);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius);
  color: var(--text);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
}
.einst__eintrag svg { width: 16px; height: 16px; flex: 0 0 auto; color: var(--icon-muted); }
.einst__eintrag:hover { background: var(--surface-sidebar-hover); text-decoration: none; }
.einst__eintrag[aria-current="page"] {
  background: var(--surface);
  color: var(--text-strong);
  font-weight: var(--weight-semibold);
  box-shadow: var(--shadow-sm);
}
.einst__eintrag[aria-current="page"] svg { color: var(--text-strong); }

/* AUSGEBLENDET HEISST WEG - dieselbe Falle wie bei `.einst[hidden]` oben,
   und sie ist hier ein zweites Mal zugeschnappt: Die Suche setzt `hidden`
   auf die Eintraege, `display: flex` schlug es, und die Leiste filterte
   sichtbar nichts. Der Test war gruen (das Attribut stand ja), der Screenshot
   zeigte alle sieben.

   MERKREGEL FUER DIESE DATEI: Wer einem Element `display` gibt, das ein
   Skript per `hidden` ausblendet, muss `[hidden]` hier mitschreiben. */
.einst__eintrag[hidden] { display: none !important; }
/* Der Knopf "Uebernehmen" beim Farbschema: nur ohne Skript sichtbar -
   einstellungen.js setzt `hidden`, und `.btn` setzt `display`. */
.einst-farbschema__knopf[hidden] { display: none !important; }

.einst__leer {
  margin: var(--space-2) var(--space-3);
  color: var(--text-muted-on-sidebar);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
}

/* --- Der Inhalt --------------------------------------------------------- */
.einst__inhalt {
  position: relative;
  min-width: 0;
  overflow-y: auto;
  padding: var(--space-8) var(--space-10) var(--space-10);
}

/* Der Schliessen-Knopf sitzt IM scrollenden Bereich und bleibt oben stehen
   (`sticky`). Fest positioniert waere er beim Scrollen ueber dem Text
   geschwebt; mitscrollend waere er nach einem Bildschirm Inhalt weg. */
.einst__schliessen {
  position: sticky;
  top: 0;
  float: right;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: var(--radius);
  color: var(--text-muted);
}
.einst__schliessen:hover { background: var(--surface-hover); color: var(--text-strong); }
.einst__schliessen svg { width: 18px; height: 18px; }

.einst__titel {
  margin: 0 0 var(--space-8);
  font-size: var(--text-lg);
  line-height: var(--text-lg-lh);
}

/* DIE BEDIENELEMENTE FOLGEN DER KLEINEREN SCHRIFT.

   Die gemeinsamen Regeln fuer Eingabefelder, Auswahlfelder und Knoepfe
   setzen eine eigene Groesse (--text-sm); ohne diese Zeilen stuenden
   15px-Felder neben 13px-Beschriftungen, und das saehe nicht nach einer
   kleineren Stufe aus, sondern nach einem Versehen.

   DIE HOEHE BLEIBT BEI --control-height. Sie ist eine Trefferflaeche und
   keine Schriftgroesse; ein 44px hohes Feld mit 13px Text ist genau die
   Luft, um die es bei diesem Umbau geht. Einzige Ausnahme ist das Suchfeld
   der Leiste, siehe dort. */
.einst__fenster input:not([type="checkbox"]):not([type="radio"]),
.einst__fenster select,
.einst__fenster textarea,
.einst__fenster button,
.einst__fenster .btn,
.einst__fenster table {
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
}

/* DER INNENRAUM SCHRUMPFT MIT, DIE TREFFERFLAECHE NICHT.

   Die gemeinsame Regel gibt Eingabefeldern 8/12px Innenabstand - bemessen
   fuer 15px-Text. Mit 13px sah das Feld daneben zu weit aus: gleich viel
   Luft um weniger Schrift wirkt nicht grosszuegig, sondern unfertig. Eine
   Stufe kleiner (6/10px) stellt das Verhaeltnis wieder her.

   `min-height` BLEIBT BEI --control-height, aus demselben Grund wie bei den
   Knoepfen: Das ist die Flaeche, die ein Finger oder eine zittrige Hand
   trifft, und die haengt nicht an der Schriftgroesse. Das Feld wird also
   nicht niedriger - nur sein Inhalt sitzt wieder im richtigen Verhaeltnis
   darin. Einzige Ausnahme bleibt das Suchfeld der Leiste (36px), das ueber
   einer Liste kurzer Eintraege steht. */
.einst__fenster input:not([type="checkbox"]):not([type="radio"]),
.einst__fenster select,
.einst__fenster textarea {
  padding: var(--space-1-5) var(--space-2-5);
}
/* Knoepfe ebenso: 16px seitlich waren auf 15px-Text bemessen. */
.einst__fenster button,
.einst__fenster .btn { padding: 0 var(--space-3); }
/* Das Suchfeld traegt seinen Innenabstand weiter am Kasten - siehe dort. */
.einst__fenster .einst__sucheingabe { padding: 0; }

.einst__panel { min-width: 0; }

/* Der Hinweis, wenn die Suche im offenen Reiter nichts findet. Er steht im
   Inhalt und nicht in der Leiste: Dort sagt schon `.einst__leer`, dass kein
   Reiter passt - hier geht es um die Abschnitte DIESES Reiters. */
.einst__panel-leer {
  margin: var(--space-6) 0 0;
  color: var(--text-muted);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
}

/* --- Ein Abschnitt und eine Zeile darin ---------------------------------
   DIE ZEILE IST DAS GRUNDMUSTER DES FENSTERS: links die Beschriftung mit
   ihrem Hinweis, rechts das Bedienelement. Sie traegt die Trennlinie nach
   unten, nicht der Abschnitt - so sitzt die Linie immer zwischen zwei
   Zeilen und nie unter der letzten. */
/* DER ABSTAND NACH UNTEN GEHOERT DEM ABSCHNITT - und der Reset dafuer
   gehoert dem letzten Abschnitt IM REITER, nicht irgendeinem letzten Kind.

   Vorher stand hier `.einst-block:last-child { margin-bottom: 0 }`. Das war
   als "der unterste Abschnitt braucht darunter nichts" gemeint, traf aber
   jeden Abschnitt, der zufaellig das letzte Kind seines Elternelements war -
   und das ist er immer, sobald ein <form> ihn umschliesst. In "Konto" und
   "Allgemeine Einstellungen" klebte der Speichern-Knopf deshalb mit GENAU
   NULL Pixeln an der naechsten Ueberschrift (nachgemessen im Browser am
   19.9.2026). Der Knopf selbst war unschuldig: Sein Innenabstand endet an
   der Kante des Abschnitts, und dahinter fehlte der Abstand.

   EIN <form> IM REITER IST EIN ABSCHNITT UNTER ABSCHNITTEN. Es traegt
   denselben Abstand nach unten wie ein Abschnitt, und der letzte Abschnitt
   DARIN traegt keinen mehr - sonst stuenden beide uebereinander. */
.einst-block { margin-bottom: var(--space-10); }
.einst__panel > .einst-block:last-child,
.einst__panel > form > .einst-block:last-child { margin-bottom: 0; }
.einst__panel > form { margin-bottom: var(--space-10); }
.einst__panel > form:last-child { margin-bottom: 0; }
.einst-block__titel {
  margin: 0 0 var(--space-1);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
  font-weight: var(--weight-semibold);
}
.einst-block__sub {
  margin: 0 0 var(--space-4);
  max-width: var(--prose-max);
  color: var(--text-muted);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
}

/* DIE ZEILE UMBRICHT, WENN DAS BEDIENELEMENT NICHT DANEBEN PASST.

   Ohne den Umbruch quetschte die Mehrwertsteuer-Zeile ihren Hinweistext in
   eine rund vierzig Pixel schmale Spalte und setzte jedes Wort auf eine
   eigene Zeile - die beiden Auswahlknoepfe daneben tragen lange
   Beschriftungen ("Meine Preise verstehen sich inklusive MwSt.") und liessen
   nichts uebrig. Gesehen im Browser am 19.9.2026, nicht im Test: Es ist ein
   Umbruch, kein Fehler im Markup.

   `flex: 1 1 260px` AM TEXT ist die eigentliche Regel. Sie sagt: unter 260px
   wird nicht mehr gestaucht, dann rutscht das Bedienelement nach unten. Ein
   fester `min-width` haette dasselbe getan und waere auf dem Telefon zur
   Ueberbreite geworden. */
/* DAS FENSTER "ZAHLUNGSMETHODE HINZUFUEGEN" (SECURITY.md 29.7 und 78).

   SEIT DEM 30.9.2026 EIN FENSTER IM FENSTER nach dem Vorbild von
   Claude.ai: abgedunkeltes Einstellungs-Fenster, darueber eine schmale
   Karte mit Titel, Kreuz und dem Formular. Vorher stand der Rahmen als
   weisse Flaeche mitten im Reiter.

   `absolute` UND NICHT `fixed`: Bezugsrahmen ist `.einst__fenster`
   (`position: relative`). Abgedunkelt wird das Einstellungs-Fenster, nicht
   die ganze Seite - die ist es schon. */
.zahl-modal {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  overflow-y: auto;
}
.zahl-modal__grund {
  position: absolute;
  inset: 0;
  background: var(--overlay);
  cursor: default;
}
.zahl-modal__karte {
  position: relative;
  width: min(460px, 100%);
  max-height: 100%;
  overflow-y: auto;
  padding: var(--space-6);
  border-radius: var(--radius-lg);
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-lg);
}
.zahl-modal__kopf {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}
.zahl-modal__titel {
  margin: 0;
  font-size: var(--text-lg);
  line-height: var(--text-lg-lh);
  font-weight: var(--weight-semibold);
  color: var(--text-strong);
}
.zahl-modal__zu {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--control-height-sm);
  height: var(--control-height-sm);
  border-radius: var(--radius);
  color: var(--text-muted);
}
.zahl-modal__zu:hover { background: var(--surface-hover); color: var(--text); }
.zahl-modal__zu:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }

/* DER RAHMEN SELBST. OHNE DIESE REGEL nimmt ein <iframe> seine
   Vorgabegroesse an: 300 mal 150 Pixel mit einem eingelassenen Rahmen.

   DIE HOEHE SETZT DAS INNERE DOKUMENT (stripe-zahlungsmethode.js) nach
   seinem Inhalt - Karte ausgeklappt ist kuerzer als Lastschrift. Der Wert
   hier gilt nur, bis Stripe geladen ist.

   KEINE EIGENE FLAECHE MEHR: Bis zum 30.9.2026 stand hier `--immer-hell`,
   weil das Formular immer hell war. Es folgt jetzt dem Modus und steht auf
   derselben Flaeche wie die Karte drumherum. */
/* STARTHOEHE 18rem, NICHT 320px (1.10.2026): 320px gehoert im Projekt dem
   Token der Startseiten-Tagesspalte (tests/test_week_axis.py). Der Wert ist
   nur ein Platzhalter, bis Stripe geladen ist - danach setzt das Skript die
   echte Hoehe. */
.einst-zahlrahmen {
  display: block;
  width: 100%;
  height: 18rem;
  border: 0;
  background: transparent;
}

/* Das Dokument IM Rahmen: dieselbe Flaeche wie `.zahl-modal__karte`,
   kein Innenabstand - den traegt die Karte. */
body:has(> .zahlrahmen) { margin: 0; background: var(--surface); }
.zahlrahmen { padding: 2px; }
.zahlform__element { min-height: 180px; }
.zahlform__hinweis {
  margin: var(--space-3) 0 0;
  color: var(--text-muted);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
}
.zahlform__knoepfe {
  display: flex;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-5);
}

.einst-zeile {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-4);
  padding: var(--space-5) 0;
  border-bottom: 1px solid var(--border);
}
.einst-zeile:last-child { border-bottom: 0; }

.einst-zeile__text {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  flex: 1 1 260px;
  min-width: 0;
}
.einst-zeile__label {
  color: var(--text-strong);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
}
.einst-zeile__hinweis {
  max-width: var(--prose-max);
  color: var(--text-muted);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
}

/* Der Verfall-Satz unter den Balken im Reiter "Nutzung" (SECURITY.md 82.6):
   klein wie die Zeilenhinweise, mit Abstand zur letzten Zeile. */
.einst-verfall {
  margin: var(--space-4) 0 0;
}

/* WIEDER SCHMALER (19.9.2026). Mit der groesseren Fensterbreite waren 300px
   mitgewachsen - aber ein Eingabefeld ist nicht deshalb breiter zu machen,
   weil daneben Platz ist. Es soll so breit sein wie das, was hineingehoert:
   ein Name, eine Adresse, ein Prozentsatz. */
.einst-feld { width: min(260px, 100%); flex: 0 0 auto; }
.einst-feld--kurz { width: min(150px, 100%); }

.einst-aktion {
  display: flex;
  justify-content: flex-end;
  padding-top: var(--space-4);
}
/* Steht die Knopfzeile hinter einem ganzen Abschnitt statt hinter einer
   Zeile, traegt schon dessen Abstand die Luft darueber - zusammen waeren es
   56px, und der Knopf sieht dann aus, als gehoere er nicht mehr dazu. */
.einst-block + .einst-aktion { padding-top: 0; }

.einst-wert {
  color: var(--text);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
}

/* Das Profilbild im Fenster - derselbe Buchstabe wie im Kontomenue, nur
   groesser. */
.einst-avatar {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  flex: 0 0 auto;
  border-radius: var(--radius-full);
  background: var(--action);
  color: var(--text-on-action);
  font-weight: var(--weight-semibold);
}

/* Die Organisationskennung. Monospace, weil sie zum Vergleichen und
   Abtippen da ist - und in einem Kasten, damit klar ist, wo sie aufhoert. */
.einst-kennung {
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--surface-sunken);
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
  overflow-wrap: anywhere;
}

/* --- Der Schalter -------------------------------------------------------
   EIN ECHTES KONTROLLKAESTCHEN, nur anders gezeichnet. Kein <div> mit
   Klick-Handler: Ein Kaestchen kennt die Tastatur, den Formularkoerper und
   den Screenreader von sich aus - ein nachgebauter Schalter kennt nichts
   davon, bis man es einzeln nachruestet.

   DAS KAESTCHEN LIEGT UEBER DER BAHN und ist durchsichtig. `opacity: 0` und
   nicht `display: none`: Ein ausgeblendetes Feld ist fuer die Tastatur nicht
   erreichbar, und dann liesse sich der Schalter nicht mehr bedienen. */
.einst-schalter {
  position: relative;
  display: inline-flex;
  flex: 0 0 auto;
  width: 40px;
  height: 22px;
}
.einst-schalter input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.einst-schalter__bahn {
  position: absolute;
  inset: 0;
  border-radius: var(--radius-full);
  background: var(--switch-track);
  transition: background 120ms ease;
}
.einst-schalter__bahn::after {
  content: "";
  position: absolute;
  top: 3px;
  left: 3px;
  width: 16px;
  height: 16px;
  border-radius: var(--radius-full);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  transition: transform 120ms ease;
}
.einst-schalter input:checked + .einst-schalter__bahn { background: var(--action); }
.einst-schalter input:checked + .einst-schalter__bahn::after { transform: translateX(18px); }
/* DER FOKUSRING SITZT AUF DER BAHN, weil das Kaestchen unsichtbar ist. Ohne
   ihn waere der Schalter mit der Tastatur bedienbar, aber unauffindbar. */
.einst-schalter input:focus-visible + .einst-schalter__bahn {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

.einst-wahl {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  /* `1 0 auto` und nicht `0 0 auto`: Rutscht die Auswahl in die naechste
     Zeile, soll sie die Breite auch nutzen duerfen. */
  flex: 1 0 auto;
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
}
.einst-wahl label { display: inline-flex; align-items: center; gap: var(--space-2); }

/* --- Abrechnung: der Tarifkopf ------------------------------------------ */
.einst-tarif {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding-bottom: var(--space-4);
}
.einst-tarif__symbol { flex: 0 0 auto; color: var(--icon-muted); }
.einst-tarif__symbol svg { width: 40px; height: 40px; }
.einst-tarif__text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--space-1); }
.einst-tarif__name {
  margin: 0;
  font-size: var(--text-base);
  line-height: var(--text-base-lh);
  font-weight: var(--weight-semibold);
}

/* --- Nutzung: die Balkenzeile ------------------------------------------- */
.einst-verbrauch {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin: 0 0 var(--space-6);
}
.einst-balken {
  display: grid;
  grid-template-columns: 200px 1fr auto;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4) 0;
  border-bottom: 1px solid var(--border);
}
.einst-balken:last-child { border-bottom: 0; }
/* DUENNE BALKEN IM NUTZUNGSDIALOG (L20, SECURITY.md Kapitel 41.4): 4 px statt
   der 10 px der Tarifseite - nur hier, die uebrigen Balken bleiben. */
.einst-balken .meter { height: 4px; }
.einst-balken__text { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.einst-balken__zahl {
  color: var(--text-muted);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
  white-space: nowrap;
}

/* --- Die eine Fehlerantwort --------------------------------------------- */
.einst-verboten {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  /* Hoch genug, dass die Meldung mittig im Fenster steht statt oben zu
     kleben. KEINE 320px: Diese Zahl gehoert im Projekt dem Token
     --preview-entries-height, und tests/test_week_axis.py haelt fest, dass
     sie sonst nirgends als Zahl auftaucht. */
  min-height: 280px;
  text-align: center;
  color: var(--text-muted);
}
.einst-verboten svg { width: 32px; height: 32px; color: var(--icon-muted); }
.einst-verboten__titel {
  margin: 0;
  color: var(--text-strong);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
  font-weight: var(--weight-semibold);
}
.einst-verboten__text { margin: 0; font-size: var(--text-xs); line-height: var(--text-xs-lh); }

/* Die Seite dahinter scrollt nicht mit, solange das Fenster offen ist.
   Ueber eine Klasse und nicht ueber einen Inline-Stil - die CSP erlaubt kein
   `unsafe-inline` fuer Stile. */
body.einst-offen { overflow: hidden; }

/* --- Auf kleinen Schirmen ----------------------------------------------
   UNTER 900px WIRD AUS DEM FENSTER EINE FLAECHE. Zwei Spalten mit 240px
   Leiste lassen auf einem Telefon keine 160px fuer den Inhalt; die Leiste
   wandert deshalb nach oben und laeuft waagerecht.

   RANDLOS UND UEBER DIE VOLLE HOEHE: Ein Dialog mit 16px Luft ringsum wirkt
   auf einem kleinen Schirm nicht leichter, sondern nur enger. */
@media (max-width: 900px) {
  .einst { padding: 0; }
  .einst__fenster {
    grid-template-columns: 1fr;
    grid-template-rows: auto 1fr;
    width: 100%;
    height: 100%;
    border-radius: 0;
  }
  .einst__leiste {
    border-right: 0;
    border-bottom: 1px solid var(--border);
    overflow-x: auto;
  }
  .einst__nav { flex-direction: row; flex-wrap: wrap; }
  .einst__gruppe { display: none; }
  .einst__inhalt { padding: var(--space-6) var(--space-5) var(--space-10); }
  .einst-zeile { flex-direction: column; align-items: stretch; }
  .einst-feld, .einst-feld--kurz { width: 100%; }
  .einst-balken { grid-template-columns: 1fr; gap: var(--space-2); }
}

/* =========================================================================
   Das Video-Gespraech (SECURITY.md Kapitel 32.12)
   =========================================================================
   EIN KOMPAKTES FENSTER, KEIN VOLLBILD-ERLEBNIS. Zwei Kacheln und eine
   schmale Leiste - so klein, dass es neben einem Formular stehen koennte,
   und so gross, dass man ein Gesicht erkennt. Wer mehr will, drueckt den
   Vollbildknopf; das ist ein Klassenwechsel und kein Neuaufbau.

   DIE BUEHNE IST DAS VOLLBILD-ELEMENT. Sie und nicht die ganze Seite geht
   in den Vollbildmodus - sonst schwebte die Steuerleiste ueber schwarzem
   Grund, und der Aufzeichnungshinweis verschwaende aus dem Blick. */
/* DIE SEITE FUELLT IHRE HOEHE, statt sich aus ihrem Inhalt zu ergeben.
   Das ist nicht Geschmack, sondern die Bedingung dafuer, dass sie sich
   RAHMEN LAESST: Im Fenster der Verwaltung ist die Hoehe vorgegeben. Mit
   einer Buehne, die stur ihr Seitenverhaeltnis behaelt, rutschte die
   Steuerleiste darunter aus dem Rahmen - man kaeme weder an "Auflegen"
   noch an "Stumm" und saehe nicht einmal, dass es sie gibt.

   `100dvh` UND NICHT `100vh`: Auf Mobilgeraeten zaehlt vh die Adresszeile
   mit, die beim Scrollen verschwindet - die Leiste laege dann bei jedem
   zweiten Aufruf knapp unter dem Rand. */
/* Inhalt UND Fusszeile in einer Spalte - siehe den Kommentar in
   app/templates/meeting.html. Die Hoehe steht hier, damit der Inhalt
   darunter weiss, wovon er seinen Anteil nimmt. */
.gespraech-rahmenseite {
  /* `height` UND NICHT `min-height`, und das ist der Unterschied, an dem es
     haengt: Eine Spalte mit `min-height` darf WACHSEN, wenn ihr Inhalt
     nicht passt - sie schiebt die Steuerleiste dann einfach nach unten
     hinaus. Erst eine feste Hoehe zwingt die Buehne zu schrumpfen.
     `overflow: auto` ist das Ventil fuer den Fall, dass selbst Leiste,
     Hinweis und Fusszeile allein nicht mehr passen. */
  height: 100dvh;
  overflow: auto;
  display: flex;
  flex-direction: column;
}

.gespraech-seite {
  /* `--content-max` UND NICHT MEHR 56rem. Die Kappe war fuer die
     eigenstaendige Seite gedacht und wurde zum Flaschenhals, sobald das
     Gespraech gerahmt in der Verwaltung laeuft: Der Rahmen war 1054px
     breit, die Seite darin hoerte bei 896px auf - 158px blieben leer, und
     die Buehne stand bei einem Verhaeltnis von 1,31 statt der 1,78, die
     zwei Videokacheln wollen (gemessen bei 1440px Fensterbreite).

     DERSELBE TOKEN WIE DIE INHALTSSPALTE DER VERWALTUNG, damit das
     Gespraech nicht breiter werden kann als die Seite, die es rahmt - und
     damit die eigenstaendige Fassung auf einem grossen Schirm dieselbe
     Grenze hat wie alles andere im Projekt. */
  max-width: var(--content-max);
  width: 100%;
  margin: 0 auto;
  padding: var(--space-6) var(--space-4) var(--space-6);
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.gespraech {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  /* Nimmt, was die Seite uebrig laesst - und gibt es an die Buehne weiter. */
  flex: 1 1 auto;
  min-height: 0;
}

.gespraech__buehne {
  position: relative;
  display: grid;
  /* Eine Kachel fuellt, zwei teilen sich - `auto-fit` erledigt beides ohne
     eine zweite Regel. Bei mehr als zwei Gegenstellen (Mesh traegt vier)
     bricht es von selbst um. */
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: var(--space-2);
  /* DAS SEITENVERHAELTNIS IST EIN WUNSCH, KEINE VORSCHRIFT: `flex` mit
     `min-height: 0` laesst die Buehne schrumpfen, wenn die Hoehe knapp ist
     (gerahmt, kleines Fenster, Querformat auf dem Handy). Die Kacheln
     behalten ihren Bildausschnitt ueber `object-fit: cover`. */
  aspect-ratio: 16 / 9;
  flex: 1 1 auto;
  min-height: 8rem;
  background: var(--gespraech-buehne);
  border-radius: var(--radius-lg);
  overflow: hidden;
}

/* Die Kachel ist der Behaelter; Bild und Platzhalter liegen darin
   uebereinander. Ohne diesen Behaelter haette der Platzhalter nichts, worauf
   er sich beziehen koennte - `position: absolute` bezoege sich dann auf die
   ganze Buehne, und bei zwei Gegenstellen laege er ueber beiden. */
.gespraech__kachel {
  position: relative;
  min-height: 0;
  background: var(--gespraech-kachel);
  overflow: hidden;
}

.gespraech__fern {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: var(--gespraech-kachel);
}

/* DER PLATZHALTER LIEGT UEBER DEM BILD, statt es zu ersetzen: Das
   <video>-Element behaelt seinen Strom, und beim Wiedereinschalten ist das
   Bild sofort da - ohne neue Aushandlung. */
.gespraech__platzhalter {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  background: var(--gespraech-kachel);
  text-align: center;
  padding: var(--space-4);
}

.gespraech__avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 4.5rem;
  height: 4.5rem;
  border-radius: var(--radius-full);
  background: var(--gespraech-avatar-bg);
  color: var(--gespraech-avatar-text);
  font-size: var(--text-3xl);
  line-height: 1;
  font-weight: var(--weight-semibold);
}

/* AUSGEBLENDET HEISST WEG - dieselbe Falle wie bei `.einst[hidden]` weiter
   oben, und sie hat hier ein drittes Mal zugeschlagen: `display: flex`
   schlaegt das `hidden`-Attribut, weil eine Klassenregel spezifischer ist
   als die Browservorgabe daran. Ohne diese Zeile bleibt der Platzhalter
   stehen, nachdem die Kamera wieder an ist - das Bild laeuft dann
   unsichtbar dahinter. Wer hier `display` setzt, muss `[hidden]`
   mitschreiben. */
.gespraech__platzhalter[hidden] { display: none !important; }

.gespraech__platzhalter-text {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  color: var(--gespraech-text);
  font-size: var(--text-base);
  line-height: var(--text-base-lh);
}

/* Der Grund steht kleiner und leiser als die Rolle: WER da sitzt ist die
   Auskunft, WARUM man ihn nicht sieht die Fussnote. */
.gespraech__platzhalter-grund {
  color: var(--gespraech-text-leise);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
}

/* Im eigenen kleinen Bild ist fuer Text kein Platz - dort steht nur das
   durchgestrichene Kamerasymbol. Die Aussage traegt der versteckte Text
   daneben, den ein Screenreader liest. */
.gespraech__platzhalter--klein { gap: 0; padding: 0; }
.gespraech__platzhalter--klein .gespraech__avatar {
  width: 2.25rem;
  height: 2.25rem;
  background: transparent;
  color: var(--gespraech-text-leise);
}

/* DAS EIGENE BILD LIEGT OBEN AUF und ist klein: Es dient der Kontrolle
   ("sitze ich im Bild?"), nicht dem Gespraech. */
.gespraech__nah {
  position: absolute;
  right: var(--space-3);
  bottom: var(--space-3);
  width: clamp(6rem, 22%, 11rem);
  aspect-ratio: 4 / 3;
  border-radius: var(--radius);
  border: 2px solid var(--gespraech-rahmen);
  box-shadow: var(--shadow);
  background: var(--gespraech-nah);
  overflow: hidden;
}

/* GESPIEGELT, weil ein ungespiegeltes Selbstbild irritiert - man hebt die
   linke Hand und sieht sie rechts. Die Gegenseite sieht es NICHT
   gespiegelt; das betrifft nur die Anzeige hier. */
.gespraech__nah video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scaleX(-1);
}

/* Der Status steht AUF DER BUEHNE und nicht darunter: Solange noch niemand
   da ist, ist sie schwarz - und ein Satz unter einem schwarzen Rechteck
   liest sich wie eine Bildunterschrift statt wie eine Auskunft.

   AM UNTEREN RAND UND NICHT IN DER MITTE, seit es den Platzhalter fuer die
   abgeschaltete Kamera gibt: Beide waren zentriert, und "Verbunden" landete
   mitten zwischen Kreis und Rolle. Unten links stoert er weder den
   Platzhalter noch das eigene Bild, das rechts sitzt.

   EIGENE FLAECHE STATT SCHATTEN: Ein Textschatten ist ueber hellem Video
   grenzwertig lesbar; eine Pille in der Buehnenfarbe ist es immer - und sie
   benutzt denselben Token wie der Grund, auf dem sie liegt. */
.gespraech__status {
  position: absolute;
  left: var(--space-3);
  bottom: var(--space-3);
  margin: 0;
  max-width: calc(100% - 12rem);
  padding: var(--space-1) var(--space-2-5);
  border-radius: var(--radius-full);
  background: var(--gespraech-buehne);
  color: var(--gespraech-text);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
}

/* SOBALD DAS BILD DA IST, SAGT DAS BILD ES BESSER. Der Text verschwindet
   nach ein paar Sekunden - nicht sofort, damit ein Screenreader ihn noch
   vorliest, und nicht nie, damit er der Gegenseite nicht dauerhaft im
   Gesicht klebt. `visibility` statt `display`, weil ein entferntes Element
   aus dem `aria-live`-Bereich fiele und die naechste Meldung dann nicht mehr
   angesagt wuerde. */
.gespraech__status--weg {
  visibility: hidden;
}

.gespraech__leiste {
  display: flex;
  justify-content: center;
  gap: var(--space-2);
}

.gespraech__knopf {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 44 Pixel: die kleinste Flaeche, die sich mit dem Daumen sicher treffen
     laesst. Ein Knopf, den man beim Auflegen verfehlt, ist hier besonders
     aergerlich. */
  width: 2.75rem;
  height: 2.75rem;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
}

/* Dieselbe Falle wie beim Platzhalter: Kamera- und Teilen-Knopf stehen im
   Markup mit `hidden` und werden erst vom Skript freigegeben, wenn das
   Geraet die Funktion ueberhaupt hat. Ohne diese Zeile waeren sie von
   Anfang an sichtbar und taeten beim Klick nichts. */
.gespraech__knopf[hidden] { display: none !important; }

.gespraech__knopf:hover { background: var(--surface-hover); }
.gespraech__knopf:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

/* DER GEDRUECKTE ZUSTAND HAT EINE EIGENE FLAECHE, nicht nur eine andere
   Farbe: `aria-pressed` traegt ihn fuer den Screenreader, das Gegensymbol
   fuer das Auge, und diese Flaeche fuer den schnellen Blick. Drei Wege zur
   selben Aussage - einer davon funktioniert immer. */
.gespraech__knopf[aria-pressed="true"] {
  background: var(--action-subtle);
  border-color: var(--action);
  color: var(--action-on-subtle);
}

.gespraech__knopf--auflegen {
  background: var(--danger-solid);
  border-color: var(--danger-solid);
  color: var(--text-on-action);
}
.gespraech__knopf--auflegen:hover { background: var(--danger-text); }

.gespraech__hinweis {
  margin: 0;
  text-align: center;
  color: var(--text-muted);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
}

/* --- Vollbild ------------------------------------------------------------
   NUR GROESSE UND ABSTAENDE. Es entsteht kein Element neu, es wird kein
   Track neu ausgehandelt - die Medienstroeme laufen waehrend des Wechsels
   weiter. Genau deshalb ist der Vollbildmodus hier eine CSS-Frage und keine
   WebRTC-Frage. */
.gespraech:fullscreen {
  width: 100%;
  height: 100%;
  padding: var(--space-3);
  background: var(--gespraech-buehne);
}

.gespraech:fullscreen .gespraech__buehne {
  /* Kein festes Seitenverhaeltnis mehr: Im Vollbild bestimmt der Bildschirm
     die Form, und die Buehne nimmt, was uebrig bleibt, nachdem Leiste und
     Hinweis ihren Platz haben. */
  aspect-ratio: auto;
  flex: 1 1 auto;
  min-height: 0;
  border-radius: var(--radius);
}

.gespraech:fullscreen .gespraech__nah {
  width: clamp(8rem, 18%, 16rem);
}

/* Der Hinweis bleibt auch im Vollbild lesbar - er ist der einzige Text, der
   dort ueberhaupt noch steht, und der wichtigste. */
.gespraech:fullscreen .gespraech__hinweis {
  color: var(--gespraech-text-leise);
}

@media (max-width: 640px) {
  .gespraech-seite { padding: var(--space-3) var(--space-2) var(--space-4); }
  .gespraech__buehne { aspect-ratio: 3 / 4; }
  .gespraech__nah { width: clamp(5rem, 28%, 8rem); }
}

/* Wer "weniger Bewegung" eingestellt hat, meint nicht das Videobild - das
   IST die Sache. Gemeint sind Uebergaenge, und die gibt es hier ohnehin
   keine. Diese Regel steht trotzdem da, damit niemand spaeter eine
   einbaut, ohne darueber nachzudenken. */
@media (prefers-reduced-motion: reduce) {
  .gespraech * { transition: none !important; animation: none !important; }
}

/* Das eingebettete Gespraechsfenster auf der Terminseite (Kapitel 32.12).
   Der Rahmen bringt sein eigenes Layout mit; hier steht nur, wie gross er
   sein darf und dass er keinen eigenen Rand zieht. */
.gespraech-einbettung {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-3);
}

.gespraech-einbettung__rahmen {
  /* UEBER DIE POLSTERUNG DER KARTE HINAUS. Der Rahmen ist kein Textabsatz,
     der Rand braucht - er ist ein Bild, und jeder Pixel daneben ist einer,
     den zwei Gesichter nicht haben. Die Knoepfe darueber und darunter
     behalten ihren Abstand; nur der Rahmen tritt heraus, und zwar um genau
     den Token, der ihn einrueckt. */
  width: calc(100% + 2 * var(--card-padding));
  margin-inline: calc(-1 * var(--card-padding));
  /* DIE HOEHE FOLGT AUS DER BREITE, statt eine Zahl zu sein.
     32rem waren zu gedrungen: Bei rund 900px Breite blieb fuer die Buehne
     ein flacher Streifen, in dem zwei Gesichter nebeneinander winzig
     wurden - und die Leiste drueckte von unten dagegen.

     4:3 UND NICHT 16:9, obwohl die Kacheln 16:9 sind: Im Rahmen stehen
     unter der Buehne noch Steuerleiste, Aufzeichnungshinweis und
     Fussnote. Waere der Rahmen selbst 16:9, bliebe der Buehne ein 21:9-
     Schlitz. Mit 4:3 bekommt sie ungefaehr ihre 16:9 und der Rest seinen
     Platz - nachgemessen im Browsertest, der verlangt, dass der
     Auflegen-Knopf im Rahmen liegt.

     `max-height` IN VIEWPORT-EINHEITEN und nicht in rem: Die Grenze ist
     der Bildschirm, nicht die Schriftgroesse. Auf einem niedrigen Fenster
     schrumpft der Rahmen, statt die Seite zu sprengen; die Buehne darin
     schrumpft mit (siehe .gespraech__buehne). */
  aspect-ratio: 4 / 3;
  max-height: 80dvh;
  border: 0;
  border-radius: var(--radius);
  background: var(--gespraech-buehne);
}

/* Dieselbe Falle wie ueberall sonst in diesem Stylesheet: `display` schlaegt
   `[hidden]`. Hier traf es `.btn` (`display: inline-flex`) - "Gespraech
   oeffnen" blieb nach dem Klick stehen, obwohl das Skript es ausgeblendet
   hatte.
   EINE REGEL FUER DEN GANZEN BAUSTEIN statt einer je Element: Rahmen,
   Hinweis und beide Knoepfe werden per Skript umgeschaltet, und der
   naechste, der dazukommt, ist sonst der naechste Fehler. */
.gespraech-einbettung [hidden] { display: none !important; }

@media (max-width: 640px) {
  /* Auf dem Handy steht die Buehne hochkant (siehe die Regel dort), also
     darf der Rahmen es auch - sonst bliebe rechts und links leerer Grund. */
  .gespraech-einbettung__rahmen { aspect-ratio: 3 / 4; }
}

/* Die Tuer: Nur die Fahrschule sieht sie, und nur solange jemand wartet.
   MITTIG UEBER DER BUEHNE, weil sie eine Handlung verlangt - der
   Verbindungstext unten links tut das nicht und darf deshalb dort bleiben. */
.gespraech__einlass {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-5);
  border-radius: var(--radius-lg);
  background: var(--gespraech-kachel);
  box-shadow: var(--shadow-lg);
  text-align: center;
  max-width: 90%;
}

.gespraech__einlass-text {
  margin: 0;
  color: var(--gespraech-text);
  font-size: var(--text-base);
  line-height: var(--text-base-lh);
}

/* `display` schlaegt `[hidden]` - dieselbe Zeile wie ueberall sonst. */
.gespraech__einlass[hidden] { display: none !important; }

/* --- Der Standortwaehler an der Kartenueberschrift (Kapitel 31) ----------
   Dem Sprachbaustein des Chatfensters nachgebaut: Wert, Pfeil, Liste
   darunter. Er sitzt IM Kartenkopf zwischen Titel und Zaehlzeile - dort,
   wo im Vorbild der Sprachname steht.

   `details`/`summary` traegt das Auf- und Zuklappen ohne Skript. Der
   Marker des Browsers faellt weg; der Pfeil ist unser eigenes Icon und
   dreht sich beim Oeffnen. */
/* TITEL UND AUSWAEHLER ALS EINE ZEILE. Der Kartenkopf verteilt mit
   `space-between`; ohne diese Gruppe landete der Auswaehler als drittes
   Kind in der Mitte - ein eigenes Bedienelement ueber dem Kalender statt
   ein Teil der Ueberschrift. */
.card__titelgruppe {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-2);
  min-width: 0;
}
.card__trenner { color: var(--text-muted); }

.ortwahl { position: relative; }
.ortwahl__knopf {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius);
  color: var(--text-muted);
  font-size: var(--text-sm);
  cursor: pointer;
  list-style: none;
}
.ortwahl__knopf::-webkit-details-marker { display: none; }
.ortwahl__knopf:hover { background: var(--surface-hover); color: var(--text); }
.ortwahl__knopf svg { width: 1rem; height: 1rem; transition: transform .15s ease; }
.ortwahl[open] > .ortwahl__knopf svg { transform: rotate(180deg); }
.ortwahl__wert { font-weight: var(--weight-medium); }

/* DIE LISTE SCHWEBT UEBER DER KARTE, statt sie auseinanderzuschieben:
   Ein aufklappender Block im Kartenkopf ruckte das ganze Raster darunter
   nach unten. `z-index` ueber den Kaestchen, damit sie nicht durchscheinen. */
.ortwahl__liste {
  position: absolute;
  top: calc(100% + var(--space-1));
  right: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 12rem;
  padding: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow);
}
.ortwahl__wahl {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  white-space: nowrap;
}

/* --- Die Hinweise im persoenlichen Kalender (SECURITY.md Kapitel 37) --- */
.hinweise { margin-bottom: var(--space-6); }
.hinweise__liste {
  list-style: none;
  margin: 0;
  padding: 0;
}
.hinweise__eintrag {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
  align-items: baseline;
  padding: var(--space-3) var(--card-padding);
  border-top: 1px solid var(--border);
}
.hinweise__eintrag--neu { font-weight: var(--weight-semibold); }

/* --- Team-Nachrichten (SECURITY.md Kapitel 38) ------------------------- */
.gespraeche { list-style: none; margin: 0; padding: 0; }
.gespraeche__eintrag { border-top: 1px solid var(--border); }
.gespraeche__eintrag:first-child { border-top: 0; }
.gespraeche__eintrag--zeile,
.gespraeche__link {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
  align-items: center;
  justify-content: space-between;
  padding: var(--space-3) var(--card-padding);
}
.gespraeche__link { color: var(--text); }
.gespraeche__link:hover { background: var(--surface-sunken); text-decoration: none; }
.gespraeche__titel {
  display: inline-flex;
  gap: var(--space-2);
  align-items: center;
  min-width: 0;
  overflow-wrap: anywhere;
  font-weight: var(--weight-semibold);
}
.gespraeche__titel svg { width: 16px; height: 16px; flex: 0 0 auto; }
.gespraeche__meta { display: inline-flex; gap: var(--space-2); align-items: center; }
/* --- Der Verlauf als Sprechblasen (Kapitel 40) --------------------------
   EIGENE rechts in der Markenfarbe (--action ist in beiden Modi #0570E4,
   weisser Text 4.73:1), FREMDE links in --surface-raised mit --text. Die
   Ecke zur eigenen Seite bleibt spitz. Aufeinanderfolgende Nachrichten
   derselben Person (`--folge`) stehen enger, ein Personenwechsel setzt
   mehr Abstand. */
.chat {
  --chat-breite: 48rem;
  display: flex;
  flex-direction: column;
  min-height: calc(100dvh - 16rem);
}
.chatverlauf {
  flex: 1;
  list-style: none;
  margin: 0;
  padding: var(--space-6) 0 var(--space-4);
}
.chat__leer { flex: 1; margin: var(--space-6) 0; text-align: center; }
.nachricht {
  --blase-radius: 18px;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  margin-top: var(--space-4);
}
.nachricht:first-child { margin-top: 0; }
.nachricht--folge { margin-top: var(--space-1); }
.nachricht--eigen { align-items: flex-end; }
.nachricht__kopf {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  margin: 0 0 var(--space-1);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
}
.nachricht__avatar {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: var(--radius-full);
  background: var(--action-subtle);
  color: var(--action-on-subtle);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  line-height: 1;
}
.nachricht__name { min-width: 0; overflow-wrap: anywhere; font-weight: var(--weight-semibold); }
.nachricht__blase {
  max-width: min(75%, 40rem);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--blase-radius) var(--blase-radius) var(--blase-radius) 4px;
  background: var(--surface-raised);
  color: var(--text);
}
.nachricht--eigen .nachricht__blase {
  border-radius: var(--blase-radius) var(--blase-radius) 4px var(--blase-radius);
  background: var(--action);
  color: var(--text-on-action);
}
/* Geloescht: eine leere, gestrichelte Blase in beiden Richtungen - gedaempfter
   Text auf Blau haette AA verfehlt. */
.nachricht .nachricht__blase--geloescht {
  background: transparent;
  border: 1px dashed var(--border-strong);
  color: var(--text-muted);
}
/* NUR TEXT: Umbrueche bleiben erhalten, ohne dass je Markup entsteht. */
.nachricht__text {
  margin: 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.nachricht__text--geloescht { font-style: italic; }
.nachricht__fuss {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--space-1);
  align-items: center;
  margin-top: 2px;
}
.nachricht--eigen .nachricht__fuss { flex-direction: row-reverse; }
/* Das Loeschen-Formular traegt sonst den Rand von .inline-form (unten 8px)
   und sitzt 4px hoeher als Stift und Zeit. */
.nachricht__fuss .inline-form { margin: 0; }
.nachricht__meta {
  margin: 0;
  color: var(--text-muted);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
}
.nachricht__aktion {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  min-height: 0;
  padding: 0;
  border: 0;
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--icon-muted);
  cursor: pointer;
}
.nachricht__aktion svg { width: 14px; height: 14px; }
.nachricht__aktion:hover { background: var(--surface-sunken); color: var(--text); text-decoration: none; }
/* `display` schlaegt `[hidden]` - das Skript blendet den Knopf so aus. */
.nachrichten__gelesen { margin: var(--space-2) 0 var(--space-4); }
.nachrichten__gelesen[hidden] { display: none; }
/* Der Echtzeit-Hinweis (Kapitel 38.11) - `.banner` setzt `display`, und
   `display` schlaegt `[hidden]`. */
.nachrichten__neu[hidden] { display: none; }
/* Anhaenge (Kapitel 38.12). */
.nachricht__anhang { margin: var(--space-2) 0 0; }
.nachricht__anhang:first-child { margin-top: 0; }
.nachricht__bild {
  display: block;
  max-width: min(100%, 20rem);
  max-height: 16rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
}
/* Das Anhang-Formular zeigt nur noch seine Fehlermeldung; gewaehlt wird
   ueber die Bueroklammer im Eingabefeld (Kapitel 40). */
.nachrichten__anhangform { margin-top: var(--space-2); }
.nachrichten__anhangform[hidden],
.nachrichten__anhangform [hidden] { display: none; }
/* Oeffentliche Gruppen (Kapitel 38.13): Beschreibung in Liste und Kopf. */
.gespraeche__beschreibung,
.gespraech__beschreibung {
  flex-basis: 100%;
  margin: 0;
  color: var(--text-muted);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* --- Drei-Spalten-Aufbau des Team-Chats (Kapitel 39.3, 40.6) -----------
   Die Liste nutzt DIESELBEN Bausteine wie die Seitenleiste links
   (`navitem`, `sidebar__label`) und ihren Grund - oben die Aktionen, darunter
   die Unterhaltungen. Unter 900px ein Block im Seitenfluss; ab 901px der
   feste Rahmen weiter unten. */
.teamchat {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-4);
}
.teamchat__liste {
  min-width: 0;
  padding: var(--space-4) var(--space-3);
  border-radius: var(--radius-lg);
  background: var(--surface-sidebar);
}
.teamchat__aktionen {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-bottom: var(--space-3);
  margin-bottom: var(--space-2);
  border-bottom: 1px solid var(--border);
}
.teamchat__neu > summary { cursor: pointer; list-style: none; }
.teamchat__neu > summary::-webkit-details-marker { display: none; }
.teamchat__neu[open] > summary { background: var(--surface-sidebar-hover); }
.teamchat__neu-form {
  display: grid;
  gap: var(--space-2);
  margin: var(--space-2) var(--space-1) var(--space-2);
  padding: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
}
.teamchat__neu-form select { max-width: none; }
.teamchat__abschnitt.sidebar__label { margin: var(--space-4) 0 var(--space-1); }
.teamchat__eintraege {
  display: flex;
  flex-direction: column;
  gap: 2px;
  list-style: none;
  margin: 0;
  padding: 0;
}
.teamchat__eintrag { font-weight: var(--weight-medium, 500); }
.teamchat__marke {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: var(--radius-full);
  background: var(--surface-raised);
  color: var(--text);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  line-height: 1;
}
.teamchat__marke--gruppe { border-radius: 7px; background: var(--action-subtle); color: var(--action-on-subtle); }
.teamchat__name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.teamchat__ehemalig { color: var(--text-muted-on-sidebar); font-weight: var(--weight-normal, 400); }
.teamchat__leer {
  padding: var(--space-1) var(--space-3);
  color: var(--text-muted-on-sidebar);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
}
.teamchat__inhalt { min-width: 0; }
.teamchat__zurueck { display: none; }
/* Der Kopf einer Unterhaltung. */
.chatkopf {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  align-items: center;
  justify-content: space-between;
  padding: var(--space-3) 0 var(--space-3);
  border-bottom: 1px solid var(--border);
}
.chatkopf__text { min-width: 0; flex: 1 1 16rem; }
.chatkopf__titel {
  margin: 0;
  font-size: var(--text-lg, 1.125rem);
  line-height: 1.4;
  font-weight: var(--weight-semibold);
  overflow-wrap: anywhere;
}
.chatkopf__sub,
.chatkopf__beschreibung {
  margin: 2px 0 0;
  color: var(--text-muted);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
}
.chatkopf__beschreibung { white-space: pre-wrap; overflow-wrap: anywhere; }
.chatkopf__trenner { margin: 0 var(--space-1); }
.chatkopf__aktionen { display: flex; gap: var(--space-1-5); align-items: center; }
.chatkopf__aktionen .inline-form { margin: 0; }
.chatkopf .chatkopf__knopf {
  display: inline-flex;
  gap: var(--space-1-5, 6px);
  align-items: center;
  height: 2.25rem;
  min-height: 0;
  margin: 0;
  padding: 0 var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
  font-weight: var(--weight-semibold);
  cursor: pointer;
}
.chatkopf .chatkopf__knopf:hover { background: var(--surface-sunken); text-decoration: none; color: var(--text); }
.chatkopf__knopf svg { width: 16px; height: 16px; }
/* Die Uebersicht ohne gewaehlte Unterhaltung: ruhig und mittig. */
.teamchat__waehlen {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: 20rem;
  text-align: center;
}
.teamchat__willkommen-symbol {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3.5rem;
  height: 3.5rem;
  border-radius: var(--radius-full);
  background: var(--action-subtle);
  color: var(--action-on-subtle);
}
.teamchat__willkommen-symbol svg { width: 26px; height: 26px; }
.teamchat__willkommen-titel { margin: var(--space-2) 0 0; font-size: var(--text-xl, 1.25rem); }
.teamchat__willkommen-text { margin: 0; max-width: 28rem; color: var(--text-muted); }
/* --- Das Eingabefeld (Kapitel 40) ----------------------------------------
   Ein runder Kasten ueber die volle Breite der dritten Spalte; er bleibt
   unten stehen (sticky), der Verlauf rollt darueber weg. */
.chatfuss {
  position: sticky;
  bottom: 0;
  z-index: 4;
  padding: var(--space-2) 0 var(--space-3);
  background: var(--surface-content);
}
.chat__neu {
  position: absolute;
  bottom: 100%;
  left: 50%;
  transform: translateX(-50%);
  margin: 0 0 var(--space-2);
}
.chat__neu[hidden] { display: none; }
.chat__neu-link {
  display: inline-flex;
  gap: var(--space-1);
  align-items: center;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-full);
  background: var(--action);
  color: var(--text-on-action);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
  font-weight: var(--weight-semibold);
  box-shadow: var(--shadow-pill);
  white-space: nowrap;
}
.chat__neu-link:hover { background: var(--action-hover); color: var(--text-on-action); text-decoration: none; }
.chat__neu-link svg { width: 16px; height: 16px; }
.chateingabe {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-3) var(--space-3) var(--space-2);
  border: 1px solid var(--border-control);
  border-radius: 20px;
  background: var(--surface);
  box-shadow: var(--shadow-lg);
}
.chateingabe:focus-within { border-color: var(--focus-ring); }
/* MITWACHSEN OHNE INLINE-STIL: Textfeld und Zwilling (::after mit dem Text
   aus data-wert) teilen eine Rasterzelle; der hoehere gibt die Hoehe vor.
   Ab 14rem rollt das Feld selbst. */
.chateingabe__feld { display: grid; }
.chateingabe__feld > textarea,
.chateingabe__feld::after {
  grid-area: 1 / 1 / 2 / 2;
  min-height: 3rem;
  max-height: 14rem;
  margin: 0;
  padding: var(--space-1) var(--space-1);
  font: inherit;
  font-size: var(--text-base);
  line-height: var(--text-base-lh);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.chateingabe__feld::after {
  content: attr(data-wert) " ";
  visibility: hidden;
  overflow: hidden;
}
.chateingabe__feld > textarea {
  width: 100%;
  max-width: none;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--text);
  resize: none;
  overflow-y: auto;
  box-shadow: none;
}
.chateingabe__feld > textarea:focus,
.chateingabe__feld > textarea:focus-visible { outline: none; box-shadow: none; border: 0; }
.chateingabe__feld > textarea::placeholder { color: var(--text-placeholder); }
.chateingabe__leiste { display: flex; gap: var(--space-2); align-items: center; justify-content: space-between; }
.chateingabe__werkzeuge { display: flex; gap: var(--space-1); align-items: center; }
.chateingabe__werkzeug {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  min-height: 0;
  padding: 0;
  border: 0;
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--icon-muted);
  font-size: 1.125rem;
  line-height: 1;
  cursor: pointer;
  list-style: none;
}
.chateingabe__werkzeug::-webkit-details-marker { display: none; }
.chateingabe__werkzeug:hover { background: var(--surface-sunken); color: var(--text); }
.chateingabe__werkzeug:disabled { opacity: 0.5; cursor: progress; }
.chateingabe__werkzeug[hidden] { display: none; }
.chateingabe__werkzeug svg { width: 20px; height: 20px; }
.chateingabe__senden {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  min-height: 0;
  padding: 0;
  border: 0;
  border-radius: var(--radius-full);
  background: var(--action);
  color: var(--text-on-action);
  cursor: pointer;
}
.chateingabe__senden:hover { background: var(--action-hover); }
.chateingabe__senden:active { background: var(--action-active); }
.chateingabe__senden svg { width: 20px; height: 20px; }
.chateingabe__hinweis {
  margin: var(--space-1) var(--space-3) 0;
  color: var(--text-muted);
  font-size: var(--text-xs);
  line-height: var(--text-xs-lh);
}
.emojiwahl { position: relative; }
.emojiwahl__liste {
  position: absolute;
  bottom: 100%;
  left: 0;
  z-index: 5;
  display: grid;
  grid-template-columns: repeat(6, 2.25rem);
  gap: var(--space-1);
  padding: var(--space-2);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.emojiwahl__knopf {
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--text);
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
}
.emojiwahl__knopf:hover { background: var(--surface-sunken); }
/* Handy: zweistufig - erst die Liste, in einer Unterhaltung der Inhalt. */
@media (max-width: 900px) {
  .teamchat--offen .teamchat__liste { display: none; }
  .teamchat:not(.teamchat--offen) .teamchat__inhalt .teamchat__waehlen { display: none; }
  .teamchat__zurueck { display: block; }
}
/* Handy: dieselben Blasen, nur schmaler. */
@media (max-width: 640px) {
  .nachricht__blase { max-width: 85%; }
  .chat { min-height: calc(100dvh - 12rem); }
}

/* --- Der feste Rahmen ab 901px (Kapitel 40.6) ----------------------------
   Der Team-Chat fuellt das Fenster, die SEITE ROLLT NICHT: Liste und
   Verlauf rollen je fuer sich, das Eingabefeld steht unten. Wer einen Chat
   anklickt, wird deshalb nicht mehr nach unten gezogen - nur der Verlauf
   steht an seinem Ende.

   AUFBAU: `.app-main` reicht ueber beide Zeilen der Shell (die Kontozeile
   liegt obenauf), `.app-content` wird ein Raster [Liste | Inhalt], und
   `.teamchat` gibt seine beiden Kinder mit `display: contents` direkt in
   dieses Raster. So stehen Banner der Shell (E-Mail bestaetigen) in der
   Inhaltsspalte, und die Liste laeuft trotzdem von oben bis unten. Zeile 1
   der Inhaltsspalte haelt ein leerer Platzhalter fuer das Kontomenue frei.
   Die Pflichtangaben (Impressum ...) stehen klein unter dem Eingabefeld. */
@media (min-width: 901px) {
  .app-shell:has(.teamchat) {
    --teamchat-liste: 17.5rem;
    --teamchat-kopfzeile: 4.75rem;
    position: relative;
    height: 100vh;
    height: 100dvh;
    min-height: 0;
    overflow: hidden;
  }
  .app-shell:has(.teamchat) .app-topbar {
    position: relative;
    z-index: 6;
    pointer-events: none;
  }
  .app-shell:has(.teamchat) .app-topbar > * { pointer-events: auto; }
  .app-shell:has(.teamchat) .app-main {
    grid-row: 1 / span 2;
    min-height: 0;
  }
  .app-content:has(> .teamchat) {
    display: grid;
    grid-template-columns: var(--teamchat-liste) minmax(0, 1fr);
    grid-template-rows: var(--teamchat-kopfzeile) repeat(3, auto) minmax(0, 1fr);
    min-height: 0;
    padding: 0;
  }
  .app-content:has(> .teamchat)::before {
    content: "";
    grid-column: 2;
    grid-row: 1;
  }
  .app-content:has(> .teamchat) > :not(.teamchat) {
    grid-column: 2;
    margin: 0 var(--space-8) var(--space-3);
  }
  .app-shell:has(.teamchat) .sitefooter {
    position: absolute;
    right: 0;
    bottom: 0;
    left: calc(var(--sidebar-width) + var(--teamchat-liste));
    z-index: 5;
    padding: var(--space-1) var(--space-4) var(--space-2);
  }
  .app-shell:has(.teamchat) .sitefooter nav { justify-content: center; }
  .teamchat { display: contents; }
  .teamchat__liste {
    grid-column: 1;
    grid-row: 1 / -1;
    overflow-y: auto;
    padding: var(--space-8) var(--space-3) var(--space-4);
    border-radius: 0;
    border-right: 1px solid var(--border);
  }
  .teamchat__inhalt {
    grid-column: 2;
    grid-row: 5;
    min-height: 0;
    overflow-y: auto;
    padding: 0 var(--space-8) var(--space-10);
  }
  .teamchat__inhalt--mitte { display: flex; align-items: center; justify-content: center; }
  .teamchat__inhalt--chat {
    display: flex;
    flex-direction: column;
    overflow: hidden;
    padding: 0;
  }
  .teamchat__inhalt--chat > :not(.chat):not(.chatkopf) { margin-inline: var(--space-8); }
  .chatkopf { padding-inline: var(--space-8); }
  .teamchat__inhalt--chat .chat {
    flex: 1;
    min-height: 0;
  }
  /* DER VERLAUF ROLLT SELBST - und steht mittig in einer lesbaren Breite,
     die Rollleiste aber am rechten Rand. */
  .teamchat__inhalt--chat .chatverlauf,
  .teamchat__inhalt--chat .chat__leer {
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    margin: 0;
    padding-inline: max(var(--space-8), calc((100% - var(--chat-breite)) / 2));
  }
  .teamchat__inhalt--chat .chatfuss {
    position: relative;
    padding: var(--space-2) max(var(--space-8), calc((100% - var(--chat-breite)) / 2)) 2.25rem;
  }
}

/* --- Die CLO-Einrichtung: Leiste und Bereiche (Kapitel 86) --------------
   Die Leiste traegt denselben Grund wie die Liste des Team-Chats und
   dieselben `navitem`-Eintraege. Darunter steht genau ein Bereich (das
   Umschalten macht app/static/js/clo-bereiche.js; ohne Skript stehen alle
   untereinander).

   MEHR LUFT ZWISCHEN EINSTELLUNG UND ERKLAERTEXT: In der alten Spalte
   klebte der Hinweis mit 4px an seinem Feld und der naechste Knopf direkt
   am Hinweis. Hier haben Feld, Hinweis und Aktion je einen eigenen Abstand. */
.clowerk__leiste {
  min-width: 0;
  padding: var(--space-6) var(--space-4);
  border-radius: var(--radius-lg);
  background: var(--surface-sidebar);
}
.clowerk__kopf { padding: 0 var(--space-3) var(--space-4); }
.clowerk__titel {
  margin: 0;
  font-size: var(--text-xl, 1.25rem);
  line-height: 1.3;
}
.clowerk__sub {
  margin: var(--space-2) 0 0;
  color: var(--text-muted-on-sidebar);
  font-size: var(--text-sm);
  line-height: var(--text-sm-lh);
}
.clowerk__bereiche {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-bottom: var(--space-4);
  margin-bottom: var(--space-5);
  border-bottom: 1px solid var(--border);
}
.clobereich { padding: 0 var(--space-3); }
.clobereich[hidden] { display: none !important; }
.clowerk:not(.clowerk--skript) .clobereich + .clobereich {
  margin-top: var(--space-10, 2.5rem);
}
.clobereich__titel {
  margin: 0 0 var(--space-4);
  font-size: var(--text-lg);
  line-height: var(--text-lg-lh);
  font-weight: var(--weight-semibold);
}
.clobereich .steuer__eintrag { padding: var(--space-6) 0; }
.clobereich .steuer__eintrag--erster { padding-top: 0; }
.clobereich .field-hint,
.clobereich .field-error,
.clobereich .steuer__eintrag > .hint {
  margin-top: var(--space-3);
  line-height: 1.55;
}
.clobereich .steuer__label + .hint { margin-top: 0; margin-bottom: var(--space-4); }
.clobereich .logowahl + .field-hint { margin-top: var(--space-4); }
.clobereich .steuer__aktion { margin-top: var(--space-5); }
.clobereich .steuer__fuss { padding-top: var(--space-6); }
.clobereich textarea { width: 100%; max-width: none; }
.clobereich [data-oeffner-nur][hidden] { display: none !important; }
/* Die zwei Formen des Oeffnen-Knopfs als Miniatur (Kapitel 87). Neutrale
   Grautoene - die Farbe des Betriebs zeigt die Vorschau rechts. */
.oeffnerwahl__bild {
  position: relative;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  height: 2.5rem;
  margin-bottom: var(--space-1);
}
.oeffnerwahl__pille {
  width: 4.5rem;
  height: 1.25rem;
  border-radius: var(--radius-full);
  background: var(--text-muted);
}
.oeffnerwahl__kreis {
  width: 1.6rem;
  height: 1.6rem;
  border-radius: var(--radius-full);
  background: var(--text-muted);
}
.oeffnerwahl__blase {
  position: absolute;
  top: 0;
  left: 50%;
  width: 2.6rem;
  height: .7rem;
  border-radius: var(--radius-full);
  border: 1px solid var(--border-strong);
  background: var(--surface);
  transform: translateX(-60%);
}

/* --- Der feste Rahmen ab 1101px (Kapitel 86) -------------------------------
   DASSELBE MUSTER WIE DER TEAM-CHAT (Kapitel 40.6): Die Seite rollt nicht;
   die Leiste rollt fuer sich, die Buehne fuellt den Rest des Fensters.
   `.clowerk` gibt seine Kinder mit `display: contents` in das Raster von
   `.app-content`; Banner der Seite stehen in der Inhaltsspalte ueber der
   Buehne. 1101px statt 901px: Darunter waere der Rahmen schmaler als die
   640px, ab denen das Widget sein Desktop-Fenster zeigt. */
@media (min-width: 1101px) {
  .app-shell:has(.clowerk) {
    --clo-leiste: 23rem;
    /* NUR EIN SCHMALER STREIFEN: Titel und Nutzungszeile der Buehne stehen
       links neben dem Kontomenue statt darunter - jede Zeile ueber dem Rahmen
       fehlt dem Chatfenster an Hoehe. */
    --clo-kopfzeile: var(--space-5);
    position: relative;
    height: 100vh;
    height: 100dvh;
    min-height: 0;
    overflow: hidden;
  }
  .app-shell:has(.clowerk) .app-topbar {
    position: relative;
    z-index: 6;
    pointer-events: none;
  }
  .app-shell:has(.clowerk) .app-topbar > * { pointer-events: auto; }
  .app-shell:has(.clowerk) .app-main {
    grid-row: 1 / span 2;
    min-height: 0;
  }
  .app-content:has(> .clowerk) {
    display: grid;
    grid-template-columns: var(--clo-leiste) minmax(0, 1fr);
    grid-template-rows: var(--clo-kopfzeile) repeat(4, auto) minmax(0, 1fr);
    min-height: 0;
    padding: 0;
  }
  .app-content:has(> .clowerk)::before {
    content: "";
    grid-column: 2;
    grid-row: 1;
  }
  .app-content:has(> .clowerk) > :not(.clowerk) {
    grid-column: 2;
    margin: 0 var(--space-8) var(--space-3);
  }
  .app-shell:has(.clowerk) .sitefooter {
    position: absolute;
    right: 0;
    bottom: 0;
    left: calc(var(--sidebar-width) + var(--clo-leiste));
    z-index: 5;
    padding: var(--space-1) var(--space-4) var(--space-2);
  }
  .app-shell:has(.clowerk) .sitefooter nav { justify-content: center; }
  .clowerk { display: contents; }
  .clowerk__leiste {
    grid-column: 1;
    grid-row: 1 / -1;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: var(--space-8) var(--space-4) var(--space-8);
    border-radius: 0;
    border-right: 1px solid var(--border);
  }
  .clowerk__buehne {
    grid-column: 2;
    grid-row: 6;
    min-height: 0;
    overflow: hidden;
    padding: 0 var(--space-8) 2.75rem;
  }
  /* Die drei Notizen unter dem Rahmen in EINER Zeile; aufgeklappt legt sich
     der Inhalt ueber die Fusszeile statt die Buehne zu verkleinern. */
  .clowerk__buehne .live__fuss { flex-wrap: nowrap; gap: var(--space-6); }
  .clowerk__buehne .notiz { flex: 1 1 0; position: relative; }
  .clowerk__buehne .notiz[open] > :not(summary) {
    position: absolute;
    bottom: 100%;
    left: 0;
    right: 0;
    z-index: 7;
    margin: 0 0 var(--space-2);
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    background: var(--surface);
    box-shadow: var(--shadow-lg);
  }
  /* DIE BUEHNE NIMMT DIE RESTHOEHE. Das Widget rechnet seine Panelhoehe
     aus der Rahmenhoehe (`min(704px, 100vh - 96px)`); bei niedrigen
     Fenstern wird es also flacher, aber nie abgeschnitten. */
  .clowerk__buehne .live__kopf { padding-right: 17rem; min-height: 3.25rem; }
  .clowerk__buehne .live__buehne {
    display: flex;
    flex: 1;
    min-height: 0;
    padding: var(--space-2);
  }
  .clowerk__buehne .vorschau-fenster {
    flex: 1;
    height: auto;
    min-height: 0;
  }
}

/* --- Persoenliche Termine in "Mein Kalender" (Kapitel 39) --------------- */
.appt--persoenlich {
  background: var(--surface-sunken);
  border-left-color: var(--border-strong);
  color: var(--text);
}
/* "Termin hinzufuegen" (Kapitel 40): zu, bis der Knopf oben rechts auf
   #termin-neu zeigt - oder eine Fehlermeldung dasteht. */
.persoenlich-neu:not(:target):not(.persoenlich-neu--offen) { display: none; }
.personenwahl { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center;
  margin-bottom: var(--space-4); }
.persoenlich-form__zeit {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 12rem));
  gap: var(--space-3);
}
@media (max-width: 640px) {
  .persoenlich-form__zeit { grid-template-columns: minmax(0, 1fr); }
}

/* ==========================================================================
   DIE TARIFAUSWAHL (admin/tarifauswahl.html, SECURITY.md 78)
   ==========================================================================
   1:1 VON DER WEBSITE (Anweisung 30.9.2026): Jede Regel unten ist aus
   website/src/components/PriceTable.astro, PriceAddons.astro und
   website/src/styles/{tokens,base}.css abgeschrieben - mit Praefix `ws-`
   und den Website-Werten als px (die Website rechnet mit denselben
   16px je rem). Farben ueber `--preis-*` (tokens.css), Schrift Inter aus
   app/static/fonts (OFL, aus dem Website-Repo, Lizenz daneben).

   Die Seite hat keinen Verwaltungsrahmen (`.ws-vollbild`), die Karten sind
   296px breit und stehen mittig - wie auf der Website. */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('/static/fonts/InterVariable.woff2') format('woff2');
}

.ws-preise {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  width: 100%;
  font-family: 'Inter', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  font-feature-settings: 'cv11', 'ss01';
  font-size: 17px;
  line-height: 1.47;
  color: var(--preis-text);
  -webkit-font-smoothing: antialiased;
}
/* DER RAHMEN DER SEITE - ohne Seitenleiste (1.10.2026, Vorbild Claude.ai
   "Upgrade"): volle Fensterhoehe, oben die Leiste mit dem Weg zurueck. */
.ws-vollbild {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  background: var(--preis-seite);
}
.ws-leiste {
  display: flex;
  align-items: center;
  height: 44px;
  padding: 0 20px;
  border-bottom: 1px solid var(--preis-linie);
  font-family: 'Inter', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
}
.ws-leiste__zurueck {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--preis-text);
  font-size: 14px;
  text-decoration: none;
}
.ws-leiste__zurueck svg { width: 16px; height: 16px; }
/* Ohne Tarif steht hier die Abmeldung statt des Wegs zurueck (Kapitel 89):
   ein Knopf, der aussieht wie der Link daneben. */
.ws-leiste__abmelden { margin: 0; }
.ws-leiste__abmelden button { background: none; border: 0; padding: 0; font: inherit; cursor: pointer; }
.ws-leiste__zurueck:hover { color: var(--preis-text-2); }
.ws-leiste__zurueck:focus-visible { outline: 3px solid var(--preis-marke); outline-offset: 3px; }
.ws-vollbild__inhalt { flex: 1; padding: 56px 24px 64px; }
.ws-kopf { display: flex; flex-direction: column; align-items: center; gap: 24px; }
.ws-titel {
  margin: 0;
  font-size: 32px;
  line-height: 1.15;
  font-weight: 650;
  letter-spacing: -0.01em;
  color: var(--preis-text);
  text-align: center;
}

/* .switch der Website */
.ws-switch {
  display: flex;
  justify-content: center;
  width: fit-content;
  margin: 0 auto;
  padding: 4px;
  border-radius: 999px;
  background: var(--preis-schalter);
}
.ws-switch a {
  padding: 10px 20px;
  border-radius: 999px;
  font-size: 14px;
  font-weight: 500;
  color: var(--preis-text-2);
  text-decoration: none;
}
.ws-switch a[aria-current] {
  background: var(--preis-schalter-an);
  color: var(--preis-text);
  box-shadow: var(--preis-schalter-schatten);
}
.ws-switch a:focus-visible { outline: 3px solid var(--preis-marke); outline-offset: 2px; }
.ws-switch__rabatt { margin-left: 2px; font-weight: 600; }

/* .plans / .plan der Website */
/* KOMPAKT UND MITTIG (1.10.2026) - und niedriger als auf der Website:
   Innenabstand, Stufen- und Punktzeilen sind enger ("Karten viel zu
   massiv"); Aufbau, Farben und Schrift bleiben die der Website.
   : jede Karte 296px wie auf der Website,
   die Reihe in der Mitte - vier nebeneinander ab 1.280px Fensterbreite, sonst
   zwei bzw. eine. Vorher streckten sich die Karten ueber die ganze Breite und
   wurden bei 1.920px ueber 400px breit. */
.ws-plans {
  display: grid;
  grid-template-columns: repeat(auto-fit, 296px);
  justify-content: center;
  column-gap: 16px;
  /* Der Abstand gilt ZWEIFACH: zwischen den Abschnitten in jeder Karte
     (die Karten erben die Zeilen ueber `subgrid`) und zwischen zwei
     Kartenreihen, wenn sie umbrechen. */
  row-gap: 14px;
  width: 100%;
}
@media (max-width: 639px) { .ws-plans { grid-template-columns: minmax(0, 1fr); } }
/* GLEICHE HOEHEN FUER GLEICHE ABSCHNITTE (1.10.2026): Jede Karte belegt fuenf
   Zeilen des gemeinsamen Rasters und uebernimmt sie (`subgrid`). Jede Zeile
   ist so hoch wie ihr laengster Abschnitt in einer der Karten - die anderen
   bekommen den Raum, ohne sichtbare Leerzeilen. Der Knopf sitzt in der
   letzten Zeile unten. */
.ws-plan {
  display: grid;
  grid-row: span 5;
  grid-template-rows: subgrid;
  align-content: start;
  margin: 0;
  padding: 24px 20px;
  border-radius: 0;
  background: var(--preis-karte);
  color: var(--preis-text);
}
.ws-plan--pro { background: var(--preis-karte-pro); color: var(--preis-pro-text); box-shadow: 0 0 0 1px var(--preis-pro-linie); }
.ws-plan__name { margin: 0; font-size: 22px; line-height: 1.15; font-weight: 650; letter-spacing: -0.01em; color: inherit; }
.ws-plan__aktuell { margin: 4px 0 0; font-size: 12px; color: var(--preis-text-2); }
.ws-plan--pro .ws-plan__aktuell { color: var(--preis-pro-text-2); }
.ws-plan__tiers { width: 100%; border-collapse: collapse; margin: 0; font-size: 17px; }
.ws-plan__tiers tr + tr th, .ws-plan__tiers tr + tr td { border-top: 1px solid var(--preis-linie); }
.ws-plan--pro .ws-plan__tiers tr + tr th, .ws-plan--pro .ws-plan__tiers tr + tr td { border-top-color: var(--preis-pro-linie); }
.ws-plan__tiers th, .ws-plan__tiers td { padding: 7px 0; text-align: left; vertical-align: baseline; background: transparent; }
.ws-plan__tiers th { font-weight: 500; font-size: 14px; color: var(--preis-text-2); white-space: nowrap; }
.ws-plan--pro .ws-plan__tiers th { color: var(--preis-pro-text-2); }
.ws-plan__tiers th span { font-weight: 400; }
/* DIE ALLGEMEINEN TABELLEN- UND FELDREGELN DER VERWALTUNG GELTEN HIER NICHT
   (Befund im Bildbeleg 30.9.2026): `label` setzte eine eigene Textfarbe -
   auf der schwarzen Pro-Karte dunkel auf Schwarz -, `tbody td` zog eine
   zweite Linie, `tr:hover` faerbte die Zeile. Die Website kennt keine davon. */
.ws-plan__tiers label {
  display: inline;
  margin: 0;
  color: inherit;
  font: inherit;
  cursor: pointer;
}
.ws-plan__tiers tbody td { border-bottom: 0; }
.ws-plan__tiers tbody tr:hover { background: transparent; }
.ws-plan__tiers th, .ws-plan__tiers td { color: inherit; }
.ws-plan__tiers th { color: var(--preis-text-2); }
.ws-plan__tiers input[type="radio"] {
  width: 14px;
  height: 14px;
  margin: 0 6px 0 0;
  vertical-align: -2px;
  accent-color: var(--preis-marke);
}
.ws-plan__tiers input:disabled + * , .ws-plan__tiers label:has(input:disabled) { cursor: default; }
.ws-stufe:has(input:checked) th { color: var(--preis-text); font-weight: 600; }
.ws-plan--pro .ws-stufe:has(input:checked) th { color: var(--preis-pro-text); }
.ws-price { text-align: right !important; font-size: 18px; font-weight: 650; white-space: nowrap; }
.ws-price label { display: grid; justify-items: end; }
.ws-price__zeile { display: block; font-size: 11px; font-weight: 400; color: var(--preis-text-2); }
.ws-plan--pro .ws-price__zeile { color: var(--preis-pro-text-2); }
.ws-rabatt {
  display: inline-block;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--preis-marke);
  color: var(--preis-auf-marke);
  font-weight: 600;
}
.ws-plan__kopf, .ws-plan__stufen { min-width: 0; }
.ws-plan__basis { align-self: end; margin: 6px 0 -6px; font-size: 14px; font-weight: 600; color: var(--preis-text); }
.ws-plan--pro .ws-plan__basis { color: var(--preis-pro-text); }
.ws-plan__features { margin: 0; padding: 0; list-style: none; font-size: 13px; color: var(--preis-text-2); }
.ws-plan--pro .ws-plan__features { color: var(--preis-pro-text-2); }
.ws-plan__features li { padding: 3px 0; line-height: 1.35; }
.ws-plan__features strong { color: var(--preis-text); }
.ws-plan--pro .ws-plan__features strong { color: var(--preis-pro-text); }
.ws-plan__ent-price { margin: 0; font-size: 18px; font-weight: 650; }
.ws-plan__ent-text { margin: 0; font-size: 13px; color: var(--preis-text-2); }
.ws-plan__stufen .ws-plan__ent-text { margin-top: 10px; }
/* "Jetzt abschliessen" - die zweite Wahl unter dem Testknopf: ein Knopf in
   Linkgestalt wie die Links der Website (Markenfarbe, Unterstrich). */
.ws-plan__aktion { display: flex; flex-direction: column; align-items: flex-start; justify-content: flex-start; gap: 10px; padding-top: 8px; }
.ws-plan__sofort {
  align-self: flex-start;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 14px;
  color: var(--preis-marke-text);
  text-decoration: underline;
  text-underline-offset: 0.18em;
  cursor: pointer;
}
.ws-plan--pro .ws-plan__sofort { color: var(--preis-marke-dunkel); }
.ws-plan__sofort:hover { color: var(--preis-marke-hover); }
.ws-plan--pro .ws-plan__sofort:hover { color: var(--preis-pro-text); }
.ws-plan__sofort:focus-visible { outline: 3px solid var(--preis-marke); outline-offset: 3px; }

/* .btn der Website (base.css) - 44px, Pille, 17px, Markenblau */
.ws-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  align-self: flex-start;
  min-height: 44px;
  padding: 0 22px;
  border-radius: 999px;
  border: 1px solid transparent;
  font: inherit;
  font-size: 17px;
  font-weight: 400;
  line-height: 1;
  letter-spacing: -0.01em;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}
.ws-btn--primary { background: var(--preis-marke); color: var(--preis-auf-marke); }
.ws-btn--primary:hover { background: var(--preis-marke-hover); color: var(--preis-auf-marke); }
.ws-btn--secondary { border-color: var(--preis-marke-text); color: var(--preis-marke-text); background: transparent; }
.ws-btn--secondary:hover { background: var(--preis-marke-text); color: var(--preis-auf-marke); }
.ws-plan--pro .ws-btn--secondary { border-color: var(--preis-marke-dunkel); color: var(--preis-marke-dunkel); }
.ws-btn:focus-visible { outline: 3px solid var(--preis-marke); outline-offset: 3px; }
.ws-btn--klein { min-height: 28px; padding: 0 12px; font-size: 12px; letter-spacing: 0; }
/* IN DER KARTE DARF EIN LANGER KNOPFTEXT UMBRECHEN ("Zu Professionell
   wechseln" ragte bei 1.440px ueber den Kartenrand - gesehen auf dem
   Vorschau-Server). Die Website kennt nur "Demo buchen" und bricht nie um. */
.ws-plan .ws-btn { max-width: 100%; min-height: 38px; padding: 8px 18px; font-size: 15px; white-space: normal; text-align: center; line-height: 1.2; }

/* .fineprint der Website */
.ws-fineprint { max-width: 680px; margin: 0 auto; font-size: 14px; line-height: 1.45; color: var(--preis-text-2); text-align: center; }
.ws-fineprint a { color: var(--preis-marke-text); }

/* PriceAddons.astro der Website - dazu je Zeile der Buchen-Knopf */
.ws-zusatz { padding-top: 16px; }
.ws-zusatz__titel { margin: 0 0 24px; font-size: 28px; line-height: 1.15; font-weight: 650; letter-spacing: -0.01em; text-align: center; color: var(--preis-text); }
.ws-zusatz__liste { max-width: 640px; margin: 0 auto; padding: 0; list-style: none; border-top: 1px solid var(--preis-text); }
.ws-zusatz__zeile {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: center;
  gap: 8px 24px;
  padding: 16px 0;
  border-bottom: 1px solid var(--preis-linie);
  font-size: 17px;
}
.ws-zusatz__zeile strong { font-weight: 650; white-space: nowrap; }
.ws-zusatz__name { display: flex; flex-direction: column; }
.ws-zusatz__hinweis, .ws-zusatz__grund { font-size: 12px; color: var(--preis-text-2); }
.ws-zusatz__grund { max-width: 180px; text-align: right; }
.ws-zusatz__form { margin: 0; }
.ws-zusatz__fuss { max-width: 640px; margin: 16px auto 0; font-size: 14px; line-height: 1.45; color: var(--preis-text-2); text-align: center; }

/* DER VOLLSTAENDIGE VERGLEICH IM FENSTER (30.9.2026) - Grund, Rahmen und
   Schatten wie das Einstellungs-Fenster (`.einst`), Tabelle monochrom wie die
   Tarifseite. Die Tabelle rollt im Fenster, nicht die Seite dahinter. */
.vergleich-modal {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 3vh 2vw;
}
.vergleich-modal__grund {
  position: absolute;
  inset: 0;
  background: var(--overlay);
  cursor: default;
}
/* DIE GANZE KARTE ROLLT (Befund 30.9.2026): Vorher rollte nur der
   Tabellenbereich. Wer mit dem Mauszeiger auf dem Kopf oder am Rand stand,
   rollte die Seite DAHINTER - das Fenster wirkte starr. Nachgestellt in
   WebKit und Chromium: Die Tabelle selbst rollte in beiden; der Fehler war
   die Flaeche, auf der das Rad nicht griff. Jetzt rollt die Karte als
   Ganzes, der Kopf klebt oben, und die Seite dahinter steht still
   (`body:has(...)`). */
.vergleich-modal__karte {
  position: relative;
  /* GROESSER (1.10.2026): die ganze Website-Tabelle braucht Platz. */
  width: min(1280px, 100%);
  max-height: 94vh;
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-lg);
  overflow: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}
body:has(.vergleich-modal) { overflow: hidden; }
.vergleich-modal__kopf {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  height: 64px;
  padding: 0 var(--space-6);
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}
.vergleich-modal__titel {
  margin: 0;
  font-size: var(--text-lg);
  line-height: var(--text-lg-lh);
  font-weight: var(--weight-semibold);
  color: var(--text-strong);
}
.vergleich-modal__zu {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--control-height-sm);
  height: var(--control-height-sm);
  border-radius: var(--radius);
  color: var(--text-muted);
}
.vergleich-modal__zu:hover { background: var(--surface-hover); color: var(--text); }
.vergleich-modal__zu:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
/* KEIN EIGENER ROLLBEREICH: `overflow-x` machte den Tabellenbereich zum
   Scroll-Container, die klebende Kopfzeile rechnete ihre 64px ab dort und
   verdeckte den ersten Abschnitt (Bildbeleg 1.10.2026). Gerollt wird die
   Karte - in beide Richtungen. */
.vergleich-modal__inhalt { font-size: var(--text-xs); }
.tarifvergleich__tabelle { width: 100%; border-collapse: collapse; }
/* Alle Zellen gleich: dieselbe Linie, derselbe Abstand - auch die
   Zeilenbeschriftung, die als <th> sonst die Tabellenkopf-Regel erbte und
   ohne Abstand am Rand klebte (gesehen im Bildbeleg). */
.tarifvergleich__tabelle th,
.tarifvergleich__tabelle td {
  padding: var(--space-2) var(--space-4);
  border-bottom: 1px solid var(--border);
}
.tarifvergleich__tabelle tbody th { background: transparent; }
.tarifvergleich__tabelle tr > :first-child { padding-left: var(--space-6); }
.tarifvergleich__tabelle tr > :last-child { padding-right: var(--space-6); }
.tarifvergleich__tabelle tbody tr:last-child > * { border-bottom: 0; }
.tarifvergleich__tabelle thead th {
  position: sticky;
  top: 64px;   /* unter dem klebenden Kopf (.vergleich-modal__kopf) */
  z-index: 1;
  background: var(--surface-sunken);
}
.tarifvergleich__tabelle th[scope="col"],
.tarifvergleich__tabelle td { text-align: center; white-space: nowrap; }
.tarifvergleich__tabelle th[scope="row"] { text-align: left; font-weight: var(--weight-normal); color: var(--text); }
.tarifvergleich__tabelle .tarifvergleich__aktuell { background: var(--surface-sunken); color: var(--text-strong); font-weight: var(--weight-semibold); }
.tarifvergleich__ja { display: inline-flex; color: var(--text-strong); }
.tarifvergleich__ja svg { width: 16px; height: 16px; }
.tarifvergleich__nein { color: var(--text-muted); }

/* DIE VERGLEICHSTABELLE DER WEBSITE (PriceCompare.astro), 1:1 - Werte von
   dort, Farben ueber --preis-*, Schrift Inter. Die Kopfzeile klebt unter dem
   Fensterkopf (64px), wie auf der Website unter der Navigation. */
.vgl {
  width: 100%;
  margin: 0;
  border-collapse: collapse;
  font-family: 'Inter', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  font-size: 14px;
  color: var(--preis-text);
  background: var(--preis-seite);
}
.vgl th, .vgl td {
  padding: 14px 12px;
  border-bottom: 1px solid var(--preis-linie);
  text-align: center;
  vertical-align: middle;
  background: transparent;
  color: var(--preis-text-2);
  text-transform: none;
  letter-spacing: normal;
  font-size: 14px;
  font-weight: 400;
}
.vgl tbody tr:hover { background: transparent; }
/* Die Website zieht auch unter der letzten Zeile eines Abschnitts die Linie
   durch; die Verwaltung nimmt sie dort sonst weg (`tbody tr:last-child td`). */
.vgl tbody tr:last-child td { border-bottom: 1px solid var(--preis-linie); }
.vgl tbody th[scope="row"] { position: relative; text-align: left; color: var(--preis-text); width: 36%; padding-left: 24px; }
.vgl td { width: 16%; }
.vgl tr > :last-child { padding-right: 24px; }
.vgl thead th, .vgl thead .vgl__links {
  position: sticky;
  top: 64px;
  z-index: 2;
  padding-top: 16px;
  padding-bottom: 16px;
  border-bottom: 1px solid var(--preis-text);
  background: var(--preis-seite);
  vertical-align: top;
}
.vgl thead .vgl__links { text-align: left; padding-left: 24px; vertical-align: bottom; }
.vgl__etikett { display: block; margin-bottom: 6px; font-size: 12px; font-weight: 600; color: var(--preis-text-2); }
.vgl__switch { margin: 0; }
.vgl__switch a { padding: 8px 14px; white-space: nowrap; }
.vgl__name { display: block; font-size: 21px; font-weight: 650; color: var(--preis-text); }
.vgl__preis { display: block; margin-top: 4px; font-size: 17px; font-weight: 600; color: var(--preis-text); white-space: nowrap; }
.vgl__zeile { display: flex; align-items: center; justify-content: center; gap: 4px; height: 20px; font-size: 12px; font-weight: 400; color: var(--preis-text-2); }
.vgl__knopf { margin-top: 16px; min-height: 36px; padding: 0 18px; font-size: 15px; }
.vgl__abschnitt th {
  padding: 48px 0 12px 24px !important;
  text-align: left !important;
  font-size: 21px !important;
  font-weight: 650 !important;
  color: var(--preis-text) !important;
}
.vgl__ja { font-size: 18px; line-height: 1; font-weight: 600; color: var(--preis-text); }
.vgl__nein { color: var(--preis-text-2); }
/* Schmucksymbole aus dem CSS, nicht aus der Vorlage: Sie sind fuer
   Screenreader verborgen und haben in keiner Sprache eine andere Gestalt. */
.vgl__ja::before { content: "\2713"; }
.vgl__info::before { content: "i"; }
/* Keine Hervorhebung der Spalte des aktuellen Tarifs - die Website kennt keine. */
.vgl__info {
  /* Die allgemeine Knopfregel der Verwaltung setzt eine Mindesthoehe - hier
     ein Kreis von 16px wie auf der Website. */
  display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; min-width: 0; min-height: 0; margin-left: 6px; padding: 0; vertical-align: 1px;
  box-shadow: none;
  border: 1px solid var(--preis-text-2); border-radius: 50%; background: none;
  color: var(--preis-text-2); font: 600 11px/1 'Inter', ui-sans-serif, system-ui, sans-serif; cursor: help;
}
.vgl__info:hover { background: none; color: var(--preis-text); }
.vgl__info:focus-visible { outline: 3px solid var(--preis-marke); outline-offset: 2px; }
.vgl__tip {
  position: absolute; left: 24px; top: calc(100% - 6px); z-index: 3;
  display: none; width: min(300px, 80vw); padding: 10px 12px;
  border-radius: 8px; background: var(--preis-karte-pro); color: var(--preis-pro-text);
  font-size: 12px; line-height: 1.45; font-weight: 400;
  box-shadow: var(--shadow-lg);
}
.vgl__info:hover + .vgl__tip, .vgl__info:focus + .vgl__tip { display: block; }
.vgl__stern { margin: 12px 24px 0; font-size: 12px; color: var(--preis-text-2); }
.vgl__fuss { max-width: 680px; margin: 24px auto 32px; padding: 0 24px; font-size: 14px; line-height: 1.45; color: var(--preis-text-2); text-align: center; }
.vergleich-modal__karte { background: var(--preis-seite); }
.vergleich-modal__kopf { background: var(--preis-seite); }

/* ==========================================================================
   DIE BESTAETIGUNGSSEITE EINES TARIFWECHSELS (admin/tarif_wechsel.html,
   SECURITY.md 82) - Aufbau nach dem Vorbild Claude.ai, Farben und Schrift
   wie die Tarifauswahl (--preis-*, Inter). Schmale Spalte in der Mitte.
   ========================================================================== */
.tw {
  display: flex;
  flex-direction: column;
  gap: 16px;
  width: min(432px, 100%);
  margin: 0 auto;
  font-family: 'Inter', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  font-size: 14px;
  line-height: 1.45;
  color: var(--preis-text);
}
.tw p { margin: 0; }
.tw__titel { margin: 0 0 8px; font-size: 22px; line-height: 1.25; font-weight: 600; color: var(--preis-text); }

.tw__zahlweisen { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.tw__zahlweise {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-height: 100px;
  padding: 14px;
  border: 1px solid var(--preis-linie);
  border-radius: 8px;
  background: var(--preis-seite);
  color: var(--preis-text);
  text-decoration: none;
}
a.tw__zahlweise:hover { border-color: var(--preis-text-2); }
a.tw__zahlweise:focus-visible { outline: 3px solid var(--preis-marke); outline-offset: 2px; }
.tw__zahlweise--gewaehlt { border-color: var(--preis-marke); background: var(--preis-karte); box-shadow: 0 0 0 1px var(--preis-marke); }
.tw__zahlweise--aus { opacity: 0.55; }
.tw__zahlweise-kopf { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
.tw__punkt { width: 18px; height: 18px; border-radius: 50%; border: 2px solid var(--preis-text-2); }
.tw__zahlweise--gewaehlt .tw__punkt { border: 5px solid var(--preis-marke); background: var(--preis-seite); }
.tw__sparen { padding: 2px 8px; border-radius: 6px; background: var(--preis-karte); color: var(--preis-marke-text); font-size: 11px; font-weight: 500; }
.tw__zahlweise-name { font-weight: 500; }
.tw__zahlweise-preis { color: var(--preis-text-2); font-size: 13px; }

.tw__details { margin-top: 20px; padding: 20px; border: 1px solid var(--preis-linie); border-radius: 8px; background: var(--preis-karte); }
.tw__details-titel { margin: 0 0 14px; font-size: 13px; font-weight: 600; color: var(--preis-text); }
.tw__vergleich { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 16px; font-size: 13px; }
.tw__etikett { color: var(--preis-text-2); }
.tw__pfeil { display: inline-flex; color: var(--preis-text-2); transform: scaleX(-1); }
.tw__pfeil svg { width: 16px; height: 16px; }

.tw__info { display: flex; align-items: flex-start; gap: 12px; padding: 14px; border: 1px solid var(--preis-linie); border-radius: 8px; font-size: 13px; color: var(--preis-text-2); }
.tw__info-symbol { display: inline-flex; margin-top: 1px; color: var(--preis-text-2); }
.tw__info-symbol svg { width: 16px; height: 16px; }

.tw__aenderung { display: flex; flex-direction: column; gap: 8px; padding: 12px 14px; border-radius: 8px; font-size: 13px; }
.tw__aenderung ul { margin: 0; padding-left: 18px; }
.tw__aenderung a { align-self: flex-start; color: var(--preis-marke-text); }
.tw__aenderung--weniger { border: 1px solid var(--warning-border); background: var(--warning-bg); color: var(--warning-text); }
.tw__aenderung--weniger a { color: var(--warning-text); }
.tw__aenderung--mehr, .tw__aenderung--gleich { border: 1px solid var(--preis-linie); background: var(--preis-karte); color: var(--preis-text); }

.tw__form { display: flex; flex-direction: column; gap: 16px; }
.tw__knopf {
  width: 100%;
  min-height: 36px;
  border: 0;
  border-radius: 8px;
  background: var(--preis-marke);
  color: var(--preis-auf-marke);
  font: inherit;
  font-weight: 500;
  cursor: pointer;
}
.tw__knopf:hover { background: var(--preis-marke-hover); }
.tw__knopf:focus-visible { outline: 3px solid var(--preis-marke); outline-offset: 2px; }
/* Wie im Vorbild: ohne Haekchen wirkt der Knopf ausgeschaltet. Abgeschickt
   wird ohnehin nicht - das Haekchen ist `required`, und die Route prueft es. */
.tw__form:not(:has(input[name="zustimmung"]:checked)) .tw__knopf { background: var(--preis-karte); color: var(--preis-text-2); }
.tw__zustimmung { display: flex; align-items: flex-start; gap: 10px; margin: 0; font-size: 13px; font-weight: 400; color: var(--preis-text-2); cursor: pointer; }
.tw__zustimmung input { flex: none; width: 16px; height: 16px; margin: 2px 0 0; accent-color: var(--preis-marke); }
.tw__zustimmung a { color: var(--preis-marke-text); }
