/* ==========================================================================
   oeffentlich.css - die geteilte Stildatei der VIER Seiten VOR der Anmeldung
   (3.0.102, Andis Antworten 7:b und 9:b).

   ⛔ WARUM ES DIESE DATEI GIBT, UND WARUM NICHT stil.css.
   `/stil.css` liegt hinter der Token-Schranke: es steht in `Handler.SEITEN`
   und wird nur nach `_auth` ausgeliefert. Die vier Seiten `login`,
   `registrieren`, `passwort-vergessen` und `passwort-neu` tragen KEIN
   `__TOKEN__` - sie werden bewusst oeffentlich ausgeliefert, weil man sie
   erreichen muss, BEVOR man angemeldet ist. Genau deshalb trug jede von ihnen
   bis heute eine EIGENE ABSCHRIFT der Farbtabelle im `<style>`-Block.
   Vier Abschriften derselben Sache laufen auseinander, und sie waren es
   bereits: `--gruen` stand auf dreien als `#4d7c3a` statt der Markenfarbe
   `#5b8c3a` (ΔE76 = 9,59 hell / 7,10 dunkel, Kayas Befund B4). Es ist der
   Haken auf der Erfolgskarte - die erste Farbe, die ein neuer Mensch als
   „geschafft" liest, und sie war nicht unsere.

   ⛔ DIESE DATEI IST UND BLEIBT TOKEN-FREI. Kein `__TOKEN__`, kein `__V__`
   im Dateiinhalt - der Cache-Buster haengt am `<link>` der Seite, nicht hier.
   `Handler._datei` ersetzt Platzhalter ohnehin nur in `.html`.

   ⛔ SIE MUSS UNTER `/oeffentlich.css` OHNE ANMELDUNG ERREICHBAR SEIN.
   Fehlt die Freigabe in `app.py`, antwortet der Server mit einer 302 auf
   `/login`, und der Browser liest eine HTML-Seite als CSS: die vier Seiten
   stehen dann voellig ungestylt da - schwarz auf weiss, ohne Karte, ohne
   Knopffarbe - und das sieht NICHT nach einem Routing-Fehler aus. Genau so
   waren `nav.js`/`ui.js`/`ziel.js` bis 2.2.10 kaputt.
   `tests/test_oeffentlich_css.py` holt die Datei deshalb ueber HTTP und
   prueft ihren Inhaltstyp - nicht ihre Existenz auf der Platte.

   ⚠️ Die rote Linie „stdlib/Vanilla, kein Build" ist nicht beruehrt: das hier
   ist eine statische Datei, keine Werkzeugkette.
   ========================================================================== */

/* --------------------------------------------------------------- Farbtoken
   ⛔ DREI Bloecke, und die Reihenfolge ist die Zusicherung:
     1. `:root`                                   - hell, die Vorgabe
     2. `:root[data-theme=dark]`                  - ausdrueckliche Wahl „Dunkel"
     3. `@media (prefers-color-scheme:dark)` mit `:root:not([data-theme=light])`
                                                  - Systemwunsch, aber NUR wenn
                                                    niemand „Hell" gewaehlt hat
   Damit stimmen alle VIER Zustaende (System hell/dunkel x Wahl hell/dunkel/keine).
   `test_akzent_kontrast.py::test_die_tokenfreien_seiten_tragen_den_tiefen_akzent_selbst`
   misst genau diese vier - ueber eine gerenderte Farbe, nicht ueber
   `getPropertyValue` (ein fehlendes Token gibt dort einen leeren String, und
   „leer != erwartet" faellt zwar, sagt aber nichts darueber, was gemalt wird).

   ⛔ 3.0.96 - `--leise` UND `--akzent-text` stehen hier mit den KORRIGIERTEN
   Werten, und das ist der Grund, warum dieses Paket im selben Sprint liegt wie
   der Kontrast: haette man die 15 Masken hinter der Anmeldung behoben und
   diese vier Seiten mit ihren Abschriften stehen lassen, saehe der zu helle
   Ton genau dort weiter, wo ein neuer Mensch als ERSTES hinschaut.
   Die Begruendung der Werte steht bei `web/stil.css`; hier stehen sie, weil
   diese Seiten `stil.css` nicht laden koennen. Ein Waechter haelt beide
   Dateien aneinander (`test_kontrast_eine_regel.py`), damit die Abschrift
   nicht zum zweiten Mal auseinanderlaeuft. */
:root{
  --grund:#f1f2e4;--karte:#ffffff;--rand:#dcdfca;--text:#2b2d1c;
  --leise:#656850;--akzent:#7f8446;--akzent-text:#656938;
  /* ⛔ 3.35/3.37: dieselbe Angleichung an `brand/colors.css` wie in
     stil.css - #5E6232 statt der erfundenen #6f733d. Diese Datei traegt
     ihre eigene Kopie der Farben, weil die Seiten VOR der Anmeldung
     ohne Token ausgeliefert werden; der Waechter
     `test_die_tokenfreien_seiten_tragen_den_tiefen_akzent_selbst` hat
     genau darauf hingewiesen, als nur stil.css geaendert war. */
  --akzent-tief:#5E6232;--auf-akzent:#ffffff;--rot:#c0492f;--rot-text:#a53f28;
  /* 3.0.102/B4: die Markenfarbe, nicht die abgeschriebene. */
  --gruen:#5b8c3a;
  --radius:14px;
  /* ⚠️ `--ziel-min` steht hier, weil diese Seiten `stil.css` nicht laden. Eine
     geerbte Variable, die es gar nicht gibt, faellt lautlos auf „keine
     min-height" zurueck - genau so kam Befund 6 aus 2.6.6e zustande. */
  --ziel-min:48px}
:root[data-theme=dark]{
  --grund:#181a12;--karte:#22251a;--rand:#373b29;--text:#eceedd;
  --leise:#a7ab8e;--akzent:#a3a874;--akzent-text:#a3a874;
  --akzent-tief:#a3a874;--auf-akzent:#1b1d15;--rot:#d6674e;--rot-text:#f6765a;
  --gruen:#7fb35a}
@media (prefers-color-scheme:dark){
  :root:not([data-theme=light]){
    --grund:#181a12;--karte:#22251a;--rand:#373b29;--text:#eceedd;
    --leise:#a7ab8e;--akzent:#a3a874;--akzent-text:#a3a874;
    --akzent-tief:#a3a874;--auf-akzent:#1b1d15;--rot:#d6674e;--rot-text:#f6765a;
    --gruen:#7fb35a}}

/* ------------------------------------------------------------------ Geruest */
*{box-sizing:border-box}
body{margin:0;min-height:100vh;display:flex;align-items:center;
  justify-content:center;background:var(--grund);color:var(--text);
  font:16px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
  padding:1.5rem;-webkit-text-size-adjust:100%}
.karte{background:var(--karte);border:1px solid var(--rand);
  border-radius:var(--radius);padding:1.6rem 1.4rem;width:100%;max-width:22rem}
h1{font-size:1.15rem;margin:0 0 1rem;text-align:center}
p.intro{font-size:.9rem;color:var(--leise);margin:0 0 .4rem}

/* ---------------------------------------------------------------- Wortmarke
   ⛔ 3.0.102/B3 - ZWEI Dateien, eine je Helligkeit. Bis hierher banden alle
   vier Seiten NUR `wordmark.svg` ein, und die traegt dunkle Tinte (`#5E6232`).
   Gemessen gegen die dunklen Gruende:
       #5E6232 auf --karte  #22251a  =  2,426 : 1     (Kayas Zahl)
       #5E6232 auf --grund  #181a12  =  2,735 : 1     (Robins Zahl)
       #ffffff auf --karte  #22251a  = 15,584 : 1
       #ffffff auf --grund  #181a12  = 17,568 : 1
   ⚠️ Die beiden gemeldeten Zahlen unterscheiden sich NICHT durch die Methode
   (gerenderte Pixel gegen deklarierte Fuellfarbe), sondern durch die FLAECHE:
   2,426 ist gegen die Karte gerechnet, 2,735 gegen den Seitengrund. Beide
   stimmen, beide liegen unter 3 : 1 - der Schriftzug war im Dunkelmodus
   praktisch nicht da.
   ⭐ Und das Bauteil dafuer war laengst da: `brand/wordmark-light.svg` liegt
   im Repository und wird in `web/onboarding.html` mit `class="dunkel"` benutzt.
   Es wurde auf den Eingangsseiten nur nie angewandt. Die vier Umschaltregeln
   sind woertlich die aus `stil.css` - nur `display:inline` statt `block`, weil
   `.marke` hier ueber `text-align:center` zentriert.
   ⚠️ Warum kein Bestandswaechter das gefunden hat: `tests/kontrastmessung.py`
   misst TEXT im DOM. Ein Logo ist ein Bild und liegt ausserhalb seiner
   Reichweite. `test_wortmarke_dunkel.py` schliesst genau diese Luecke. */
.marke{text-align:center;margin-bottom:1.2rem}
.marke img{height:2.1rem;max-width:100%}
.marke .dunkel{display:none}
@media (prefers-color-scheme:dark){
  :root:not([data-theme=light]) .marke .hell{display:none}
  :root:not([data-theme=light]) .marke .dunkel{display:inline}}
:root[data-theme=light] .marke .hell{display:inline}
:root[data-theme=light] .marke .dunkel{display:none}
:root[data-theme=dark] .marke .hell{display:none}
:root[data-theme=dark] .marke .dunkel{display:inline}

/* ------------------------------------------------------------------ Felder */
label{display:block;font-size:.8rem;color:var(--leise);margin:.9rem 0 .25rem}
/* Gemessen ohne min-height: 42,4 px - das Projekt misst sich an 48 (2.6.6e/6).
   Der erste Bildschirm der App waere damit der einzige, der das Mass verfehlt.
   ⛔ `:not([type=checkbox])` statt einer spaeteren Ausnahme fuer `.merken`:
   ein `min-height` von 48 px schluege dort die 1,15rem des Kaestchens (es steht
   in derselben Kaskade weiter unten, aber `height` verliert gegen
   `min-height`) - und „ein globaler Fix plus eine lokale Ausnahme ergibt keinen
   Fix" ist die Lehre aus 2.6.6. Das Fingerziel des Haekchens ist ohnehin das
   ganze `<label class="merken">`, nicht das Kaestchen.
   ⚠️ Bis 3.0.102 stand diese Ausnahme NUR in `login.html`; die drei anderen
   Seiten trugen das schlichte `input{...}`. Dass daraus kein Fehler wurde, lag
   allein daran, dass sie kein Kaestchen haben - also an ihrem Inhalt, nicht an
   ihrer Regel. In der geteilten Datei gilt jetzt ueberall die tragfaehige. */
input:not([type=checkbox]){width:100%;padding:.7rem;font-size:1rem;
  background:var(--grund);color:var(--text);border:1px solid var(--rand);
  border-radius:9px;font-family:inherit;min-height:var(--ziel-min)}
input:focus{outline:2px solid var(--akzent);outline-offset:1px}

/* ------------------------------------------------------------------ Knoepfe
   ⛔ `border-color` traegt denselben Ton wie die Flaeche. Wer nur `background`
   umstellt, bekommt einen 1 px hellen Saum aus dem alten Akzent um jeden Knopf,
   und KEIN Kontrastwaechter meldet das: ein Rand traegt keinen Text (3.0.51). */
button{font:inherit;width:100%;margin-top:1.2rem;padding:.8rem 1rem;
  min-height:2.9rem;border-radius:10px;border:1px solid var(--akzent-tief);
  background:var(--akzent-tief);color:var(--auf-akzent);font-weight:600;
  cursor:pointer}
/* ⛔ E-175 - HIER STAND `button:disabled{opacity:.6}`, UND DAS WAR DER
   KONTRASTFEHLER AUF `/login`. Gemessen (15.09.2026, 8 von 8 Laeufen gleich,
   390 px und 1440 px ohne Unterschied):

       hell    weisse Schrift auf #9ea184   2,67 : 1     (bedienbar: 6,42)
       dunkel  #1e2017      auf #6f7450     3,35 : 1     (bedienbar: 6,82)

   Beide unter AA, und beide aus DERSELBEN Zeile. ⛔ Der dunkle Wert stand in
   keinem Befund: wer nur den hellen repariert, liefert eine halbe Korrektur
   und hat trotzdem einen gruenen Waechter.

   ⛔ WARUM `opacity` HIER NICHT NUR ZU HELL, SONDERN DIE FALSCHE SACHE IST:
   eine Deckkraft am Knopf dimmt die GRUPPE - Flaeche UND Schrift werden erst
   voll gezeichnet und dann gemeinsam ueber die Karte gelegt. Die weisse
   Schrift bleibt dabei weiss, die Flaeche wird hell: der Abstand faellt von
   beiden Seiten zusammen. `web/stil.css` haelt denselben Befund seit 3.0.9a
   schriftlich fest (*mit `opacity:.6` fiel ein `.haupt`-Knopf auf 1,61 : 1*)
   und hat ihn dort mit einem eigenen FARBPAAR behoben. Diese Datei ist auf
   die Korrektur nie nachgezogen worden.

   ⛔ ES IST WOERTLICH DIE REGEL AUS `stil.css` (Dateiende), nicht eine
   zweite Erfindung. Genau darum geht es in dieser Datei seit 3.0.102: vier
   Abschriften derselben Sache laufen auseinander, und hier ist es zum zweiten
   Mal passiert - die 15 Masken hinter der Anmeldung bekamen 3.0.9a, die vier
   Masken davor nicht. Gemessen ergibt das Paar `--leise` auf `--karte`:
       hell    #656850 auf #ffffff   5,755 : 1
       dunkel  #a7ab8e auf #22251a   6,574 : 1
   Der gestrichelte Rand traegt dieselben Zahlen und braucht als Bauteil 3 : 1.

   ⚠️ WARUM SIE HIER STEHT UND NICHT AM DATEIENDE. In `stil.css` steht sie
   ganz unten, weil rund ein Dutzend SPAETERER Regeln (`.zeitfilter button`
   …) dieselbe Spezifitaet (0,1,1) haben und bei Gleichstand die Reihenfolge
   entscheidet. In dieser Datei gibt es solche Regeln nicht - die einzige
   spaetere Knopfregel ist `.zurueck` (0,1,0) und verliert ohnehin. Eine
   Begruendung aus einer anderen Datei mitzukopieren, ohne dass ihre Lage
   hier gilt, waere genau der Kommentar, vor dem CLAUDE.md warnt.
   ⛔ Verlassen muss man sich darauf trotzdem nicht: `test_login_gesperrter_knopf.py`
   misst den GERENDERTEN Knopf in beiden Helligkeiten. Verliert die Regel je
   ihre Kaskade, faellt er - ganz gleich, woran es lag.

   ⚠️ `-webkit-text-fill-color` ausdruecklich: iOS-Safari faerbt deaktivierte
   Bedienelemente sonst so blass, dass sie am Handy verschwinden. Uebernommen
   aus `stil.css`, aus demselben Grund.
   ⚠️ `cursor:not-allowed` statt `default` - auch das ist die Fassung aus
   `stil.css`. */
button:disabled{background:var(--karte);color:var(--leise);
  -webkit-text-fill-color:var(--leise);border-style:dashed;
  border-color:var(--leise);cursor:not-allowed;box-shadow:none}
/* Der Rand darf beim Ueberfahren NICHT auf Akzent springen - er liest sich
   sonst wie „anklickbar". (In dieser Datei gibt es heute kein `button:hover`;
   die Zeile steht als dieselbe Zusicherung wie in `stil.css`, damit ein
   spaeter ergaenztes Hover den gesperrten Knopf nicht mitnimmt.) */
button:disabled:hover{border-color:var(--leise)}

/* ------------------------------------------------------------------- Texte */
/* ⛔ 3.0.96 / Junos Befund 0.4: die Fehlerzeile stand in `--rot` und mass
   dunkel **4,385 : 1** - AA verfehlt, ausgerechnet in der Box, die seit
   3.0.104 den Abweisungssatz traegt.
   ⛔ **Rot liegt im RAND, die Aussage traegt das WORT in `--text`.** Das ist
   woertlich `.gfehlerzeile` aus 3.0.89/B2 - dieselbe Zahl, dieselbe Ursache,
   dieselbe Antwort, nur zwei Masken weiter. Damit ist die Kontrastfrage hier
   gar nicht mehr gestellt: `--text` traegt auf `--karte` nachgemessen
   **14,048 : 1** hell und **13,239 : 1** dunkel (dieselben Zahlen, die Vera
   an der Abweisungsbox gemessen hat); der Rand braucht als Bauteil nur 3 : 1.
   ⚠️ Hier stand als Punkt 1 *„Der Ton ist --rot-text"* - und zwei Zeilen
   darunter (richtig) das Gegenteil. Gebaut war immer das Zweite; Vera hat den
   Widerspruch gefunden (V-10). `--rot-text` steht in dieser Datei nur noch als
   WERT, in keiner Regel - das ist Absicht und kein Rest: ein Kommentar, der
   eine Farbe behauptet, die die Regel daneben nicht setzt, kostet den
   naechsten Leser eine Suche.
   ⚠️ Junos Begruendung geht ueber den Kontrast hinaus, und sie ist der Grund,
   warum hier nicht nur der Ton getauscht wurde: *Rot ist eine BEHAUPTUNG* -
   „du hast etwas falsch gemacht". Der Mensch, der die Freigabe-Abweisung
   liest, hat alles richtig gemacht; sein Passwort stimmt. Eine Farbe ohne
   Bedeutung ist dieselbe Klasse Fehler wie eine Farbangabe ohne Kontrastwert.
   ⛔ `:not(:empty)` fuer den Rand: die Box steht IMMER im DOM (mit
   `min-height`, damit die Karte beim Erscheinen einer Meldung nicht springt).
   Ohne die Bedingung staende dort dauerhaft ein herrenloser roter Strich -
   dieselbe Bauform wie `.warum:empty{display:none}` aus 2.6.6. */
.fehler{color:var(--text);font-size:.9rem;margin-top:.9rem;min-height:1.2em;
  text-align:center}
.fehler:not(:empty){text-align:left;border-left:3px solid var(--rot);
  padding-left:.55rem}
.hinweis{font-size:.82rem;color:var(--leise);margin-top:.3rem;text-align:center}
.feldhinweis{font-size:.78rem;color:var(--leise);margin:.3rem 0 0}
.wechsel{text-align:center;font-size:.9rem;margin-top:1.1rem;color:var(--leise)}
/* 3.0.96: `--akzent-text`, nicht `--akzent`. Der kraeftige Ton traegt als
   SCHRIFT auf Weiss nur 3,969 : 1 und auf dem Seitengrund 3,508 : 1. */
.wechsel a{color:var(--akzent-text);font-weight:600}

/* ------------------------------------------------- Erfolgskarte / Anmeldung */
.erfolg{text-align:center}
.erfolg .haken{font-size:2.4rem;color:var(--gruen);line-height:1}
.erfolg p{margin:.9rem 0}
/* ⛔ Veras V-5: die Adresse stand als `.feldhinweis` (.78rem = 12,48 px) und
   war damit die KLEINSTE Schrift der Karte - kleiner als das
   Kleingedruckte darunter (`.hinweis`, .82rem). Sie ist ab jetzt der
   Schluessel des Nutzers; Junos Entwurf nennt 14,4 px, und genau daran
   misst der Waechter. Dieselbe Bauform wie der Befund eine Zeile hoeher:
   *die Aussage der Seite stand im Kleingedruckten.* */
.fertigadresse{font-size:.9rem;color:var(--text);margin:.9rem 0}
.merken{display:flex;align-items:center;gap:.5rem;margin-top:1rem;font-size:.9rem;
  color:var(--text);cursor:pointer}
.merken input{width:1.15rem;height:1.15rem;margin:0;flex:0 0 auto;
  accent-color:var(--akzent)}
.vergessen{display:block;text-align:center;font-size:.85rem;margin-top:.9rem}
.vergessen a{color:var(--leise)}
/* Zweiter Schritt der Anmeldung (2.3.1) */
.codefeld{letter-spacing:.35em;text-align:center;font-size:1.35rem;
  font-variant-numeric:tabular-nums}
.erklaerung{font-size:.9rem;color:var(--leise);margin:.2rem 0 .6rem;
  text-align:center}
/* ⛔ HIER STAND `padding:0; min-height:0` - und damit Befund 6 aus 2.6.6e in
   neuer Verkleidung, acht Zeilen unter dem Kommentar, der woertlich davor
   warnt. Gemessen von Vera: **20,4 px** fuer „Zurück zur Anmeldung", 40,8 px
   fuer den Wiederherstellungs-Knopf - beide unter `--ziel-min: 48px`.
   *Ein globaler Fix plus eine lokale Ausnahme ergibt keinen Fix.*
   ⛔ Und warum es niemand gemerkt hat, ist der teurere Teil: `test_lesbarkeit`
   fragte, ob eine Seite `/stil.css` laedt. Diese vier laden seit 3.0.102
   `/oeffentlich.css` - der Waechter sah sie also weiterhin nicht, und seine
   Begruendung (*„die Pre-Auth-Seiten kennen --ziel-min gar nicht"*) war bis
   zu diesem Paket wahr und ist seither falsch. Eine Begruendung, die ihre
   Lage ueberlebt hat, haelt den Waechter von genau den Seiten fern, die ein
   neuer Mensch zuerst sieht. Der Waechter fragt jetzt, ob die Seite
   `--ziel-min` ueberhaupt KENNT - egal, aus welcher Datei.
   ⚠️ `flex` statt `block`, damit der Text in den 48 px mittig sitzt; die
   Unterstreichung liegt weiter nur unter den Woertern, nicht unter der
   ganzen Zeile. */
.zurueck{display:flex;align-items:center;justify-content:center;
  text-align:center;font-size:.85rem;margin-top:1.1rem;
  color:var(--leise);background:none;border:0;width:100%;cursor:pointer;
  text-decoration:underline;padding:.4rem;min-height:var(--ziel-min)}

/* --------------------------------------------------------------- [hidden]
   ⛔ MUSS GANZ UNTEN STEHEN UND MUSS `!important` TRAGEN. `[hidden]` ist eine
   Angabe des Browsers mit der Staerke eines Elementselektors und verliert
   gegen JEDEN Klassenselektor, der `display` setzt - hier gegen
   `.vergessen{display:block}`. `stil.css` traegt dieselbe Zeile aus demselben
   Grund am Dateiende; diese Seiten laden `stil.css` nicht und saehen sie nie.
   ⚠️ Es ist kein Vorratsbau: `web/login.html` blendet mit `hidden` die beiden
   falschen Auswege der Freigabe-Abweisung aus (3.0.104). Ohne diese Zeile
   blieben sie sichtbar - und der Fehler saehe aus wie ein vergessener
   JavaScript-Aufruf, nicht wie eine verlorene Kaskade. */
[hidden]{display:none !important}
