/* ==========================================================================
   DESIGN-TOKENS - die einzige Stelle, an der Farben, Groessen und Abstaende
   definiert werden.

   WARUM EINE EIGENE DATEI: Das hier ist die Datei, die man beim Rebranding
   anfasst - und nur diese. Komponenten in app.css greifen ausschliesslich auf
   die SEMANTISCHEN Tokens (--text, --action, --surface ...) zu, niemals direkt
   auf eine Ramp-Stufe wie --blue-600. Diese Trennung ist der Grund, warum ein
   spaeterer Dark Mode das Umdefinieren dieser Variablen ist und nicht das
   Durchsuchen aller Komponenten.

   KONTRASTE SIND NACHGERECHNET, nicht geschaetzt. Jeder Wert, der Text traegt,
   erreicht WCAG 2.1 AA (4.5:1 fuer Fliesstext, 3:1 fuer Bedienelement-
   Umrandungen nach 1.4.11). Die gemessenen Werte stehen als Kommentar an der
   jeweiligen Stelle. Wer eine Farbe aendert, rechnet neu nach - sonst faellt
   die Zugaenglichkeit still um, ohne dass irgendetwas kaputt aussieht.
   ========================================================================== */

:root {
  /* --- Primitive: Blau (Marke UND Aktion) --------------------------------
     Markenblau #0570E4 = 500. Diese Stufe traegt hier ANDERS ALS BEIM
     FRUEHEREN ROT auch Text und Bedienelemente: Sie erreicht auf Weiss
     4.73:1 und damit WCAG AA fuer Fliesstext (4.5:1). Das Markenrot davor
     schaffte nur 4.31 und musste deshalb in zwei Toene geteilt werden - der
     eine fuer Flaechen, der andere fuer alles Bedienbare. Diese Teilung
     entfaellt: Was der Nutzer als Markenfarbe sieht, ist dieselbe Farbe, die
     er anklickt.

     600 UND 700 BLEIBEN TROTZDEM, aber in anderer Rolle: als Hover- und
     Aktivzustand (dort SOLL es dunkler werden) und als Textfarbe auf
     getoenten Flaechen - siehe --action-on-subtle weiter unten.

     DIE RAMPE IST AM 22.9.2026 UMGERECHNET, nicht neu erfunden: Jede Stufe
     behaelt ihre OKLab-HELLIGKEIT, uebernimmt den Farbton des neuen 500er
     und skaliert ihre Buntheit im selben Verhaeltnis (Faktor 1,039). Deshalb
     bleiben alle Kontrastzusagen erhalten - die groesste Abweichung liegt
     bei 0,15 Stufen. Wer die Marke erneut wechselt, rechnet genauso: nicht
     jede Stufe von Hand waehlen, sondern die Helligkeiten behalten. */
  --blue-50:  #ecf3fd;
  --blue-100: #d7e6fb;
  --blue-200: #b5d1f9;
  --blue-300: #84b3f4;
  --blue-400: #4d91ee;
  --blue-500: #0570e4;   /* Marke UND Aktion (4.73:1 auf Weiss)            */
  --blue-600: #005cc2;   /* Hover (6.34:1)                                  */
  --blue-700: #0053b2;   /* Aktiv; Text auf --blue-50 (6.53:1)              */
  --blue-800: #01438e;
  --blue-900: #073771;

  /* --- Primitive: Gefahr -------------------------------------------------
     EIGENE RAMP, NICHT MEHR DIE MARKENFARBE. Bis zur Umstellung auf Blau war
     das Markenrot zugleich die Gefahrenfarbe: --action und --danger-icon
     zeigten auf denselben Wert. Ein bestaetigender Knopf ("Genehmigung liegt
     vor") sah damit aus wie ein loeschender.

     Die Trennung ist jetzt strukturell und nicht mehr zufaellig: Diese Ramp
     haengt an keinem Marken-Token. Ein spaeterer Rebrand kann die Marke
     wechseln, ohne dass "Achtung" mitwandert - und umgekehrt.

     Die Werte sind dieselben wie im alten Markenrot; sie waren nachgerechnet
     und bleiben es. */
  --danger-50:  #fef3f2;
  --danger-100: #fee4e2;
  --danger-200: #fecdc9;
  --danger-300: #fda29b;   /* nur fuer den Dark Mode - siehe unten          */
  --danger-400: #f87066;   /*   "                                          */
  --danger-500: #e63329;   /* Flaeche - NIE Text (4.31:1, verfehlt AA)      */
  --danger-600: #c81e14;   /* Icons, Knopfrand (5.75:1)                     */
  --danger-700: #a71a12;   /* Text, auch auf --danger-50 (6.90:1)           */
  --danger-800: #8a1710;

  /* 300 UND 400 SIND IM HELLEN MODUS UNBENUTZT und stehen trotzdem hier.
     Grund ist der vorbereitete Dark Mode: Dort kehrt sich die Richtung um -
     Text steht auf dunklem Grund und muss HELLER werden, nicht dunkler. Die
     dunklen Stufen taugen dafuer nicht (--danger-600 auf --neutral-900 sind
     3.10:1), die hellen schon: --danger-300 erreicht 9.20:1, --danger-400
     noch 6.38:1.

     Ohne sie waere die Zusage im Kopf des semantischen Blocks - ein Dark Mode
     definiere nur die semantischen Tokens neu - fuer Fehlermeldungen nicht
     einloesbar; es gaebe schlicht keinen Wert, auf den --danger-text zeigen
     koennte. Die Blaurampe kann es bereits (--blue-300: 8.20:1). */

  /* --- Primitive: Neutral ------------------------------------------------
     Kuehle Grau-Skala. 400 und heller sind BORDER- UND ICON-Toene und duerfen
     keinen Text tragen: --neutral-400 erreicht auf Weiss nur 2.56:1. Der
     hellste Ton, der als Text durchgeht, ist --neutral-500 (4.76:1). */
  --neutral-0:   #ffffff;
  --neutral-50:  #f8fafc;
  --neutral-100: #f1f5f9;
  --neutral-200: #e2e8f0;
  --neutral-300: #cbd5e1;
  --neutral-400: #94a3b8;   /* nur Icons/Trenner - als Text 2.56:1, zu schwach */
  --neutral-500: #64748b;   /* hellster Textton (4.76:1)                       */
  --neutral-600: #475569;   /* 7.24:1 auf --neutral-50                         */
  --neutral-700: #334155;
  --neutral-800: #1e293b;   /* Haupttext (14.63:1) - bewusst nicht reines Schwarz */
  --neutral-900: #0f172a;

  /* Sonderstufe zwischen 300 und 400: der hellste Ton, der die 3:1 aus WCAG
     1.4.11 fuer Bedienelement-Umrandungen noch erreicht (3.12:1). Eingabe-
     felder brauchen ihn, weil dort die Umrandung das Bedienelement DEFINIERT -
     ein zu blasser Rahmen laesst das Feld fuer manche Nutzer verschwinden.
     Dekorative Trenner (Karten, Tabellenzeilen) brauchen ihn nicht. */
  --neutral-control: #8593a8;

  /* Der Grund der Seitenleiste. ZWEITE EIGENE STUFE NEBEN --neutral-control,
     und aus einem verwandten Grund: Tailwinds `slate` hat zwischen 300
     (#cbd5e1) und 400 (#94a3b8) nichts, und 300 erreicht gegen Weiss nur
     1.48:1 - knapp unter dem, was hier gebraucht wird.

     GEBRAUCHT WIRD ES, WEIL DER AKTIVE EINTRAG WEISS IST. Die Leiste ist
     nicht dunkel, weil dunkel schoener waere, sondern weil eine weisse
     Flaeche etwas braucht, wovon sie sich abheben kann. Auf der bisherigen
     Leiste (#f8fafc) waeren es 1.05:1 gewesen - dieselbe Falle wie bei den
     weissen Karten auf weissem Grund.

     IN VIER SCHRITTEN AUFGEHELLT UND ENTFAERBT: #c3cddb (zu dunkel, 24 Stufen
     Blaustich), #e3e6ea, #eceef1, #f1f2f3 - jeweils noch zu kraeftig. Jetzt
     1.04:1 gegen Weiss, also praktisch weiss.

     DIE LEISTE IST DAMIT KEINE EIGENE FLAECHE MEHR, sondern ein Bereich, den
     nur seine Trennlinie ausweist. Das ist eine Entscheidung und kein
     Versehen; sie hat zwei Folgen, die anderswo aufgefangen werden:

       1. Der ausgewaehlte Eintrag kann sich nicht mehr durch seine Farbe
          abheben - Weiss auf Fastweiss sind 1.04:1. Er haengt vollstaendig
          an Kontur und Schatten (siehe .navitem[aria-current] in app.css).
       2. Die Trennung zur Inhaltsflaeche traegt allein der rechte Rand.

     DAS IST DER GRUND, WARUM DER TEST DORT DIE BAUWEISE PRUEFT und keine
     Zahl: Ueber fuenf Runden hat sich verschoben, WELCHES der drei Merkmale
     traegt - erst die Flaeche (1.61:1), dann die Kontur, jetzt Kontur und
     Schatten allein. Eine feste Schwelle auf ein einzelnes Merkmal waere in
     den meisten dieser Runden falsch gewesen. */
  --neutral-sidebar: #fafafb;

  /* Das neutrale Dunkelgrau der Farbvorschau auf "Digitaler Mitarbeiter",
     solange kein Skript die Markenfarbe eingesetzt hat. Stand vorher als
     Rueckfallwert in app.css (Dark Mode: keine festen Farben ausserhalb
     dieser Datei). */
  --neutral-farbwahl: #4b5563;

  /* --- Primitive: Statusfarben ------------------------------------------- */
  --green-50:  #ecfdf5;
  --green-200: #a7f3d0;
  --green-600: #059669;
  --green-700: #047857;   /* 5.21:1 auf --green-50 */

  --amber-50:  #fffbeb;
  --amber-200: #fde68a;
  --amber-600: #d97706;
  /* 700 IST NEU (September 2026) und hat ZWEI Bedingungen zu erfuellen: Das
     Hinweisbanner steht seit der Umfaerbung auf WEISS statt auf der getoenten
     Flaeche. Dort trug --amber-600 als Symbolfarbe nur 3.19:1 - knapp ueber
     der Schwelle aus WCAG 1.4.11 und leiser als das Symbol im Fehlerbanner
     daneben. Die Stufe muss also dunkler sein.

     DUNKLER HEISST BEI BERNSTEIN ABER: NAEHER AM ROT, und Hinweis- und
     Fehlerbanner stehen auf der Startseite untereinander. Gemessen, Abstand
     zur Gefahrenfarbe #c81e14 (Farbton 3.3 Grad) und Kontrast auf Weiss:

         #d97706 (600)   32.1 Grad   28.8 Abstand   3.19:1   zu leise
         #b45309         26.0 Grad   22.6 Abstand   5.02:1   zu nah am Rot
         #92400e (800)   22.7 Grad   19.4 Abstand   7.09:1   zu nah am Rot
         #a16207         35.5 Grad   32.1 Abstand   4.92:1   erfuellt beides

     Der erste Anlauf stand auf #b45309 und ist an genau dieser Pruefung
     gescheitert (tests/test_design_tokens.py). */
  --amber-700: #a16207;
  --amber-800: #92400e;   /* 6.84:1 auf --amber-50 */

  /* --- Primitive: die beiden Terminkalender -------------------------------
     Violett fuer Beratung, Blaugruen fuer Rueckruf.

     WARUM BERATUNG NICHT MEHR BLAU IST: Blau ist seit der Umstellung die
     AKTIONSFARBE - Knoepfe, Links, Fokusring. Der Beratungskalender stand
     auf #2563eb und lag damit 2.3 Grad Farbton neben der neuen Marke: fuer
     das Auge dieselbe Farbe. Ein Kalender in der Aktionsfarbe sieht aus wie
     etwas, das man druecken kann, und der Unterschied zwischen "hier ist ein
     Termin" und "hier ist eine Schaltflaeche" geht verloren. Violett liegt
     38 Grad daneben und ist damit sicher unterscheidbar; Blaugruen mit 49
     Grad war es ohnehin.

     WARUM UEBERHAUPT ZWEI FARBEN und nicht nur zwei Beschriftungen: In einem
     Wochenraster sucht das Auge Muster, nicht Woerter - "am Dienstag sind
     lauter Rueckrufe" liest man an der Farbe in einer halben Sekunde und am
     Text in zehn. Die Farbe ist deshalb die Abkuerzung und nie die einzige
     Auskunft: Jedes Kaestchen traegt den Kalendernamen als Text (WCAG 1.4.1).
     Die Werte tragen Text und sind nachgerechnet. */
  --violet-50:  #f5f3ff;
  --violet-200: #ddd6fe;
  --violet-600: #7c3aed;
  --violet-800: #5b21b6;   /* 8.19:1 auf --violet-50 */

  --teal-50:  #f0fdfa;
  --teal-200: #99f6e4;
  --teal-600: #0d9488;
  --teal-800: #115e59;   /* 7.27:1 auf --teal-50 */

  /* Gelbgruen fuer die Online-Termine (SECURITY.md Kapitel 37, persoenlicher
     Kalender). 86 Grad Farbton - 125 von der Aktionsfarbe, 89 vom Rueckruf,
     176 von der Beratung. Nachgerechnet: 800 auf 50 6.84:1, 700 auf 50
     4.82:1. */
  --lime-50:  #f7fee7;
  --lime-700: #4d7c0f;
  --lime-800: #3f6212;   /* 6.84:1 auf --lime-50 */

  /* --- Primitive: Website-Preiskarten (30.9.2026, SECURITY.md 78) --------
     1:1 AUS DEM WEBSITE-REPO uebernommen (website/src/styles/tokens.css,
     Stand 1a437bc/b1e0078): Die Tarifauswahl zeigt die Preiskarten der
     Website Pixel fuer Pixel. Diese Werte stehen hier und nirgends sonst -
     aendert die Website ihre Karten, wird hier nachgezogen. Kontraste wie
     dort gemessen: --web-text 16.12:1, --web-text-2 7.03:1 auf
     --web-surface-alt; --web-on-dark-2 14.21:1 auf --web-surface-dark. */
  --web-surface: #ffffff;
  --web-surface-alt: #f4f4f5;
  --web-surface-dark: #000000;
  --web-surface-dark-2: #141416;
  --web-text: #18181b;
  --web-text-2: #52525b;
  --web-on-dark: #fafafa;
  --web-on-dark-2: #d4d4d8;
  --web-line: #e4e4e7;
  --web-line-dark: #2a2a2e;
  --web-brand: #0570e4;
  --web-brand-hover: #005cc2;
  --web-brand-on-dark: #4d91ee;

  /* ======================================================================
     SEMANTISCHE TOKENS - ab hier greifen die Komponenten zu.

     Ein spaeterer Dark Mode definiert NUR diesen Block neu:
       @media (prefers-color-scheme: dark) { :root { --surface: ...; } }
     Kein Komponenten-CSS muss dafuer angefasst werden.
     ====================================================================== */

  /* Die Preiskarten der Tarifauswahl - Website-Werte (siehe Primitive oben).
     Im Dark Mode der Verwaltung gelten die dunklen Flaechen der Website
     (--web-surface-dark-2 fuer Karten, wie ihre dunklen Abschnitte). */
  --preis-seite:        var(--web-surface);
  --preis-karte:        var(--web-surface-alt);
  --preis-karte-pro:    var(--web-surface-dark);
  --preis-text:         var(--web-text);
  --preis-text-2:       var(--web-text-2);
  --preis-pro-text:     var(--web-on-dark);
  --preis-pro-text-2:   var(--web-on-dark-2);
  --preis-linie:        var(--web-line);
  --preis-pro-linie:    var(--web-line-dark);
  --preis-marke:        var(--web-brand);
  --preis-marke-hover:  var(--web-brand-hover);
  --preis-marke-text:   var(--web-brand-hover);   /* Zweitknopf auf Grau: .is-alt der Website */
  --preis-marke-dunkel: var(--web-brand-on-dark);
  --preis-auf-marke:    var(--web-surface);
  --preis-schalter:     var(--web-surface-alt);
  --preis-schalter-an:  var(--web-surface);
  --preis-schalter-schatten: 0 1px 3px rgb(15 23 42 / 0.15);   /* .switch der Website */

  /* Flaechen */
  --surface:          var(--neutral-0);    /* Karten, Tabellen, Inputs      */
  --surface-sunken:   var(--neutral-50);   /* Tabellenkopf, Kartenfuss      */
  --surface-hover:    var(--neutral-50);   /* Zeilen-Hover                  */

  /* DER ARBEITSBEREICH IST REINWEISS, DIE SEITENLEISTE ABGESETZT.

     Vorher war es umgekehrt herum gedacht: Seitengrund grau, Karten weiss.
     Das ergibt eine Oberflaeche, auf der jeder Inhalt in einem Kasten sitzt -
     ruhig, aber auch stumpf, und auf einer Seite ohne Karten (ein Formular,
     eine Leermeldung) fehlt der Bezug. Umgekehrt traegt die Flaeche selbst
     den Inhalt, und die Leiste hebt sich als Navigation davon ab.

     Der Ton der Leiste ist bewusst der HELLSTE der Skala: Sie soll sich
     absetzen, nicht zur Wand werden - die Navigation ist nicht der Inhalt.
     Ein Schatten kommt dazu (app.css), damit die Kante auch dort sichtbar
     ist, wo Leiste und Inhalt gleich hell wirken. */
  --surface-content:  var(--neutral-0);
  /* --- Die Seitenleiste ---------------------------------------------------
     EINE EIGENE FLAECHE MIT EIGENEN TEXTTOENEN. Der dunklere Grund kostet
     Kontrast, und zwar an vier Stellen gleichzeitig - alle vier sind unten
     nachgerechnet. Ohne diese Toene waeren Gruppenlabel (2.96:1), Symbole
     (2.96:1), der Abmelden-Link (2.97:1) und der Fokusring (2.97:1) unter
     ihre Schwellen gefallen. */
  --surface-sidebar:       var(--neutral-sidebar);
  /* Fuer den Mauszeiger: heller als der Grund, aber deutlich NICHT weiss -
     sonst saehe jeder ueberfahrene Eintrag aus wie der ausgewaehlte. */
  --surface-sidebar-hover: var(--neutral-200);
  --text-muted-on-sidebar: var(--neutral-600);   /* 4.72:1 - Label, Symbole */
  --action-on-sidebar:     var(--blue-700);      /* 4.60:1 - Link, Fokusring */

  /* Text */
  --text:             var(--neutral-800);
  --text-muted:       var(--neutral-500);
  --text-strong:      var(--neutral-900);
  --text-on-action:   var(--neutral-0);
  /* DIE AKTIONSFARBE ALS SCHRIFT (Dark Mode, 23.9.2026). Im hellen Modus
     dieselben Werte wie --action/--action-hover - im dunklen eine aufgehellte
     Fassung, weil #0570E4 auf dunklem Grund nur 3.8:1 erreicht. Als
     KNOPFFLAECHE mit weisser Schrift bleibt --action unveraendert (B6 b). */
  --action-text:       var(--action);
  --action-text-hover: var(--action-hover);
  /* Die Farbvorschau ohne Skript (siehe --neutral-farbwahl). */
  --farbwahl-vorgabe:  var(--neutral-farbwahl);
  /* IMMER HELL, in beiden Modi (Dark Mode, B5): der Grund hinter dem
     2FA-QR-Code (dunkel auf hell, sonst liest ihn kein Telefon) und die
     Flaeche um das Zahlungsformular von Stripe. */
  --immer-hell:        var(--neutral-0);
  /* Die drei Stufen des Verbrauchsbalkens (Nutzung). Eigene Namen, weil sie
     im dunklen Modus hellere Werte brauchen als die Flaechen, von denen sie
     stammen - ein Balken ist eine Grafik auf dunklem Grund (3:1). */
  --meter-ok:          var(--action);
  --meter-warn:        var(--warning-accent);
  --meter-crit:        var(--danger-solid);
  /* Die Bahn eines ausgeschalteten Schalters (Einstellungs-Fenster). Stand
     bis zum Dark Mode als direkter Griff auf --neutral-control in app.css -
     die eine Stelle, die im dunklen Satz hell geblieben waere (im Browser
     gefunden, tests/test_verwaltung_dunkel_browser.py). */
  --switch-track:      var(--neutral-control);

  /* Linien */
  --border:           var(--neutral-200);        /* dekorativ: Karten, Zeilen */
  --border-strong:    var(--neutral-300);
  --border-control:   var(--neutral-control);    /* Inputs - 3:1 nach 1.4.11  */
  --border-hover:     var(--neutral-500);        /* Rand unter dem Mauszeiger */
  /* Die Kontur um den ausgewaehlten Navigationseintrag. EIGENER NAME, obwohl
     es "nur" eine Rampenstufe ist - und der Grund dafuer ist ein Fehler, der
     genau hier passiert ist: Die Kontur sollte von neutral-500 auf neutral-400
     zurueckgenommen werden, und dafuer wurde sie von --text-placeholder auf
     --border-hover umgestellt. Beide zeigen auf neutral-500. Die Aenderung
     war im Quelltext zu sehen und im Browser nicht vorhanden. */
  --border-pill:      var(--neutral-400);        /* 2.29:1 auf --surface-sidebar */

  /* --- Die stillen Grautoene ---------------------------------------------
     BIS HIERHER GAB ES SIE NICHT, und das war der Grund fuer dreizehn
     Stellen, an denen app.css direkt in die Rampe griff. Wer eine leise
     Schrift brauchte, nahm sich irgendeine Stufe - so kamen vier
     verschiedene Grautoene fuer im Grunde dieselbe Aufgabe zusammen, und
     fuenf davon lagen unter der Lesbarkeitsschwelle.

     Die Namen sagen jetzt, wofuer der Ton da ist, und der Wert dahinter ist
     nachgerechnet statt gegriffen. */
  --text-label:       var(--neutral-600);   /* Tabellenkopf, Kleinlabel 7.58:1 */
  --text-placeholder: var(--neutral-500);   /* Platzhalter im Feld     4.76:1 */
  --icon-muted:       var(--neutral-500);   /* Symbol ohne eigene Aussage     */

  /* --- Flaechen mit Zustand ---------------------------------------------- */
  --surface-raised:   var(--neutral-100);   /* gedrueckter Knopf, Kalenderpfeil */
  --surface-disabled: var(--neutral-200);   /* abgeschalteter Knopf           */

  /* Aktion (Markenblau)

     DIE MARKENFARBE IST DIE AKTIONSFARBE. Beim Rot war das nicht moeglich -
     es verfehlte AA fuer Text und musste in zwei Toene geteilt werden. #0570E4
     traegt Text (4.73:1) und Flaeche, also gibt es hier nur EINEN Wert. */
  --action:           var(--blue-500);
  --action-hover:     var(--blue-600);
  --action-active:    var(--blue-700);
  --action-subtle:    var(--blue-50);   /* Hintergrund aktiver Nav-Eintraege */

  /* Aktionsfarbener TEXT AUF der getoenten Flaeche - und der einzige Grund,
     warum es diesen Token gibt.

     --action auf --action-subtle ergibt 4.23:1 und verfehlt AA. Das ist die
     eine Stelle, an der die helle Markenfarbe nicht reicht: Auf Weiss hat sie
     Luft (4.73), auf einer blauen Toenung nicht mehr. Es ist dasselbe Muster,
     das die Statusfarben unten seit jeher fahren - dort traegt auch nicht
     --danger-600 den Text auf --danger-50, sondern --danger-700. */
  --action-on-subtle: var(--blue-700);  /* 6.53:1 auf --action-subtle       */

  /* Gedaempfter Text auf derselben getoenten Flaeche - das Datum unter dem
     Wochentag in der "heute"-Spalte.

     VORGEFUNDENER MANGEL, nicht durch die Umstellung entstanden: --text-muted
     erreichte schon auf der alten roten Toenung nur 4.38:1 und verfehlte AA.
     Auf der blauen sind es 4.26:1. Da die Regel ohnehin angefasst wurde, ist
     sie jetzt richtig statt nur unveraendert falsch. */
  --text-muted-on-subtle: var(--neutral-600);  /* 6.77:1 auf --action-subtle */

  /* --brand IST ENTFALLEN. Der Token faerbte das blaue Quadrat mit "21" in der
     Wortmarke - einen Platzhalter, der galt, solange keine Logodatei vorlag.
     Seit app/static/img/logo.png existiert, ist die Marke ein Bild und traegt
     ihre Farbe selbst. Ein Token ohne Verwendung waere eine Einladung, ihn
     irgendwo einzusetzen, wo er nichts bedeutet.

     DIE MARKE IST ROT, DIE OBERFLAECHE BLAU, und das ist Absicht: Das Logo ist
     die Marke, --action ist die Aktionsfarbe. Wer beides angleicht, nimmt der
     Marke ihre Wiedererkennung. */
  --focus-ring:       var(--blue-500);  /* 4.73:1 - Schwelle waere 3:1        */

  /* Der weiche Hof um ein fokussiertes Eingabefeld - 3px in einer sehr
     hellen Toenung, hinter dem harten Rand. Er traegt keine Information und
     muss deshalb keinen Kontrast erfuellen; er macht das aktive Feld auf
     einem langen Formular auffindbar. Zuvor griffen die Formularregeln dafuer
     direkt auf --red-100 zu und umgingen damit die semantische Schicht. */
  --focus-halo:        var(--blue-100);
  --danger-focus-halo: var(--danger-100);

  /* Status - je Paar aus Flaeche, Linie, Text */
  --success-bg:     var(--green-50);
  --success-border: var(--green-200);
  --success-text:   var(--green-700);
  --success-icon:   var(--green-600);

  /* WARNUNG - SEIT SEPTEMBER 2026 ZWEI ERSCHEINUNGSFORMEN, und das ist
     Absicht: Das BANNER steht auf weisser Flaeche und traegt die Farbe nur
     noch als Leiste, Symbol und fette Einleitung (siehe .banner--warn in
     app.css). BADGE und der Hinweis in der Tour-Karte bleiben getoente
     Flaechen - eine Pille ohne Flaeche waere keine Pille mehr.

     DESHALB BLEIBEN -bg, -border UND -text UNVERAENDERT. Ihre Werte sind
     nachgerechnet, sie tragen die getoenten Bausteine weiter, und
     --warning-text ist ausserdem an eine feste Farbe im Chat-Widget
     gekoppelt (tests/test_design_tokens.py, WIDGET_FARBEN). Neu sind nur
     die beiden Toene fuer das Banner auf Weiss. */
  --warning-bg:     var(--amber-50);
  --warning-border: var(--amber-200);
  --warning-text:   var(--amber-800);
  --warning-icon:   var(--amber-700);   /* 4.92:1 auf Weiss, 4.75:1 auf --warning-bg */
  --warning-accent: var(--amber-600);   /* Leiste am Banner, 3.19:1 auf Weiss */
  --warning-strong: var(--amber-800);   /* fette Einleitung, 7.09:1 auf Weiss */

  /* Die Vorwarnung vor der selbsttaetigen Abmeldung - BLAU und mit EIGENEN
     Tokens.

     WARUM NICHT --warning-*: Die Warnfarbe gehoert den Bannern, die
     Handlungsbedarf melden ("1 offene Anfrage", "E-Mail bestaetigen"). Teilte
     die Abmeldewarnung sie, wuerde jede Aenderung an der einen die andere
     mitnehmen - und die Warnung vor dem Abmelden ist ein Hinweis mit
     Countdown, keine Stoerung (SECURITY.md Kapitel 26, J).

     GETOENT UND NICHT WEISS MIT BLAUER LEISTE: So saehe der Kasten aus wie
     die Tour-Karte, die an derselben Kante sitzt.

     Nachgerechnet: --text auf dem Grund 13.06:1, die Leiste 5.72:1 auf dem
     Grund und 6.41:1 auf Weiss. Der Knopfrand braucht einen eigenen Wert:
     --border-control erreicht auf der Toenung nur 2.78:1 und verfehlt damit
     WCAG 1.4.11, --neutral-500 traegt 4.25:1. */
  --session-notice-bg:             var(--blue-50);
  --session-notice-border:         var(--blue-200);   /* dekorativ */
  --session-notice-accent:         var(--blue-600);
  --session-notice-control-border: var(--neutral-500);

  /* Gefahr - seit der Umstellung auf Blau eine EIGENSTAENDIGE Farbe und
     nicht mehr die Marke. Siehe die Begruendung an der Ramp oben. */
  --danger-bg:      var(--danger-50);
  --danger-border:  var(--danger-200);
  --danger-text:    var(--danger-700);
  --danger-icon:    var(--danger-600);
  --danger-solid:   var(--danger-600);   /* gefuellter Loeschen-Knopf */

  --info-bg:        var(--neutral-50);
  --info-border:    var(--neutral-200);
  --info-text:      var(--neutral-600);
  --info-icon:      var(--neutral-500);

  /* Die beiden Kalender im Wochenraster - je Kalender dasselbe Trio aus
     Flaeche, Kante und Text wie bei den Statusfarben oben. */
  --cal-consultation-bg:     var(--violet-50);
  --cal-consultation-edge:   var(--violet-600);
  --cal-consultation-text:   var(--violet-800);

  --cal-callback-bg:         var(--teal-50);
  --cal-callback-edge:       var(--teal-600);
  --cal-callback-text:       var(--teal-800);

  /* Online, Probetraining, Besichtigung (SECURITY.md Kapitel 37). Bis dahin
     standen sie im Raster ohne eigene Farbe; im persoenlichen Kalender
     stehen sie gemischt neben Beratung und Rueckruf und brauchen eine.
     Probetraining und Besichtigung teilen sich Bernstein: Keine Branche hat
     beide (SECURITY.md Kapitel 35), sie stehen nie nebeneinander. */
  --cal-online-bg:           var(--lime-50);
  --cal-online-edge:         var(--lime-700);
  --cal-online-text:         var(--lime-800);

  --cal-branche-bg:          var(--amber-50);
  --cal-branche-edge:        var(--amber-700);
  --cal-branche-text:        var(--amber-800);

  /* Das Videogespraech (SECURITY.md Kapitel 32).

     EINE EIGENE FAMILIE UND KEINE AUSNAHME: Die Buehne ist die einzige
     Flaeche des Projekts, die IMMER dunkel ist - auch im hellen Modus. Ein
     Videobild auf weissem Grund blendet, und die Kachel ohne Bild soll sich
     als Flaeche zeigen und nicht als Loch.

     WARUM TROTZDEM UEBER DIE SEMANTISCHE SCHICHT: Ein direkter Griff in die
     Rampe (`var(--neutral-900)`) waere im vorbereiteten Dark Mode die eine
     Stelle, die ihre Farbe behaelt - und dann steht die dunkle Buehne auf
     dunklem Grund ohne Kante. Hier steht sie als Name, und der Dark Mode
     kann ihn umdefinieren, ohne app.css anzufassen.

     `--gespraech-text` IST WEISS UND BLEIBT ES: Er liegt auf der Buehne,
     nicht auf der Seite. Geprueft wird das Paar in tests/test_design_tokens
     wie jedes andere. */
  --gespraech-buehne:        var(--neutral-900);
  /* Die Vorschau des Dunkel-Logos in der Verwaltung (Dark Mode des Widgets):
     IMMER dunkel, damit die Fahrschule sieht, wie ihr Logo auf dem dunklen
     Chatfenster wirkt - unabhaengig davon, wie sie selbst die Verwaltung
     sieht. */
  --logo-vorschau-dunkel:    var(--neutral-900);
  --gespraech-kachel:        var(--neutral-800);
  --gespraech-nah:           var(--neutral-700);
  --gespraech-text:          var(--neutral-0);
  --gespraech-text-leise:    var(--neutral-300);
  --gespraech-rahmen:        var(--neutral-0);
  /* Der Kreis des Platzhalters, wenn die Kamera aus ist. HELLE SCHRIFT AUF
     DUNKLEM KREIS - umgekehrt zum Kontomenue, wo ein weisser Kreis auf
     blauem Grund steht. Der Grund ist derselbe: Der Kreis muss sich von
     SEINEM Untergrund abheben, und der ist hier die dunkle Kachel. */
  --gespraech-avatar-bg:     var(--neutral-700);
  --gespraech-avatar-text:   var(--neutral-0);

  /* --- Typografie --------------------------------------------------------
     System-Font-Stack statt Web-Font: die CSP erlaubt mit `font-src 'self'`
     keine CDN-Schrift, und eine selbst gehostete waere hier 200 KB fuer einen
     Unterschied, den auf macOS niemand sieht (dort rendert das als San
     Francisco). Kein Ladezeitverlust, kein Datenabfluss an Dritte. */
  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
               "Helvetica Neue", Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
               "Liberation Mono", monospace;

  /* Groessen. Line-Height sinkt mit steigender Groesse: grosse Schrift
     braucht relativ weniger Durchschuss, sonst zerfaellt die Ueberschrift.

     EINE STUFE GROESSER ALS URSPRUENGLICH. Die Skala stand auf 16/14/12 - den
     Werten, mit denen Entwicklerwerkzeuge und Dashboards gebaut werden. Diese
     Oberflaeche bedient aber niemand acht Stunden am 27-Zoll-Schirm: Ein
     Fahrschulinhaber traegt zwischen zwei Fahrstunden einen Termin nach.
     Dafuer ist 14px UI-Text zu klein, und der Tabellentext bei 12px erst
     recht.

     DIE GANZE SKALA WANDERT MIT, nicht nur der Fliesstext. Waere nur
     --text-base gewachsen, laege die Ueberschrift ploetzlich naeher am Text
     als vorher, und die Rangfolge, die die Groessen herstellen, waere
     eingeebnet. Die Abstaende zwischen den Stufen bleiben deshalb dieselben. */
  --text-3xl:      1.875rem;  /* 30px - H1          */
  --text-3xl-lh:   2.25rem;
  --text-xl:       1.375rem;  /* 22px - H2          */
  --text-xl-lh:    1.875rem;
  --text-lg:       1.125rem;  /* 18px - H3          */
  --text-lg-lh:    1.625rem;
  --text-base:     1.0625rem; /* 17px - Fliesstext  */
  --text-base-lh:  1.625rem;
  --text-sm:       0.9375rem; /* 15px - UI, Tabelle */
  --text-sm-lh:    1.375rem;
  --text-xs:       0.8125rem; /* 13px - Caption     */
  --text-xs-lh:    1.125rem;

  --weight-normal:   400;
  --weight-medium:   500;
  --weight-semibold: 600;
  --weight-bold:     700;

  /* --- Abstaende (4px-Basis) --------------------------------------------- */
  /* HALBE STUFEN. Der Viererraster kennt kein "halbwegs" - zwischen 8 und
     12px liegt nichts, zwischen 16 und 24 auch nicht. Genau das wurde beim
     Zurechtruecken der Seitenleiste gebraucht: nicht zurueck auf das enge
     Mass, aber deutlich unter dem grosszuegigen. Tailwind, dessen Skala diese
     hier ist, fuehrt dieselben Zwischenstufen (1.5, 2.5, 5). */
  --space-1:    0.25rem;   /*  4px */
  --space-1-5:  0.375rem;  /*  6px */
  --space-2:    0.5rem;    /*  8px */
  --space-2-5:  0.625rem;  /* 10px */
  --space-3:  0.75rem;   /* 12px */
  --space-4:  1rem;      /* 16px */
  --space-5:  1.25rem;   /* 20px */
  --space-6:  1.5rem;    /* 24px */
  --space-8:  2rem;      /* 32px */
  --space-10: 2.5rem;    /* 40px */
  --space-12: 3rem;      /* 48px */

  /* --- Radien ------------------------------------------------------------

     DREI EBENEN, UND DIE REGEL LAUTET: EIN KASTEN IN EINEM KASTEN TRAEGT DEN
     KLEINEREN BOGEN.

       Ebene 1  Flaechen, die AUF dem Blatt liegen: .card, .session-warning.
                --radius-lg, dazu ein Schatten.
       Ebene 2  Kaesten IN einer Karte: .legal-text, .embed-code, .banner,
                .empty__icon, .appt, .appt__state, .freeslot, .passwort.
                --radius oder --radius-sm, kein Schatten.
       Ebene 3  Bedienelemente: .btn, textarea, .switch__item, .chip.
                --radius oder --radius-sm.

     WARUM DAS AUFGESCHRIEBEN STEHT: Beim Anheben des Kartenradius von 10 auf
     12 lag der Vorschlag auf dem Tisch, "alle Kaesten im Projekt" auf
     denselben Wert zu ziehen. Die Vorlagen zeigten dann, dass .legal-text
     und .embed-code INNERHALB einer .card stehen - derselbe Bogen haette
     dort zwei Ecken aufeinandergelegt. Und .empty__icon war beim Anheben
     stillschweigend mitgewandert und wirkte auf 48x48 runder als die Karte,
     in der es sitzt.

     Ein gemeinsamer Radius fuer alles klingt nach Konsequenz und ist das
     Gegenteil: Er ignoriert, dass der Bogen im Verhaeltnis zur Flaeche
     gelesen wird.
     ---------------------------------------------------------------------- */
  --radius-sm: 4px;
  --radius:    6px;   /* Buttons, Inputs */
  /* Karten. AM REFERENZBILD GEMESSEN und nicht geschaetzt: Der Bogen der
     Kartenecke dort laeuft ueber 22 Geraetepixel, bei doppelter Aufloesung
     also 11 Punkte; der glatte Wert im 2er-Raster dieser Skala ist 12.

     VORHER STANDEN HIER 10px. Der Unterschied ist klein und war nicht die
     Ursache des Eindrucks, unsere Karten seien "kantiger" - das war der
     fehlende Schatten (siehe unten). Er wird trotzdem mitgezogen, weil die
     Messung ihn hergibt. */
  --radius-lg: 12px;  /* Karten          */
  /* Die Pille des aktiven Navigationseintrags. Aus dem Referenzbild
     abgeleitet: Der Radius betraegt dort rund 0.29 der Eintragshoehe, bei 48px
     also 14. Die Stufe steht hier und nicht als Zahl in app.css, damit sie
     auffindbar bleibt, wenn eine zweite Stelle sie braucht. */
  --radius-xl: 14px;  /* Navigationspille */
  --radius-full: 999px;

  /* --- Schatten ----------------------------------------------------------
     Bewusst flach.

     =====================================================================
     HIER STAND: "Karten bekommen ENTWEDER Border ODER Schatten, nicht
     beides - die Kombination ist der haeufigste Grund, warum selbstgebaute
     Oberflaechen 'schwer' wirken."
     =====================================================================
     DIE REGEL IST ZURUECKGENOMMEN, und der Grund ist eine Messung. Eine
     Referenzoberflaeche wurde beanstandet mit "unsere Kartenraender sind
     deutlich schwaecher konturiert". Die naheliegende Erklaerung war ein zu
     heller Rahmen - und sie war falsch: Ausgemessen traegt die Referenz
     rgb(227,232,240), unser --border traegt rgb(226,232,240). Derselbe Ton,
     ein Wert Unterschied in einem Kanal.

     Was die Referenzkarte als eigenstaendige Flaeche lesbar macht, ist ein
     WEICHER AUSSENSCHATTEN NEBEN dem Rahmen. Auch das ist gemessen: seitlich
     sechs Geraetepixel Verlauf von 241 auf 255, oben nur vier und nur bis
     250 - die Signatur eines Schattens mit einem Punkt Versatz nach unten.
     Das ist fast auf den Wert --shadow, wie er hier seit je steht und
     nirgends benutzt wurde.

     Die alte Regel war also nicht falsch, sondern zu grob: Die Kombination
     macht eine Oberflaeche schwer, wenn der Schatten kraeftig ist. Bei
     sechs Prozent Deckung auf drei Punkt Weichzeichnung tut sie das nicht -
     sie setzt die Karte auf das Blatt, statt sie hineinzustanzen.

     WAS WEITERHIN GILT: Kein Baustein bekommt --shadow-lg zusaetzlich zum
     Rahmen. Der ist fuer Dinge gedacht, die ueber der Seite schweben
     (Dialoge), und dort traegt er den Abstand allein. */
  /* DER GRUND HINTER EINEM DIALOG (Kapitel 29, das Einstellungs-Fenster).

     ER STEHT HIER UND NICHT IN app.css, weil er eine FARBE ist und jede
     Farbe ueber die semantische Schicht laeuft - im Dark Mode wird er
     kraeftiger, nicht heller, und das entscheidet dann diese Datei.

     50 PROZENT: Bei 30 blieb die Seite dahinter lesbar und zog das Auge auf
     Text, der gerade nicht gemeint ist; bei 80 sah es aus, als sei die
     Anwendung verlassen worden. Bei 50 erkennt man, WO man ist, ohne dort
     weiterlesen zu wollen.

     NEUTRALES SCHWARZ UND NICHT --neutral-900 (19.9.2026). Der Grundton der
     Rampe ist ein Marineblau (#0f172a); ueber einer weissen Seite legte er
     einen deutlich blauen Schleier darueber, und wo dahinter nichts stand,
     sah das Ergebnis aus wie eine deckende Markenflaeche statt wie eine
     abgedunkelte Seite. Ein Dialoggrund soll die Farbe der Seite daempfen,
     nicht sie einfaerben. */
  --overlay: rgba(0, 0, 0, 0.5);
  --shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.04);
  --shadow:    0 1px 3px rgba(15, 23, 42, 0.06), 0 1px 2px rgba(15, 23, 42, 0.04);
  --shadow-lg: 0 10px 24px rgba(15, 23, 42, 0.10), 0 2px 6px rgba(15, 23, 42, 0.05);
  /* Der Schatten unter dem ausgewaehlten Navigationseintrag. DEUTLICH
     KRAEFTIGER ALS --shadow, und das ist Absicht: Er liegt auf einem
     mittelhellen Grund, nicht auf Weiss, und muss dort noch zu sehen sein.
     Zwei Lagen - eine enge fuer die Kante, eine weite fuer den Abstand. */
  --shadow-pill: 0 1px 2px rgba(15, 23, 42, 0.14), 0 4px 12px rgba(15, 23, 42, 0.18);

  /* --- Layout ------------------------------------------------------------ */
  --sidebar-width: 256px;
  --content-max:   1320px;   /* damit auf grossen Schirmen nichts ausreisst */
  /* Ein Kaestchen im Wochenraster - Termin wie freier Platz. EINE Hoehe fuer
     beide, damit das Raster nicht bei jedem Termin eine andere Zeilenhoehe
     bekommt. Gemessen schwankten die Termine zwischen 52 und 94px, je
     nachdem, ob der Name umbrach und ob ein Zustand dranstand.

     52px sind zwei Zeilen Kleinschrift plus Polsterung - die Hoehe, die ein
     Termin ohne Zustand ohnehin hatte. */
  --slot-height: 52px;

  /* Die Hoehe einer Tagesspalte in der Terminvorschau der Startseite - dort
     seit 973b75b (04.09.2026) als `min-height: 320px` an .preview__entries,
     gemessen fuer vier Termine samt Jetzt-Linie. Seit September 2026 AUCH
     die Mindesthoehe des Zeilenbereichs im Wochenraster (unter dem
     Tageskopf): Kurze Regeln werden mit abgeschalteten Zeilen bis hierhin
     aufgefuellt. EIN Wert fuer beide - wer ihn aendert, aendert beide. */
  --preview-entries-height: 320px;

  /* Die HOECHSTHOEHE des rollbaren Zeilenbereichs im Wochenraster
     (21.9.2026). Bis dahin wuchs der Kasten mit jeder Zeitzeile: gemessen
     2526px bei einer Regel von 8 bis 17 Uhr, und die Seite darunter 3126px
     in einem 900px hohen Fenster - wer die letzte Zeile sehen wollte, verlor
     Kopfzeile, Wochennavigation und Kartenrand aus dem Blick.

     ABGELEITET UND NICHT ERFUNDEN: doppelt so hoch wie eine Tagesspalte der
     Terminvorschau. Dort steht EIN Tag, hier eine ganze Woche - und zwei
     Vorschauhoehen sind das, was in einem 900px-Fenster neben Kopfzeile,
     Wochennavigation und Kartenkopf noch Platz hat. Wer die Vorschauhoehe
     aendert, aendert beides zusammen; das ist der Sinn der Ableitung. */
  --raster-max-height: calc(var(--preview-entries-height) * 2);

  --control-height: 44px;    /* eine Hoehe fuer Inputs UND Buttons          */
  /* Die kleine Fassung - heute nur das Suchfeld des Einstellungs-Fensters.

     WARUM ES SIE BRAUCHT: Dort steht das Feld ueber einer Liste aus 34px
     hohen Eintraegen. Mit den vollen 44px war es das groesste Element der
     Leiste und zog das Auge auf sich, obwohl es die Nebensache ist - man
     sucht selten, man klickt meistens. 36px sitzt zwischen beiden und
     ordnet sich unter.

     ALS TOKEN UND NICHT ALS ZAHL AN DER KOMPONENTE, weil die naechste
     kompakte Leiste dieselbe Hoehe braucht - und weil eine Zahl in app.css
     die Stelle waere, an der die zweite Leiste 35px bekommt. */
  --control-height-sm: 36px;

  /* Die Polsterung der Inhaltsspalte - EINE Stelle fuer JEDE Seite hinter der
     Anmeldung.

     WARUM ALS TOKEN UND NICHT JE SEITE: Genau das ist der Fehler, den diese
     Datei verhindern soll. Wer den Rand einer einzelnen Seite grosszuegiger
     macht, hat danach eine Seite, die anders aussieht als die anderen - und
     merkt es erst, wenn er zwischen ihnen hin und her wechselt. Der Wert
     gehoert deshalb hierher, nicht in eine Komponentenregel.

     Die schmale Fassung gilt unter 900px, wo die Sidebar zur Kopfleiste wird
     (app.css). Sie ist nicht einfach die halbe: Auf einem Telefon ist Rand
     verschenkte Zeilenlaenge, auf einem grossen Schirm ist er Ruhe. */
  --content-padding:        var(--space-10);   /* 40px */
  --content-padding-narrow: var(--space-6);    /* 24px, unter 900px */

  /* Der Abstand ZWISCHEN den Bloecken einer Seite. Er ist der zweite Grund,
     warum eine Verwaltungsoberflaeche eng wirkt: Nicht der Rand aussen ist
     zu knapp, sondern die Kaesten stehen zu dicht beieinander und lesen sich
     als eine Flaeche statt als mehrere Abschnitte. */
  --section-gap: var(--space-12);   /* 48px zwischen Abschnitten */
  --card-gap:    var(--space-6);    /* 24px zwischen zwei Karten */

  /* Die Polsterung IN einer Karte.

     ACHTUNG BEIM AENDERN: Kopf, Rumpf und Fuss einer Karte teilen sich diesen
     Wert, damit ihr Text auf einer Kante steht. Die Zellen einer Tabelle in
     einer randlosen Karte (.card--flush) tun das NICHT - sie stehen bewusst
     enger (--space-4), weil eine Datentabelle mit 40px Zellrand auseinander
     faellt. Wer diesen Wert erhoeht, vergroessert damit den sichtbaren Versatz
     zwischen Kartenkopf und Tabellenspalte darunter. */
  --card-padding: var(--space-6);   /* 24px */

  /* Die WAAGERECHTE Polsterung einer Tabellenzelle.

     ALS EIGENES TOKEN UND BREITENABHAENGIG, und beides ist das Ergebnis
     einer Messung. Am Referenzbild liegt die erste Tabellenspalte 38 Punkte
     von der Kartenkante entfernt; bei uns lagen 16px Zellrand, also 18
     Punkte. Der Wert wurde auf 24 gehoben - und dann auf vier
     Bildschirmbreiten nachgesehen, was das anrichtet:

         /admin/services   1440px  kein Ueberlauf -> kein Ueberlauf
                           1100px  43px           -> 139px
                            900px  KEINER         -> 35px
                            768px  71px           -> 167px

     Der Einwand am --card-padding darueber ("eine Datentabelle mit 40px
     Zellrand faellt auseinander") trifft also zu, nur eine Stufe frueher als
     erwartet: Nicht die Zahl ist das Problem, sondern die Breite, auf der
     sie gilt. Auf 1440 Punkten ist grosszuegiger Zellrand Ruhe; auf 768 ist
     er verschenkte Spaltenbreite und schiebt die Aktionsspalte aus dem Bild.

     Dieselbe Ueberlegung steht schon bei --content-padding: "Auf einem
     Telefon ist Rand verschenkte Zeilenlaenge, auf einem grossen Schirm ist
     er Ruhe." Der schmale Wert ist deshalb der alte - 16px, unveraendert
     gegenueber dem Stand vor dieser Aenderung.

     DIE UMSCHALTUNG SITZT BEI 1100px, nicht bei 900, wo die Seitenleiste
     umbricht: Der Schwellenwert ist eine Aussage ueber die verfuegbare
     Tabellenbreite. Bei 900 waere er zu spaet gekommen und haette die
     Verschlechterung auf 1100 stehen gelassen - der Breite, auf der die
     meisten Laptops arbeiten. Nachgemessen tragen 1100, 900 und 768 nach
     der Umstellung genau die Werte von vorher. */
  --cell-padding-x:        var(--space-6);   /* 24px */
  --cell-padding-x-narrow: var(--space-4);   /* 16px, unter 900px */

  /* Die Karte VOR der Anmeldung ist grosszuegiger als eine im Arbeitsbereich.

     WARUM NICHT DERSELBE WERT: Sie steht allein auf einer leeren Seite und
     traegt zwei Felder. Mit der Polsterung des Arbeitsbereichs wirkt sie
     darin verloren - dort sitzt eine Karte zwischen anderen und bekommt ihr
     Gewicht aus der Nachbarschaft, hier hat sie keine. Es ist derselbe
     Grund, aus dem die Spalte hier schmaler ist als der Inhaltsbereich. */
  --auth-card-padding: var(--space-10);  /* 40px */

  /* --- Grenzen INNERHALB einer Seite -------------------------------------
     Die Inhaltsspalte laeuft ueber die volle Breite (siehe app.css). Damit
     ist der Deckel nicht verschwunden, sondern gewandert: von der Seite auf
     die einzelnen Bausteine, die ihn wirklich brauchen.

     WARUM DIESE ZWEI UND NICHT DIE SEITE: Eine Tabelle mit acht Spalten
     gewinnt mit jedem Pixel, ein Eingabefeld nicht - ein Textfeld ueber 1900
     Pixel ist kein grosszuegiges Feld, sondern ein unlesbares. Und ein
     Fliesstext, dessen Zeilen ueber den ganzen Schirm laufen, verliert beim
     Zeilenwechsel den Anschluss; die Lesbarkeitsforschung nennt 60 bis 80
     Zeichen, das sind ungefaehr diese 40rem. */
  --field-max: 32rem;   /* 512px - Eingabefelder und Auswahllisten */

  /* DIE BREITE EINES FELDES IST EIN VERSPRECHEN darueber, wie viel
     hineingehoert. Ein Feld fuer "6" in derselben Breite wie eines fuer eine
     URL sagt "hier kommt etwas Langes hin" - und ist damit eine Auskunft, die
     nicht stimmt. Zwei engere Masse fuer Angaben mit bekannter Laenge: */
  --field-max-sm: 11rem;  /* 176px - Datum, Uhrzeit                     */
  --field-max-xs: 8rem;   /* 128px - Anzahl, Dauer, Betrag              */
  --prose-max: 40rem;   /* 640px - erklaerende Absaetze              */
}

/* ==========================================================================
   DER DUNKLE MODUS DER VERWALTUNG (Dark Mode, 23.9.2026, SECURITY.md Kap. 34)
   ==========================================================================
   Die Zusage aus dem Kopf dieser Datei wird eingeloest: Der dunkle Modus
   definiert NUR die semantischen Tokens neu. Kein Komponenten-CSS in app.css
   kennt ihn.

   WER WELCHEN SATZ BEKOMMT, entscheidet eine Klasse am <html>, die der
   Server beim Ausliefern setzt (app/core/farbschema.py) - und deshalb gibt es
   kein Aufblitzen, obwohl die CSP Inline-Skripte verbietet:
     modus-hell     immer hell (auch Kundenseite des Meeting-Raums,
                    Zahlungsdokument, Widget-Vorschau)
     modus-dunkel   immer dunkel
     modus-system   folgt dem Geraet - ebenso jede Seite OHNE Klasse (alles
                    vor der Anmeldung)

   DERSELBE BLOCK STEHT ZWEIMAL, fuer die Klasse und fuer die Media-Query.
   CSS kann beides nicht in einem Selektor ausdruecken; die beiden Bloecke
   haelt tests/test_verwaltung_dunkel.py Zeichen fuer Zeichen gleich.

   NUR FUER DEN BILDSCHIRM: Gedruckt wird immer hell.

   KONTRASTE nachgerechnet in tests/test_verwaltung_dunkel.py (dieselbe
   Paarliste wie im hellen Modus) und im Browser gemessen. */
@media screen {
  html.modus-dunkel {
    color-scheme: dark;
    --surface: #1a1d22;
    --surface-sunken: #20242a;
    --surface-hover: #262a31;
    --surface-content: #15171b;
    --surface-sidebar: #111316;
    --surface-sidebar-hover: #262b33;
    --text-muted-on-sidebar: #a3abb6;
    --action-on-sidebar: #7fb0f5;
    --text: #e6e8eb;
    --text-muted: #a3abb6;
    --text-strong: #f5f6f8;
    --text-on-action: #ffffff;
    --border: #2c3139;
    --border-strong: #3a4049;
    --border-control: #6f7886;
    --border-hover: #8d96a3;
    --border-pill: #4d5563;
    --text-label: #b7bfca;
    --text-placeholder: #949dab;
    --icon-muted: #97a0ac;
    --surface-raised: #2d323a;
    --surface-disabled: #2d323a;
    --action: #0570e4;
    --action-hover: #005cc2;
    --action-active: #0053b2;
    --action-text: #7fb0f5;
    --action-subtle: #132640;
    --action-on-subtle: #8cbaf7;
    --text-muted-on-subtle: #b7bfca;
    --focus-ring: #5d9cf5;
    --focus-halo: #1d3a63;
    --danger-focus-halo: #4a1f1c;
    --success-bg: #10281f;
    --success-border: #1e5a44;
    --success-text: #6ee7b7;
    --success-icon: #34d399;
    --warning-bg: #2b2310;
    --warning-border: #6b5217;
    --warning-text: #fcd34d;
    --warning-icon: #f5b83d;
    --warning-accent: #f59e0b;
    --warning-strong: #fcd34d;
    --session-notice-bg: #132640;
    --session-notice-border: #264a78;
    --session-notice-accent: #5d9cf5;
    --session-notice-control-border: #8d96a3;
    --danger-bg: #2d1614;
    --danger-border: #6b2a24;
    --danger-text: #fda29b;
    --danger-icon: #f87066;
    --danger-solid: #c81e14;
    --info-bg: #20242a;
    --info-border: #2c3139;
    --info-text: #b7bfca;
    --info-icon: #97a0ac;
    --cal-consultation-bg: #241a3d;
    --cal-consultation-edge: #a78bfa;
    --cal-consultation-text: #ddd6fe;
    --cal-callback-bg: #0f2b29;
    --cal-callback-edge: #2dd4bf;
    --cal-callback-text: #99f6e4;
    --cal-online-bg: #1a2e05;
    --cal-online-edge: #a3e635;
    --cal-online-text: #d9f99d;
    --cal-branche-bg: #2b200c;
    --cal-branche-edge: #f5b83d;
    --cal-branche-text: #fcd34d;
    --gespraech-buehne: #0f172a;
    --logo-vorschau-dunkel: #0f172a;
    --gespraech-kachel: #1e293b;
    --gespraech-nah: #334155;
    --gespraech-text: #ffffff;
    --gespraech-text-leise: #cbd5e1;
    --gespraech-rahmen: #ffffff;
    --gespraech-avatar-bg: #334155;
    --gespraech-avatar-text: #ffffff;
    --action-text-hover: #a9ccf9;
    --meter-ok: #5d9cf5;
    --meter-warn: #f5b83d;
    --meter-crit: #f87066;
    --switch-track: #6f7886;
    --preis-seite: #15171b;
    --preis-karte: #141416;
    --preis-karte-pro: #000000;
    --preis-text: #fafafa;
    --preis-text-2: #d4d4d8;
    --preis-pro-text: #fafafa;
    --preis-pro-text-2: #d4d4d8;
    --preis-linie: #2a2a2e;
    --preis-pro-linie: #48484a;
    --preis-marke: #0570e4;
    --preis-marke-hover: #005cc2;
    --preis-marke-text: #4d91ee;
    --preis-marke-dunkel: #4d91ee;
    --preis-auf-marke: #ffffff;
    --preis-schalter: #141416;
    --preis-schalter-an: #2a2a2e;
    --preis-schalter-schatten: 0 1px 3px rgba(0, 0, 0, 0.5);
    --overlay: rgba(0, 0, 0, 0.65);
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow:    0 1px 3px rgba(0, 0, 0, 0.5), 0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow-lg: 0 10px 24px rgba(0, 0, 0, 0.55), 0 2px 6px rgba(0, 0, 0, 0.4);
    --shadow-pill: 0 1px 2px rgba(0, 0, 0, 0.45), 0 4px 12px rgba(0, 0, 0, 0.5);
  }
}

@media screen and (prefers-color-scheme: dark) {
  html.modus-system,
  html:not(.modus-hell):not(.modus-dunkel) {
    color-scheme: dark;
    --surface: #1a1d22;
    --surface-sunken: #20242a;
    --surface-hover: #262a31;
    --surface-content: #15171b;
    --surface-sidebar: #111316;
    --surface-sidebar-hover: #262b33;
    --text-muted-on-sidebar: #a3abb6;
    --action-on-sidebar: #7fb0f5;
    --text: #e6e8eb;
    --text-muted: #a3abb6;
    --text-strong: #f5f6f8;
    --text-on-action: #ffffff;
    --border: #2c3139;
    --border-strong: #3a4049;
    --border-control: #6f7886;
    --border-hover: #8d96a3;
    --border-pill: #4d5563;
    --text-label: #b7bfca;
    --text-placeholder: #949dab;
    --icon-muted: #97a0ac;
    --surface-raised: #2d323a;
    --surface-disabled: #2d323a;
    --action: #0570e4;
    --action-hover: #005cc2;
    --action-active: #0053b2;
    --action-text: #7fb0f5;
    --action-subtle: #132640;
    --action-on-subtle: #8cbaf7;
    --text-muted-on-subtle: #b7bfca;
    --focus-ring: #5d9cf5;
    --focus-halo: #1d3a63;
    --danger-focus-halo: #4a1f1c;
    --success-bg: #10281f;
    --success-border: #1e5a44;
    --success-text: #6ee7b7;
    --success-icon: #34d399;
    --warning-bg: #2b2310;
    --warning-border: #6b5217;
    --warning-text: #fcd34d;
    --warning-icon: #f5b83d;
    --warning-accent: #f59e0b;
    --warning-strong: #fcd34d;
    --session-notice-bg: #132640;
    --session-notice-border: #264a78;
    --session-notice-accent: #5d9cf5;
    --session-notice-control-border: #8d96a3;
    --danger-bg: #2d1614;
    --danger-border: #6b2a24;
    --danger-text: #fda29b;
    --danger-icon: #f87066;
    --danger-solid: #c81e14;
    --info-bg: #20242a;
    --info-border: #2c3139;
    --info-text: #b7bfca;
    --info-icon: #97a0ac;
    --cal-consultation-bg: #241a3d;
    --cal-consultation-edge: #a78bfa;
    --cal-consultation-text: #ddd6fe;
    --cal-callback-bg: #0f2b29;
    --cal-callback-edge: #2dd4bf;
    --cal-callback-text: #99f6e4;
    --cal-online-bg: #1a2e05;
    --cal-online-edge: #a3e635;
    --cal-online-text: #d9f99d;
    --cal-branche-bg: #2b200c;
    --cal-branche-edge: #f5b83d;
    --cal-branche-text: #fcd34d;
    --gespraech-buehne: #0f172a;
    --logo-vorschau-dunkel: #0f172a;
    --gespraech-kachel: #1e293b;
    --gespraech-nah: #334155;
    --gespraech-text: #ffffff;
    --gespraech-text-leise: #cbd5e1;
    --gespraech-rahmen: #ffffff;
    --gespraech-avatar-bg: #334155;
    --gespraech-avatar-text: #ffffff;
    --action-text-hover: #a9ccf9;
    --meter-ok: #5d9cf5;
    --meter-warn: #f5b83d;
    --meter-crit: #f87066;
    --switch-track: #6f7886;
    --preis-seite: #15171b;
    --preis-karte: #141416;
    --preis-karte-pro: #000000;
    --preis-text: #fafafa;
    --preis-text-2: #d4d4d8;
    --preis-pro-text: #fafafa;
    --preis-pro-text-2: #d4d4d8;
    --preis-linie: #2a2a2e;
    --preis-pro-linie: #48484a;
    --preis-marke: #0570e4;
    --preis-marke-hover: #005cc2;
    --preis-marke-text: #4d91ee;
    --preis-marke-dunkel: #4d91ee;
    --preis-auf-marke: #ffffff;
    --preis-schalter: #141416;
    --preis-schalter-an: #2a2a2e;
    --preis-schalter-schatten: 0 1px 3px rgba(0, 0, 0, 0.5);
    --overlay: rgba(0, 0, 0, 0.65);
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow:    0 1px 3px rgba(0, 0, 0, 0.5), 0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow-lg: 0 10px 24px rgba(0, 0, 0, 0.55), 0 2px 6px rgba(0, 0, 0, 0.4);
    --shadow-pill: 0 1px 2px rgba(0, 0, 0, 0.45), 0 4px 12px rgba(0, 0, 0, 0.5);
  }
}
