/* ==========================================================================
   modern.css - Die Modernisierungsschicht (20.09.2026)
   ==========================================================================

   WARUM EINE EIGENE DATEI

   src/index.css ist 14.800 Zeilen gewachsen und traegt die Geschichte von
   zwei Jahren. Wer dort modernisiert, aendert Regeln, auf die an anderer
   Stelle jemand gezaehlt hat. Diese Datei wird als LETZTE geladen und
   aendert deshalb, ohne zu loeschen: gleiche Spezifitaet gewinnt die
   spaetere Regel. Was hier steht, laesst sich in einer einzigen Zeile in
   index.html wieder abschalten - das ist der Sicherheitsgurt.

   WAS HIER NICHT STEHT

   Nichts mit !important. Die Utility-Dateien (utilities-neu/runde6/runde7)
   tragen alle !important, weil sie gegen Inline-Stile bestehen muessen.
   Sie gewinnen also weiter gegen alles hier - und das ist richtig so: wer
   eine Utility-Klasse an ein Element schreibt, meint es ernst.

   REIHENFOLGE

   1. Tokens, die es noch nicht gab (Ebenen, Ring, Bewegung)
   2. Fluide Schrift
   3. Tippen statt Zeigen (Druckgefuehl, Hover nur wo es Hover gibt)
   4. Fokus, der zu sehen ist
   5. Eingabefelder, die iOS nicht hineinzoomen laesst
   6. Hoehen, die die Adressleiste ueberleben (dvh)
   7. Ruhiges Scrollen
   8. Karten: Rand ODER Schatten
   9. Das Tablet bekommt Spalten
   10. Warten, ohne zu zappeln (Skelette)
   11. Leerzustaende, die weiterhelfen
   ========================================================================== */


/* --------------------------------------------------------------------------
   1. TOKENS, DIE ES NOCH NICHT GAB

   Die Tokens in index.css beschreiben Farbe, Mass und Ebene. Was fehlte:
   ein eigener Ton fuer den Fokusring (bisher lieh er sich --primary-soft,
   das mit 95% Helligkeit auf weissem Grund kaum zu sehen ist), eine zweite
   und dritte Flaechenstufe (bisher gab es nur --surface und --bg-2), und
   Namen fuer die Bewegungsdauern, die ueber die zwei bestehenden
   hinausgehen.
   -------------------------------------------------------------------------- */
:root {
  /* Der Fokusring. Kraeftig genug, um auf --surface UND auf --bg-2 zu
     stehen; die Deckkraft laesst die Farbe des Feldes darunter durch,
     damit der Ring nicht wie ein zweiter Rahmen wirkt. */
  --ring: hsl(328, 70%, 52%);
  --ring-schatten: 0 0 0 3px hsl(328, 70%, 52%, 0.32);
  --ring-breite: 2px;

  /* Flaechenstufen. --surface ist das Blatt, --surface-2 die Vertiefung
     darin (Tabellenkopf, eingebettete Liste), --surface-3 die zweite
     Vertiefung (Codefeld, Eingabe im gefuellten Zustand). */
  --surface-2: hsl(36, 30%, 97%);
  --surface-3: hsl(36, 26%, 94%);

  /* Bewegung. --transition-fast/normal gab es; hier kommen die Dauern
     dazu, die eine App von einer Webseite unterscheiden: das kurze
     Einrasten beim Tippen und die laengere Bahn eines Sheets. */
  --dauer-tipp: 90ms;
  --dauer-kurz: 160ms;
  --dauer-mittel: 260ms;
  --bahn-standard: cubic-bezier(0.2, 0, 0, 1);
  --bahn-heraus: cubic-bezier(0.3, 0, 0.8, 0.15);
  --bahn-hinein: cubic-bezier(0.05, 0.7, 0.1, 1);

  /* Wie weit ein Element beim Tippen einsinkt. Als Token, damit es ueberall
     gleich viel ist - 0.97 ist sichtbar, ohne dass der Text flimmert. */
  --tipp-skala: 0.97;

  /* Die Haltepunkte. Im Bestand stehen neunzehn verschiedene Breiten
     (400, 420, 480, 560, 599, 600, 620, 640, 700, 760, 768, 820, 900,
     992, 1024, 1100 ...) - gewachsen, nicht entschieden. CSS kann in
     @media keine Variablen lesen, deshalb sind das hier Merkzettel und
     keine Werte: NEUE Regeln benutzen 600 / 900 / 1200, und nur diese.
     --hp-klein: 600px   Telefon quer, kleines Tablet hoch
     --hp-mittel: 900px  Tablet hoch
     --hp-gross: 1200px  Tablet quer, Rechner
     Die alten Haltepunkte bleiben, wo sie stehen. Sie umzuhaengen waere
     ein Umbau mit Risiko und ohne sichtbaren Gewinn. */

  /* Randabstand zum Bildschirmrand, einheitlich. Auf dem Telefon 16px,
     ab dem Tablet mehr - als Token, damit Karten, Kopfzeile und
     Hauptbereich denselben Rand haben. */
  --rand: 16px;
}

@media (min-width: 600px) {
  :root { --rand: 24px; }
}

@media (min-width: 900px) {
  :root { --rand: 32px; }
}


/* --------------------------------------------------------------------------
   2. FLUIDE SCHRIFT

   Bisher hatte jede Schriftstufe genau eine Groesse - auf dem 390px-iPhone
   dieselbe wie auf dem 1280px-Bildschirm. Ab hier wachsen die drei grossen
   Stufen mit der Breite mit.

   Die UNTERE Grenze jeder clamp() ist der bisherige Wert. Auf dem Telefon
   aendert sich damit nichts; gewonnen wird auf Tablet und Rechner, wo eine
   Ueberschrift bisher verloren wirkte. Die kleinen Stufen (xs, sm, md)
   bleiben fest - Nebentext soll nicht mitwachsen, sonst verliert die Seite
   ihre Ordnung.

   Gerechnet mit 16px Grundschrift:
     --fs-xl   390px: 20.0px   768px: 21.5px   1280px: 23.4px
     --fs-2xl  390px: 24.0px   768px: 26.3px   1280px: 29.1px
     --fs-3xl  390px: 29.6px   768px: 33.1px   1280px: 37.4px
   -------------------------------------------------------------------------- */
:root {
  --fs-xl:  clamp(1.25rem, 1.16rem + 0.24vw, 1.46rem);
  --fs-2xl: clamp(1.50rem, 1.36rem + 0.36vw, 1.82rem);
  --fs-3xl: clamp(1.85rem, 1.65rem + 0.55vw, 2.34rem);

  /* Zeilenhoehen als Tokens. Der Bestand setzt sie frei (1.2, 1.3, 1.4,
     1.5, 1.55, 1.6 ...). Neue Regeln nehmen diese drei. */
  --zh-eng:    1.2;   /* grosse Ueberschriften */
  --zh-normal: 1.45;  /* Bedienelemente, kurze Texte */
  --zh-lesen:  1.65;  /* Fliesstext, den jemand wirklich liest */

  /* Zeilenlaenge fuer Fliesstext. Ueber 75 Zeichen findet das Auge den
     Zeilenanfang nicht mehr zuverlaessig - auf dem Tablet quer laufen die
     Mitteilungen der Eltern heute ueber 110 Zeichen. */
  --lesebreite: 68ch;
}

/* Grosse Ueberschriften: enger Zeilenabstand und ein ausgeglichener
   Umbruch. text-wrap:balance verteilt die Woerter auf die Zeilen, statt
   ein einzelnes Wort allein in die letzte Zeile zu schicken - bei
   deutschen Komposita ("Betreuungsvereinbarung") der Unterschied zwischen
   zwei und drei Zeilen. Begrenzt auf kurze Ueberschriften, weil der
   Browser balance ab etwa sechs Zeilen ohnehin aufgibt. */
h1, h2, h3,
.card-title,
.seiten-titel {
  line-height: var(--zh-eng);
  text-wrap: balance;
}

/* Fliesstext bekommt pretty: das verhindert Schusterjungen (ein einzelnes
   Wort in der letzten Zeile). Kostet nichts, wo der Browser es nicht kann. */
p, li, dd, .hinweis, .leer-text {
  text-wrap: pretty;
}

/* Zahlen in Tabellen und Abrechnungen stehen untereinander, wenn die
   Ziffern gleich breit sind. Ohne das wandert das Komma von Zeile zu
   Zeile - in der Stundenabrechnung und im Steuerbericht gut sichtbar. */
table td, table th,
.betrag, .stunden, .zahl,
[class*="-betrag"], [class*="-summe"] {
  font-variant-numeric: tabular-nums;
}


/* --------------------------------------------------------------------------
   3. TIPPEN STATT ZEIGEN

   Gemessen am 20.09.2026: In allen fuenf Stylesheets steht KEINE einzige
   :active-Regel, aber 92 :hover-Regeln. Fuer ein Geraet ohne Maus heisst
   das zweierlei:

   - Beim Antippen passiert nichts. Der Finger drueckt, und bis die Antwort
     vom Server kommt, ist nicht zu sehen, ob der Tipp angekommen ist. Das
     ist der groesste einzelne Unterschied zwischen "Webseite" und "App".
   - Nach dem Antippen bleibt der :hover-Zustand haengen, bis irgendwo
     anders hingetippt wird. Ein Knopf, den man gerade gedrueckt hat, sieht
     danach dauerhaft anders aus als seine Nachbarn.

   Beides wird hier behoben, ohne eine bestehende Regel zu loeschen: Die
   :hover-Regeln bleiben, wo sie sind - sie werden nur fuer Geraete ohne
   Zeiger zurueckgenommen.
   -------------------------------------------------------------------------- */

/* Druckgefuehl. Sehr kurz (90ms) und sehr klein (3%), weil es sich anfuehlen
   soll wie Nachgeben, nicht wie eine Animation. */
.btn:active,
button:active,
.app-nav-item:active,
.app-bottombar-item:active,
.app-menu-item:active,
.chip:active,
.segment:active,
[role="button"]:active {
  transform: scale(var(--tipp-skala));
  transition-duration: var(--dauer-tipp);
}

/* Karten, die als Ganzes anklickbar sind, sinken weniger tief ein - bei
   einer grossen Flaeche wirken 3% wie ein Sprung. */
a.card:active,
.card[role="button"]:active,
.card-klickbar:active,
.liste-zeile-klickbar:active {
  transform: scale(0.995);
  transition-duration: var(--dauer-tipp);
}

/* Damit das Einsinken ueberhaupt eine Bahn hat: die Grundelemente bekommen
   einen Uebergang fuer transform. Bewusst NUR transform und nicht "all" -
   "all" wuerde auch Farbwechsel und Groessenaenderungen animieren, die
   sofort da sein sollen. */
.btn,
.app-nav-item,
.app-bottombar-item,
.app-menu-item,
.chip,
.segment {
  transition: transform var(--dauer-tipp) var(--bahn-standard),
              background-color var(--dauer-kurz) var(--bahn-standard),
              border-color var(--dauer-kurz) var(--bahn-standard),
              color var(--dauer-kurz) var(--bahn-standard);
}

/* Hover nur, wo es einen Zeiger gibt, der schweben kann.

   Auf einem Telefon meldet der Browser hover:none. Dort setzt diese Regel
   die Hover-Wirkung zurueck - der Knopf sieht nach dem Tippen wieder aus
   wie vorher. Die Liste deckt die Klassen ab, deren :hover im Bestand eine
   sichtbare Flaechen- oder Farbaenderung macht. */
@media (hover: none) {
  .btn:hover,
  .app-nav-item:hover,
  .app-bottombar-item:hover,
  .app-menu-item:hover,
  .card:hover,
  .chip:hover,
  .segment:hover,
  tbody tr:hover {
    /* Alles auf den Ruhezustand zurueck. Farbe und Rand erben damit wieder
       von der Grundregel, der Schatten vom Ruhewert. */
    background-color: inherit;
    border-color: inherit;
    box-shadow: inherit;
    transform: none;
  }
}


/* --------------------------------------------------------------------------
   4. FOKUS, DER ZU SEHEN IST

   Der Bestand setzt an drei Stellen outline:none. Zwei davon haben einen
   Ersatz - aber in --primary-soft, einem Ton mit 95% Helligkeit: auf
   weissem Grund ist dieser Ring kaum zu erkennen.

   :focus-visible statt :focus ist der zweite Teil: Der Ring erscheint bei
   Tastatur-Bedienung, aber nicht, wenn jemand mit dem Finger tippt. Vorher
   blieb nach jedem Antippen ein Rahmen stehen.
   -------------------------------------------------------------------------- */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: var(--ring-breite) solid var(--ring);
  outline-offset: 2px;
  /* Der zweite, weiche Ring hebt den harten von jedem Untergrund ab -
     auch von einer farbigen Flaeche, auf der eine reine Outline
     verschwinden koennte. */
  box-shadow: var(--ring-schatten);
  /* Der Ring darf nicht unter einem Nachbarn liegen. */
  position: relative;
  z-index: 1;
}

/* Eingabefelder: der Ring ersetzt den blassen Bestandsring. Hier bewusst
   :focus und nicht :focus-visible - bei einem Textfeld will man auch beim
   Antippen sehen, in welches Feld man gerade schreibt. */
.form-control:focus,
input:focus,
select:focus,
textarea:focus {
  outline: none;
  border-color: var(--ring);
  box-shadow: var(--ring-schatten);
}

/* Fuer Knoepfe, die auf farbigem Grund stehen: ein heller Ring, weil der
   pinke auf pinkem Grund nicht zu sehen waere. */
.btn-primary:focus-visible,
.btn-danger:focus-visible,
.btn-accent:focus-visible {
  outline-color: var(--surface);
  box-shadow: 0 0 0 4px hsl(328, 70%, 52%, 0.45);
}

/* Zum Inhalt springen. Der Bestand hat keinen solchen Link; das Markup
   dafuer setzt shell.js. Die Regel steht hier schon bereit. */
.zum-inhalt {
  position: absolute;
  left: var(--s-3);
  top: var(--s-3);
  z-index: calc(var(--z-header) + 1);
  padding: 10px var(--s-4);
  border-radius: var(--radius-sm);
  background: var(--primary);
  color: #fff;
  font-weight: 600;
  text-decoration: none;
  /* Versteckt, bis jemand mit der Tastatur darauf landet. Nicht ueber
     display:none - das nimmt ihn aus der Tabulatorreihenfolge. */
  transform: translateY(-200%);
  transition: transform var(--dauer-kurz) var(--bahn-hinein);
}

.zum-inhalt:focus {
  transform: translateY(0);
}


/* --------------------------------------------------------------------------
   5. EINGABEFELDER, DIE iOS NICHT HINEINZOOMEN LAESST

   .form-control traegt font-size: var(--fs-md) = 0.95rem = 15.2px. Mobile
   Safari zoomt beim Antippen in jedes Feld unter 16px hinein und kehrt
   danach NICHT zurueck - die Seite bleibt vergroessert, und der Rest der
   Oberflaeche steht ausserhalb des Bildschirms.

   Genau dagegen stand bisher user-scalable=no im Viewport-Meta. Das ist die
   falsche Antwort auf das richtige Problem: Es unterdrueckt zwar den
   ungewollten Zoom, sperrt aber auch den GEWOLLTEN aus - jemand mit
   schwachen Augen kann die Seite nicht mehr vergroessern (WCAG 1.4.4).

   Hier die richtige Antwort: 16px im Feld. Damit darf das Zoom-Verbot aus
   index.html verschwinden.

   Nur auf Zeigern ohne Hover (= Telefon/Tablet). Am Rechner bleibt die
   Feldschrift wie bisher, damit dichte Formulare nicht auseinanderlaufen.
   -------------------------------------------------------------------------- */
@media (hover: none) {
  .form-control,
  input:not([type="checkbox"]):not([type="radio"]),
  select,
  textarea {
    font-size: 16px;
  }
}

/* Kaestchen und Schalter in der Handlungsfarbe statt im Browser-Blau.
   Der Bestand hat accent-color an sechs Stellen - hier fuer alle. */
input[type="checkbox"],
input[type="radio"],
input[type="range"],
progress {
  accent-color: var(--primary);
}

/* Ein Textfeld, das mitwaechst, statt eine Bildlaufleiste zu bekommen.
   field-sizing ist neu; wo der Browser es nicht kennt, bleibt alles wie
   bisher. Begrenzt, damit ein langer Text das Formular nicht sprengt. */
textarea.waechst-mit {
  field-sizing: content;
  min-height: 4.5em;
  max-height: 18em;
}

/* Tippflaeche auch fuer Kaestchen: 20px sind zu klein zum Treffen. Der
   Bestand sichert 44px fuer Knoepfe, aber nicht fuer Kaestchen. */
@media (pointer: coarse) {
  input[type="checkbox"],
  input[type="radio"] {
    width: 22px;
    height: 22px;
  }

  /* Die Beschriftung wird Teil der Tippflaeche. */
  label:has(> input[type="checkbox"]),
  label:has(> input[type="radio"]) {
    display: flex;
    align-items: center;
    gap: var(--s-2);
    min-height: 44px;
  }
}


/* --------------------------------------------------------------------------
   6. HOEHEN, DIE DIE ADRESSLEISTE UEBERLEBEN

   100vh ist auf dem Telefon die Hoehe des Fensters OHNE die Adressleiste -
   also mehr, als tatsaechlich zu sehen ist. Ein Element mit min-height:100vh
   ragt deshalb immer ein Stueck unter den Bildschirmrand.

   100dvh ist die Hoehe, die gerade wirklich da ist, und aendert sich mit,
   wenn die Adressleiste beim Scrollen einfaehrt. Der Bestand hat das an
   einer einzigen Stelle (.login-wrapper), an acht weiteren nicht.

   Die Regeln hier greifen dieselben Stellen auf. @supports haelt alte
   Browser aussen vor, die dvh nicht kennen - dort bleibt 100vh stehen.
   -------------------------------------------------------------------------- */
@supports (height: 100dvh) {
  .app-shell {
    min-height: 100dvh;
  }

  .app-sidebar {
    height: calc(100dvh - var(--header-h));
  }

  .login-wrapper {
    min-height: calc(100dvh - 120px);
  }

  .app-sheet {
    max-height: 85dvh;
  }
}

/* Die untere Leiste sitzt auf dem Rand des Bildschirms. Auf Geraeten mit
   Gestenbalken (jedes iPhone ohne Knopf, viele Android) liegt der Balken
   darueber - env(safe-area-inset-bottom) haelt den Platz frei. Der Bestand
   macht das fuer .app-bottombar richtig. Was fehlte: dasselbe fuer alles
   andere, was unten klebt. */
.app-sheet,
.toast-bereich,
.meldung-bereich,
.sticky-fuss,
.formular-fuss {
  padding-bottom: max(var(--s-3), env(safe-area-inset-bottom));
}

/* Und an den Seiten: im Querformat schneidet die Aussparung der Kamera
   sonst den Text an. */
@media (orientation: landscape) {
  .app-main,
  .app-header {
    padding-left: max(var(--rand), env(safe-area-inset-left));
    padding-right: max(var(--rand), env(safe-area-inset-right));
  }
}


/* --------------------------------------------------------------------------
   7. RUHIGES SCROLLEN

   Drei kleine Regeln, die zusammen den Unterschied machen zwischen einer
   Seite, die man scrollt, und einer App, die sich bewegen laesst.
   -------------------------------------------------------------------------- */

/* Ein Sprungziel landet nicht unter der klebenden Kopfzeile. Der Bestand
   hat scroll-padding-bottom fuer die untere Leiste - oben fehlte es. */
html {
  scroll-padding-top: calc(var(--header-h) + var(--s-3));
}

/* Sanft scrollen, aber nur fuer den, der das moechte. */
@media (prefers-reduced-motion: no-preference) {
  html {
    scroll-behavior: smooth;
  }
}

/* Wer in einem Sheet, einem Dialog oder einer eigenen Liste am Ende
   angekommen ist, scrollt sonst die Seite DAHINTER weiter - und findet
   beim Schliessen einen ganz anderen Ausschnitt vor. */
.app-sheet,
.modal-body,
.modal-content,
.dialog-inhalt,
.chat-verlauf,
.scroll-bereich {
  overscroll-behavior: contain;
}

/* Waagerechte Streifen (Wochentage, Filterreihen, Bildergalerien) rasten
   ein, statt zwischen zwei Eintraegen stehenzubleiben. */
.streifen,
.chip-reihe,
.tag-streifen,
.bild-streifen {
  display: flex;
  gap: var(--s-2);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  /* Die Bildlaufleiste verschwindet, der Streifen bleibt scrollbar. */
  scrollbar-width: none;
  /* Damit der erste und letzte Eintrag nicht am Rand kleben. */
  padding-inline: var(--rand);
  margin-inline: calc(var(--rand) * -1);
}

.streifen::-webkit-scrollbar,
.chip-reihe::-webkit-scrollbar,
.tag-streifen::-webkit-scrollbar,
.bild-streifen::-webkit-scrollbar {
  display: none;
}

.streifen > *,
.chip-reihe > *,
.tag-streifen > *,
.bild-streifen > * {
  scroll-snap-align: start;
  flex: 0 0 auto;
}

/* Lange Listen, von denen nur ein Teil zu sehen ist, muessen nicht
   vollstaendig berechnet werden. contain-intrinsic-size gibt dem Browser
   eine Schaetzung, damit die Bildlaufleiste trotzdem stimmt. */
.liste-lang > * {
  content-visibility: auto;
  contain-intrinsic-size: auto 88px;
}


/* --------------------------------------------------------------------------
   8. KARTEN: RAND ODER SCHATTEN

   Eine Karte mit Rand UND Schatten war die Konvention von 2015 - damals
   brauchte es beides, weil Bildschirme kontrastaermer waren. Heute liest
   sich das als doppelte Begrenzung: der Schatten sagt "ich liege oben
   drauf", der Rand sagt "ich bin ausgeschnitten".

   Entschieden wird pro Ebene:
   - Karten auf dem Seitengrund: weicher Schatten, kein Rand.
   - Karten IN einer Karte: Rand, kein Schatten (sonst schwebt etwas in
     etwas, das selbst schwebt).
   -------------------------------------------------------------------------- */
.card {
  border: 0;
  box-shadow: var(--shadow-1);
  /* Der Uebergang gilt dem Schatten, nicht der Position: Karten springen
     beim Zeigen nicht hoch (so steht es in DESIGN.md), aber der Schatten
     darf antworten. */
  transition: box-shadow var(--dauer-kurz) var(--bahn-standard);
}

/* Karte in Karte: flach und abgegrenzt. */
.card .card,
.card .karte,
.karte .card {
  box-shadow: none;
  border: 1px solid var(--line);
}

/* Anklickbare Karten duerfen auf Zeigegeraeten antworten. */
@media (hover: hover) {
  a.card:hover,
  .card-klickbar:hover,
  .card[role="button"]:hover {
    box-shadow: var(--shadow-2);
  }
}

/* Karten, die einen Zustand melden (Warnung, Fehler, Erfolg), behalten
   ihren farbigen Rand - dort ist er Information, nicht Dekoration. Diese
   Regel stellt ihn wieder her, falls die Regel oben ihn genommen hat. */
.card.ist-warnung,
.card.ist-fehler,
.card.ist-erfolg,
.card.ist-hinweis,
.card[data-ton] {
  border-left: 4px solid currentColor;
}


/* --------------------------------------------------------------------------
   9. DAS TABLET BEKOMMT SPALTEN

   Gemessen am 20.09.2026 auf 768x1024: Die Elternansicht ist dieselbe wie
   auf dem iPhone, nur breiter - vierzehn Karten untereinander, jede ueber
   die volle Breite, Mitteilungstexte ueber 110 Zeichen pro Zeile. Der
   Bildschirm ist doppelt so breit und zeigt dieselbe Menge.

   Der Umbau geschieht ueber CONTAINER QUERIES, nicht ueber Media Queries.
   Der Unterschied ist entscheidend: Eine Media Query fragt, wie breit der
   BILDSCHIRM ist. Eine Container Query fragt, wie breit der PLATZ ist, den
   diese Karte hat. Dieselbe Karte steht in der Verwaltung einmal in einer
   schmalen Spalte neben der Seitenleiste und einmal ueber die volle
   Breite - die Media Query kann das nicht unterscheiden, die Container
   Query schon.

   Damit eine Container Query wirkt, braucht ihr Elternelement
   container-type. Das kostet nichts, solange kein Layout davon abhaengt,
   dass der Container seine Hoehe vom Inhalt bekommt - deshalb bewusst
   "inline-size" (nur die Breite wird zum Bezug), nie "size".
   -------------------------------------------------------------------------- */

.karten-raster {
  container-type: inline-size;
  container-name: raster;
  display: grid;
  gap: var(--s-4);
  grid-template-columns: 1fr;
}

/* Ab 640px Platz zwei Spalten, ab 1000px drei. Die Zahlen beziehen sich
   auf den Container, nicht auf den Bildschirm. */
@container raster (min-width: 640px) {
  .karten-raster {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@container raster (min-width: 1000px) {
  .karten-raster {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* Eine Karte, die immer die volle Breite behalten soll (eine Tabelle, ein
   langer Text), traegt diese Klasse. */
.karten-raster > .volle-breite {
  grid-column: 1 / -1;
}

/* Das Raster ohne feste Spaltenzahl: so viele Spalten, wie bei einer
   Mindestbreite hineinpassen. Fuer Kennzahlen-Kacheln und Kinderkarten,
   wo die Anzahl nicht feststeht. auto-fit statt auto-fill, damit eine
   einzelne Kachel nicht als Viertel dasteht. */
.kachel-raster {
  display: grid;
  gap: var(--s-3);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr));
}

/* Fliesstext bekommt eine Lesebreite. Ohne das laufen die Mitteilungen auf
   dem Tablet quer ueber 110 Zeichen - das Auge findet den naechsten
   Zeilenanfang nicht mehr. */
.lesetext,
.mitteilung-text,
.aushang-text {
  max-width: var(--lesebreite);
  line-height: var(--zh-lesen);
}

/* Zweispaltig ab dem Tablet: Liste links, was dazu gehoert rechts. Das
   Muster, das ein Tablet von einem grossen Telefon unterscheidet. */
.zweispalt {
  container-type: inline-size;
  display: grid;
  gap: var(--s-5);
  grid-template-columns: 1fr;
  align-items: start;
}

@container (min-width: 820px) {
  .zweispalt {
    grid-template-columns: minmax(280px, 340px) 1fr;
  }

  /* Die linke Spalte scrollt mit, statt oben wegzurutschen. */
  .zweispalt > :first-child {
    position: sticky;
    top: calc(var(--header-h) + var(--s-4));
    max-height: calc(100dvh - var(--header-h) - var(--s-6));
    overflow-y: auto;
    overscroll-behavior: contain;
  }
}


/* --------------------------------------------------------------------------
   10. WARTEN, OHNE ZU ZAPPELN

   Ein drehender Kreis sagt "es passiert etwas", aber nicht "was kommt".
   Ein Skelett zeigt die Form dessen, was gleich da ist - die Seite baut
   sich nicht neu auf, sie fuellt sich.

   Wichtig fuer diese App: Der Aufbau geschieht ueber innerHTML. Ohne
   Skelett springt der Inhalt in dem Moment ins Bild, in dem die Antwort
   da ist, und schiebt alles darunter nach unten.
   -------------------------------------------------------------------------- */
.skelett {
  /* Der Verlauf wandert von links nach rechts - der Balken "atmet" nicht,
     er wird gelesen. */
  background: linear-gradient(
    90deg,
    var(--surface-2) 0%,
    var(--surface-3) 40%,
    var(--surface-2) 80%
  );
  background-size: 300% 100%;
  animation: skelett-lauf 1.4s ease-in-out infinite;
  border-radius: var(--radius-sm);
  /* Was im Skelett steht, ist nur Platzhalter - niemand soll es lesen. */
  color: transparent;
  user-select: none;
  pointer-events: none;
}

@keyframes skelett-lauf {
  from { background-position: 100% 0; }
  to   { background-position: -100% 0; }
}

/* Fertige Formen, damit jede Stelle dasselbe Skelett bekommt. */
.skelett-zeile {
  height: 1em;
  margin-bottom: 0.55em;
}

.skelett-zeile:last-child {
  width: 62%;
  margin-bottom: 0;
}

.skelett-titel {
  height: 1.4em;
  width: 45%;
  margin-bottom: var(--s-3);
}

.skelett-kreis {
  border-radius: 50%;
  width: 40px;
  height: 40px;
  flex-shrink: 0;
}

.skelett-karte {
  padding: var(--s-4);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-1);
}

/* Wer Bewegung abgestellt hat, bekommt eine ruhige Flaeche statt des
   wandernden Verlaufs - aber weiterhin die richtige Form. */
@media (prefers-reduced-motion: reduce) {
  .skelett {
    animation: none;
    background: var(--surface-3);
  }
}

/* Wenn Inhalt eintrifft, blendet er auf, statt zu springen. Die Klasse
   setzt das JS nach dem Fuellen. */
.eingeblendet {
  animation: sanft-auf var(--dauer-mittel) var(--bahn-hinein) both;
}

@keyframes sanft-auf {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .eingeblendet {
    animation: none;
  }
}


/* --------------------------------------------------------------------------
   11. LEERZUSTAENDE, DIE WEITERHELFEN

   Im Bestand steht an diesen Stellen ein Satz in kursivem Grau:
   "Keine Unfallberichte vorhanden." Das beantwortet, was NICHT da ist, und
   laesst offen, ob das gut ist, ob man etwas tun muss und was.

   Ein Leerzustand braucht drei Dinge: ein Zeichen, dass die Stelle
   absichtlich leer ist (Symbol), einen Satz, der sagt was das bedeutet,
   und - wo es einen gibt - den naechsten Schritt.
   -------------------------------------------------------------------------- */
.leerzustand {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--s-3);
  padding: var(--s-6) var(--s-4);
  color: var(--ink-2);
}

.leerzustand-symbol {
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: var(--gold-soft);
  color: var(--warn-ink);
}

.leerzustand-symbol i,
.leerzustand-symbol svg {
  width: 26px;
  height: 26px;
}

.leerzustand-titel {
  font-size: var(--fs-lg);
  font-weight: 600;
  color: var(--ink);
  line-height: var(--zh-normal);
}

.leerzustand-text {
  max-width: 44ch;
  font-size: var(--fs-md);
  line-height: var(--zh-lesen);
  /* Nicht kursiv. Kursiv liest sich als Zitat oder Randbemerkung - der
     Satz ist aber die Hauptaussage dieser Flaeche. */
  font-style: normal;
}

/* Der freundliche Fall: Leer ist hier richtig so ("keine Unfaelle" ist
   eine gute Nachricht, keine Luecke). */
.leerzustand.ist-gut .leerzustand-symbol {
  background: var(--success-soft);
  color: var(--success-ink);
}


/* --------------------------------------------------------------------------
   12. DAS GERUEST: KOPFZEILE, SHEET, LEISTE

   Drei Stellen, an denen sich "Webseite" und "App" am deutlichsten
   unterscheiden - und an denen der Bestand jeweils die aeltere Loesung
   hatte.
   -------------------------------------------------------------------------- */

/* Die Kopfzeile klebt oben (das tat sie schon). Neu ist, dass der Inhalt
   darunter durchscheint, statt dagegenzustossen. Das ist nicht Dekoration:
   Eine undurchsichtige Leiste sieht aus, als waere die Seite dort zu Ende;
   eine durchscheinende zeigt, dass darunter weitergeht, was man gerade
   wegscrollt.

   Ohne backdrop-filter bleibt der Hintergrund voll deckend - deshalb die
   Grundfarbe zuerst und die durchscheinende Fassung nur im @supports. */
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  .app-header {
    background: hsl(36, 40%, 98%, 0.82);
    -webkit-backdrop-filter: blur(14px) saturate(1.4);
    backdrop-filter: blur(14px) saturate(1.4);
  }

  .app-bottombar {
    background: hsl(36, 40%, 98%, 0.88);
    -webkit-backdrop-filter: blur(16px) saturate(1.4);
    backdrop-filter: blur(16px) saturate(1.4);
  }
}

/* Die Trennlinie der Kopfzeile erscheint erst, wenn wirklich etwas
   darunter liegt. Solange die Seite ganz oben steht, gibt es nichts zu
   trennen. Die Klasse setzt app.js beim Scrollen. */
.app-header {
  border-bottom-color: transparent;
  transition: border-color var(--dauer-kurz) var(--bahn-standard),
              box-shadow var(--dauer-kurz) var(--bahn-standard);
}

.app-header.ist-gescrollt {
  border-bottom-color: var(--line);
}

/* --- Das Sheet faehrt hoch, statt zu erscheinen ------------------------- */

/* Bisher: [hidden] setzt display:none, also gab es keine Animation - das
   Sheet war schlagartig da. Mit transition-behavior:allow-discrete und
   @starting-style animiert der Browser auch aus display:none heraus. Wo er
   das nicht kann (aeltere Fassungen), bleibt es genau wie vorher: sofort
   da, ohne Fehler. */
@supports (transition-behavior: allow-discrete) {
  .app-sheet {
    transition: transform var(--dauer-mittel) var(--bahn-hinein),
                opacity var(--dauer-kurz) var(--bahn-standard),
                display var(--dauer-mittel) allow-discrete;
  }

  .app-sheet[hidden] {
    display: block;
    opacity: 0;
    transform: translateY(100%);
    pointer-events: none;
    /* Fuer Vorlesehilfen und die Tabulatortaste bleibt es unsichtbar. */
    visibility: hidden;
    transition: transform var(--dauer-mittel) var(--bahn-heraus),
                opacity var(--dauer-kurz) var(--bahn-standard),
                visibility 0s linear var(--dauer-mittel),
                display var(--dauer-mittel) allow-discrete;
  }

  @starting-style {
    .app-sheet:not([hidden]) {
      transform: translateY(100%);
      opacity: 0;
    }
  }

  .app-sheet-backdrop {
    transition: opacity var(--dauer-kurz) var(--bahn-standard),
                display var(--dauer-kurz) allow-discrete;
  }

  .app-sheet-backdrop[hidden] {
    display: block;
    opacity: 0;
    pointer-events: none;
  }

  @starting-style {
    .app-sheet-backdrop:not([hidden]) {
      opacity: 0;
    }
  }
}

/* Der Griff oben am Sheet. Ein Strich, den jeder von seinem Telefon kennt:
   er sagt "das hier kam von unten und geht wieder dorthin". Per ::before,
   damit kein Markup geaendert werden muss. */
.app-sheet::before {
  content: "";
  display: block;
  width: 40px;
  height: 4px;
  margin: 0 auto var(--s-3);
  border-radius: var(--radius-pill);
  background: var(--line-strong);
}

/* Bewegung nur fuer den, der sie moechte. */
@media (prefers-reduced-motion: reduce) {
  .app-sheet,
  .app-sheet[hidden],
  .app-sheet-backdrop,
  .app-sheet-backdrop[hidden] {
    transition: none;
  }
}

/* --- Die untere Leiste -------------------------------------------------- */

/* Der aktive Reiter bekommt eine Marke ueber dem Symbol. Farbe allein
   reicht nicht: Wer Rot und Gruen nicht unterscheidet, sieht bei
   --primary gegen --ink-2 kaum einen Unterschied (WCAG 1.4.1 -
   Information nicht allein ueber Farbe). */
.app-bottombar-item {
  position: relative;
}

.app-bottombar-item[aria-current="page"]::before,
.app-bottombar-item.aktiv::before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  width: 26px;
  height: 3px;
  margin-left: -13px;
  border-radius: 0 0 var(--radius-pill) var(--radius-pill);
  background: var(--primary);
}

/* Die Tippflaeche der Leiste: 60px hoch geteilt durch fuenf Eintraege auf
   einem 375px-Telefon sind 75x60px - das reicht. Was fehlte, ist die
   Sicherung nach unten, falls eine Leiste einmal sechs Eintraege bekommt. */
.app-bottombar-item {
  min-width: 48px;
  min-height: 48px;
}


/* --------------------------------------------------------------------------
   13. DAS TABLET IM HOCHFORMAT

   .parent-grid (index.css:2591) faellt bei @media (max-width: 768px) auf
   eine Spalte zurueck. Ein iPad im Hochformat ist GENAU 768px breit und
   liegt damit auf der falschen Seite der Grenze: Es bekommt dieselbe
   einspaltige Ansicht wie ein iPhone, nur mit doppelt so breiten Karten.
   Gemessen am 20.09.2026 auf 768x1024.

   Die Grenze umzuhaengen (auf 767px) waere die kleine Antwort. Die
   richtige ist eine Container Query: Das Raster entscheidet dann nach
   dem Platz, den es HAT, nicht nach der Breite des Bildschirms - und
   verhaelt sich damit auch dann richtig, wenn es einmal neben einer
   Seitenleiste steht.

   Der Sicherheitsgurt: Der @container-Block gilt nur, wo der Browser
   Container Queries kennt (Chrome/Edge 105+, Safari 16+, Firefox 110+).
   Wo nicht, bleibt die bestehende Media Query die einzige Regel - also
   genau der heutige Zustand.
   -------------------------------------------------------------------------- */
@supports (container-type: inline-size) {
  .app-main {
    container-type: inline-size;
    container-name: inhalt;
  }

  /* Ab 700px Platz zwei Spalten. Ein iPad hoch gibt dem Hauptbereich
     768px minus 2x16px Rand = 736px - die Karten bekommen also je rund
     356px, genug fuer die zwei Datumsfelder in "Abwesenheit melden". */
  @container inhalt (min-width: 700px) {
    .parent-grid {
      grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    }
  }

  /* Unter 700px eine Spalte - dasselbe, was die Media Query tut, nur
     nach dem richtigen Mass. */
  @container inhalt (max-width: 699px) {
    .parent-grid {
      grid-template-columns: minmax(0, 1fr);
    }
  }
}

/* Auf dem Tablet quer (ab 900px Platz) darf das Elternraster eine dritte
   Spalte bekommen - dort stehen sonst zwei sehr breite Saeulen. */
@supports (container-type: inline-size) {
  @container inhalt (min-width: 1040px) {
    .parent-grid {
      grid-template-columns: repeat(3, minmax(0, 1fr));
    }
  }
}

/* Fliesstext in Karten bekommt auch dann eine Lesebreite, wenn die Karte
   ueber die volle Breite geht. 68ch entspricht auf dem iPad etwa
   zwei Dritteln der Breite - der Rest bleibt Luft, und das ist richtig so:
   ein Text, der ueber 110 Zeichen laeuft, wird nicht gelesen, sondern
   ueberflogen. */
.card p,
.card-body p,
.announcement-text,
.board-text {
  max-width: var(--lesebreite);
}

/* Ausgenommen: Text, der in einer engen Spalte steht oder mittig
   ausgerichtet ist - dort waere eine Begrenzung sinnlos oder stoerend. */
.card .u-mitte p,
.leerzustand p,
.kachel p,
td p, th p {
  max-width: none;
}


/* --------------------------------------------------------------------------
   14. KENNZAHLEN-KACHELN

   Die Antwort auf "wie sieht mein Tag aus", bevor die Seite von
   Erinnerungen erzaehlt. Eine Kachel ist eine grosse Zahl mit einem
   kleinen Wort darunter - lesbar, ohne hinzusehen.

   Die Toene folgen den Zustandsfarben des Designsystems (DESIGN.md):
   --success fuer "laeuft", --warn fuer "sieh hin", --ink-2 fuer "zur
   Kenntnis". Keine eigenen Farben.
   -------------------------------------------------------------------------- */
.kennzahl-reihe {
  margin-bottom: var(--s-5);
}

.kennzahl {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--s-3) var(--s-4);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-1);
  text-decoration: none;
  /* Der farbige Streifen links sagt den Zustand auch dann, wenn die
     Schriftfarbe nicht unterscheidbar ist (WCAG 1.4.1). */
  border-left: 4px solid var(--line-strong);
  min-height: 72px;
  justify-content: center;
}

.kennzahl-wert {
  font-size: var(--fs-2xl);
  font-weight: 700;
  line-height: 1.1;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.kennzahl-text {
  font-size: var(--fs-sm);
  color: var(--ink-2);
  line-height: var(--zh-normal);
}

.kennzahl-gut {
  border-left-color: var(--success);
}

.kennzahl-gut .kennzahl-wert {
  color: var(--success-ink);
}

.kennzahl-achtung {
  border-left-color: var(--warn);
}

.kennzahl-achtung .kennzahl-wert {
  color: var(--warn-ink);
}

.kennzahl-ruhig {
  border-left-color: var(--line-strong);
}

.kennzahl-offen {
  border-left-color: var(--primary);
}

.kennzahl-offen .kennzahl-wert {
  color: var(--primary-ink);
}

/* Auf einem Telefon sollen mindestens zwei Kacheln nebeneinander passen -
   sonst wird aus der Uebersicht wieder eine Liste. 200px Mindestbreite aus
   .kachel-raster waeren auf 390px minus Raendern nur eine. */
@media (max-width: 599px) {
  .kennzahl-reihe {
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 140px), 1fr));
    gap: var(--s-2);
  }

  .kennzahl {
    min-height: 62px;
    padding: var(--s-2) var(--s-3);
  }
}


/* --------------------------------------------------------------------------
   15. DER AUFKLAPPER "WEITERE SACHEN, DIE WARTEN KOENNEN"

   <details>/<summary> statt eines selbstgebauten Knopfes mit JS: Der
   Browser bringt den Zustand, die Tastaturbedienung und die Ansage an
   Vorlesehilfen mit. Was fehlt, ist nur das Aussehen - der Standardpfeil
   von <summary> passt zu keinem Designsystem.
   -------------------------------------------------------------------------- */
.offen-mehr {
  margin-top: var(--s-3);
}

.offen-mehr-knopf {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-2);
  /* 44px Tippflaeche, wie jede andere Handlung. */
  min-height: 44px;
  padding: var(--s-2) var(--s-3);
  border-radius: var(--radius-md);
  background: var(--surface-2);
  color: var(--ink-2);
  font-size: var(--fs-sm);
  font-weight: 600;
  cursor: pointer;
  /* Der Standardpfeil weg - der eigene steht rechts. */
  list-style: none;
  transition: background-color var(--dauer-kurz) var(--bahn-standard);
}

.offen-mehr-knopf::-webkit-details-marker {
  display: none;
}

@media (hover: hover) {
  .offen-mehr-knopf:hover {
    background: var(--surface-3);
  }
}

.offen-mehr-knopf i,
.offen-mehr-knopf svg {
  flex-shrink: 0;
  transition: transform var(--dauer-kurz) var(--bahn-standard);
}

.offen-mehr[open] > .offen-mehr-knopf i,
.offen-mehr[open] > .offen-mehr-knopf svg {
  transform: rotate(180deg);
}

.offen-liste-mehr {
  margin-top: var(--s-3);
}

/* Aufklappen mit Bewegung, wo der Browser es kann. Ohne
   interpolate-size:allow-keywords laesst sich height:auto nicht
   animieren - dann springt es auf, wie bisher jedes <details>. */
@supports (interpolate-size: allow-keywords) {
  :root {
    interpolate-size: allow-keywords;
  }

  @media (prefers-reduced-motion: no-preference) {
    .offen-liste-mehr {
      transition: height var(--dauer-mittel) var(--bahn-hinein);
    }
  }
}


/* --------------------------------------------------------------------------
   16. DIE KARTE "HEUTE" IM ELTERNBEREICH

   Vier Kacheln, die man im Vorbeigehen liest: Stimmung, Gegessen,
   Geschlafen, Gewickelt. Darueber der Satz, den die Tagespflegeperson
   ohnehin schreibt.

   Die Kacheln stehen in einem Raster mit auto-fit: bei zwei Angaben zwei
   Spalten, bei vier vier (oder zwei mal zwei auf dem Telefon). Es gibt
   keine leeren Plaetze, weil eine Kachel ohne Inhalt gar nicht erst
   erzeugt wird.
   -------------------------------------------------------------------------- */
.heute-karte {
  /* Die einzige Karte auf dieser Seite mit einem farbigen Kopf - sie ist
     die Antwort auf die Frage, mit der die Seite aufgemacht wird. */
  border-top: 3px solid var(--primary);
}

.heute-fazit {
  font-size: var(--fs-lg);
  line-height: var(--zh-lesen);
  color: var(--ink);
  margin-top: var(--s-3);
  max-width: var(--lesebreite);
  text-wrap: pretty;
}

.heute-kacheln {
  display: grid;
  /* Auf dem Telefon zwei Spalten. auto-fit waere naheliegend, laesst aber
     bei drei Kacheln die dritte halb leer in der zweiten Zeile stehen -
     und drei ist der haeufigste Fall (Stimmung, Gegessen, Geschlafen;
     gewickelt wird nicht jedes Kind). */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-2);
  margin-top: var(--s-4);
}

/* Genau drei Kacheln: die dritte nimmt die volle Breite, statt neben
   einer Luecke zu stehen. :has() fragt danach, ob es ein drittes Kind
   gibt, das zugleich das letzte ist. Wo der Browser :has() nicht kennt,
   bleibt es bei der halben Breite - also genau wie ohne diese Regel. */
.heute-kacheln:has(> :nth-child(3):last-child) > :last-child {
  grid-column: 1 / -1;
  /* Ueber die volle Breite steht der Inhalt nicht mehr mittig gut, sondern
     in einer Reihe: Symbol, Beschriftung, Wert nebeneinander. */
  flex-direction: row;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  text-align: left;
}

.heute-kacheln:has(> :nth-child(3):last-child) > :last-child .heute-kachel-symbol {
  margin-bottom: 0;
}

/* Ab dem Tablet stehen alle Kacheln in einer Reihe - dort ist der Platz
   da, und vier Kacheln nebeneinander liest man in einem Blick. */
@media (min-width: 600px) {
  .heute-kacheln {
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 140px), 1fr));
  }

  .heute-kacheln:has(> :nth-child(3):last-child) > :last-child {
    grid-column: auto;
    flex-direction: column;
    text-align: center;
  }
}

.heute-kachel {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  text-align: center;
  padding: var(--s-3) var(--s-2);
  border-radius: var(--radius-md);
  background: var(--surface-2);
}

.heute-kachel-symbol {
  width: 20px;
  height: 20px;
  color: var(--primary);
  margin-bottom: 2px;
}

.heute-kachel-titel {
  font-size: var(--fs-xs);
  color: var(--ink-2);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.heute-kachel-wert {
  font-size: var(--fs-md);
  font-weight: 600;
  color: var(--ink);
  line-height: var(--zh-normal);
  font-variant-numeric: tabular-nums;
}

.heute-mehr {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin-top: var(--s-2);
  color: var(--primary-ink);
  font-size: var(--fs-sm);
  font-weight: 600;
  text-decoration: none;
}

@media (hover: hover) {
  .heute-mehr:hover {
    text-decoration: underline;
  }
}

/* Das Sprungziel darf nicht unter der klebenden Kopfzeile landen. html
   hat dafuer schon scroll-padding-top; scroll-margin-top am Ziel selbst
   ist die zweite Sicherung - sie wirkt auch dort, wo ein innerer Bereich
   scrollt statt der Seite. */
#tagesberichte {
  scroll-margin-top: calc(var(--header-h) + var(--s-4));
}

/* Der knappe Leerzustand: fuer Stellen, an denen "noch nichts da" der
   Normalfall ist und nicht die Ausnahme. Der grosse Leerzustand mit
   seinen 32px Luft oben und unten beansprucht sonst mehr Platz als die
   gefuellte Karte. */
.leerzustand-knapp {
  padding: var(--s-4) var(--s-3);
  gap: var(--s-2);
}

.leerzustand-knapp .leerzustand-symbol {
  width: 44px;
  height: 44px;
}

.leerzustand-knapp .leerzustand-symbol i,
.leerzustand-knapp .leerzustand-symbol svg {
  width: 20px;
  height: 20px;
}


/* --------------------------------------------------------------------------
   17. DIALOGE WERDEN AUF DEM TELEFON ZU BLAETTERN VON UNTEN

   Der Bestand zeichnet jeden Dialog als mittigen Kasten (max-width 500px,
   scale 0.95 -> 1). Auf einem Rechner ist das richtig. Auf einem Telefon
   ist es das Muster von 2014: Der Kasten erscheint irgendwo in der Mitte,
   seine Knoepfe liegen oben ausser Reichweite des Daumens, und der Rand
   ringsum zeigt eine Seite, die man gerade nicht bedienen kann.

   Heute kommt so etwas von unten: Es faengt dort an, wo der Daumen liegt,
   nimmt die Breite des Bildschirms und laesst oben so viel von der Seite
   stehen, dass klar bleibt, wohin man zurueckkehrt.

   Umgesetzt nur unter 600px und nur fuer Zeiger ohne Hover. Am Rechner und
   auf dem grossen Tablet bleibt alles, wie es war - dort stimmt der
   mittige Kasten.
   -------------------------------------------------------------------------- */
@media (max-width: 599px) and (hover: none) {
  .modal-overlay {
    align-items: flex-end;
    /* Kein Rand mehr ringsum: das Blatt sitzt auf dem unteren Rand. */
    padding: 0;
  }

  .modal-container {
    max-width: none;
    margin: 0;
    /* Oben rund, unten gerade - es kommt von unten und geht dorthin
       zurueck. */
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    border: 0;
    border-top: 1px solid var(--line);
    /* Nie hoeher als 92% des Bildschirms: Ein Rest der Seite bleibt
       sichtbar, damit klar ist, dass darunter etwas liegt. */
    max-height: 92dvh;
    overflow-y: auto;
    /* Die Seite dahinter scrollt nicht mit, wenn man im Blatt am Ende
       angekommen ist. */
    overscroll-behavior: contain;
    /* Platz fuer den Gestenbalken. */
    padding-bottom: max(var(--s-5), env(safe-area-inset-bottom));
    /* Statt scale: von unten hochfahren. */
    transform: translateY(100%);
    transition: transform var(--dauer-mittel) var(--bahn-hinein);
  }

  .modal-overlay.open .modal-container {
    transform: translateY(0);
  }

  /* Der Griff, wie am Sheet. */
  .modal-container::before {
    content: "";
    display: block;
    width: 40px;
    height: 4px;
    margin: 0 auto var(--s-3);
    border-radius: var(--radius-pill);
    background: var(--line-strong);
  }

  /* Der Kopf des Blattes bleibt stehen, waehrend der Inhalt darunter
     scrollt - sonst verliert man bei einem langen Formular den Titel und
     das Kreuz zum Schliessen. */
  .modal-header {
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--surface);
    padding-top: var(--s-1);
    margin-top: calc(var(--s-1) * -1);
  }

  @media (prefers-reduced-motion: reduce) {
    .modal-container {
      transition: none;
      transform: none;
    }
  }
}

/* Solange ein Dialog offen ist, darf die Seite dahinter nicht scrollen.
   Fuer das Sheet gibt es diese Regel schon (body.app-sheet-offen); fuer
   Dialoge fehlte sie. :has() macht sie ohne eine Zeile JavaScript. */
@supports selector(:has(*)) {
  body:has(.modal-overlay.open) {
    overflow: hidden;
  }
}


/* ==========================================================================
   18. DUNKELMODUS

   Bis zum 20.09.2026 hatte die App keinen. Wer sein Telefon dunkel
   gestellt hat - und das sind inzwischen die meisten - bekam abends im
   Kinderzimmer eine weisse Flaeche ins Gesicht.

   WIE ER GEBAUT IST

   Ausschliesslich ueber die Tokens aus index.css. Keine einzige Regel
   nennt hier eine Farbe an einem Element - es werden nur die Werte
   umdefiniert, auf die der Bestand ohnehin zeigt. Das funktioniert, weil
   das Designsystem an den entscheidenden Stellen sauber gebaut ist:
   .btn-primary etwa setzt color: var(--surface), nicht color: white. Wird
   --surface dunkel, wird die Knopfschrift dunkel - und steht dann richtig
   auf dem helleren Pink, das --primary im Dunkeln hat.

   WARUM ER NICHT VON SELBST ANGEHT

   Im Bestand stehen rund 355 feste Farbwerte ausserhalb des :root-Blocks
   (Hexwerte, rgba(...) und so fort). Jeder davon bleibt im Dunkelmodus
   stehen, wo er steht. Solange nicht jeder einzelne angesehen ist, waere
   ein Dunkelmodus, der sich nach der Systemeinstellung von selbst
   einschaltet, ein Versprechen, das an irgendeiner Stelle bricht - und
   zwar bei Joana im Echtbetrieb, nicht hier.

   Deshalb: Der Dunkelmodus haengt an data-theme="dunkel" am <html>, und
   die Voreinstellung ist hell. Die @media-Abfrage steht schon da, ist
   aber an data-theme="auto" gebunden - wer das waehlt, bekommt die
   Systemeinstellung. Sobald die festen Farben durchgesehen sind, kann
   "auto" die Voreinstellung werden; dafuer ist dann genau eine Zeile in
   src/thema.js zu aendern und keine hier.

   DIE HELLIGKEITSORDNUNG IST NICHT GESPIEGELT

   Im Hellen ist die Karte (--surface, weiss) heller als der Grund (--bg,
   Creme). Im Dunkeln ist sie ebenfalls heller als der Grund - nicht
   dunkler. Dieselbe Regel, nicht ihr Gegenteil: Was naeher an der
   Nutzerin liegt, faengt mehr Licht.
   ========================================================================== */

/* Die Farbwerte stehen zweimal - einmal fuer den festen Schalter, einmal
   fuer die Systemeinstellung. CSS kennt keine Mixins, und eine gemeinsame
   Klasse haette einen Schreibvorgang im JavaScript gebraucht, bevor die
   erste Farbe steht (und damit ein sichtbares Aufblitzen in Weiss).
   Die zwei Bloecke sind wortgleich; Aenderungen immer an BEIDEN Stellen.
   Die Probe tools/design/dunkelmodus.py vergleicht sie miteinander. */

:root[data-theme="dunkel"] {
  /* Damit Bildlaufleisten, Datumswaehler und andere Bedienelemente des
     Browsers mitkommen. Ohne diese Zeile bleiben sie weiss - und ein
     weisser Datumswaehler auf dunklem Grund faellt mehr auf als gar kein
     Dunkelmodus. */
  color-scheme: dark;

  /* --- Flaechen. Von dunkel nach hell: Grund, Vertiefung, Karte. --- */
  --bg:        hsl(28, 16%, 9%);
  --bg-2:      hsl(28, 13%, 13%);
  --surface:   hsl(28, 12%, 16%);
  --surface-2: hsl(28, 11%, 20%);
  --surface-3: hsl(28, 10%, 25%);
  --line:        hsl(28, 10%, 27%);
  --line-strong: hsl(28, 10%, 44%);

  /* --- Schrift --- */
  --ink:   hsl(36, 24%, 93%);
  --ink-2: hsl(36, 15%, 76%);
  --ink-3: hsl(36, 12%, 64%);

  /* --- Marke. Das Pink wird heller, damit es auf dunklem Grund traegt;
         die Knopfschrift wird dunkel, weil sie auf --surface zeigt. --- */
  --primary:       hsl(328, 68%, 72%);
  --primary-hover: hsl(328, 72%, 80%);
  --primary-soft:  hsl(328, 26%, 22%);
  --primary-ink:   hsl(328, 66%, 80%);

  --gold:      hsl(44, 80%, 64%);
  --gold-soft: hsl(44, 24%, 18%);

  /* --- Zustaende. Die -soft-Toene werden von hell nach dunkel gedreht,
         die -ink-Toene von dunkel nach hell - sonst staende dunkle
         Schrift auf dunklem Grund. --- */
  --danger:      hsl(4, 72%, 70%);
  --danger-soft: hsl(4, 30%, 20%);
  --danger-ink:  hsl(4, 78%, 80%);

  --success:      hsl(122, 40%, 64%);
  --success-soft: hsl(122, 22%, 17%);
  --success-ink:  hsl(122, 48%, 74%);

  --warn:      hsl(38, 78%, 64%);
  --warn-soft: hsl(38, 28%, 18%);
  --warn-ink:  hsl(40, 82%, 76%);

  --info:      hsl(200, 62%, 66%);
  --info-soft: hsl(200, 28%, 19%);
  --info-ink:  hsl(200, 68%, 78%);

  /* --- Bereichstoene der Navigation --- */
  --ton-blau:        hsl(205, 60%, 68%);
  --ton-gruen:       hsl(122, 40%, 64%);
  --ton-orange:      hsl(28, 78%, 68%);
  --ton-blau-soft:   hsl(205, 26%, 20%);
  --ton-gruen-soft:  hsl(122, 22%, 18%);
  --ton-orange-soft: hsl(28, 26%, 20%);

  /* --- Terracotta (Alt-Token, bewusst zurueckgenommen) --- */
  --accent:       hsl(24, 52%, 68%);
  --accent-light: hsl(24, 24%, 20%);
  --accent-dark:  hsl(24, 50%, 78%);
  --accent-text:  hsl(24, 55%, 78%);

  /* --- Auszeichnung der Angebote --- */
  --prem:      hsl(38, 70%, 66%);
  --prem-hell: hsl(38, 26%, 18%);
  --prem-rand: hsl(38, 30%, 36%);
  --ans:      hsl(212, 45%, 72%);
  --ans-hell: hsl(212, 22%, 20%);
  --ans-rand: hsl(212, 24%, 38%);

  /* --- Schatten. Die Bestandswerte sind warmbraun eingefaerbt und auf
         dunklem Grund praktisch unsichtbar. Im Dunkeln traegt nicht der
         Schatten die Ebene, sondern die Helligkeit der Flaeche - der
         Schatten setzt nur noch die Kante. --- */
  --shadow-1: 0 1px 2px rgb(0 0 0 / 0.55), 0 1px 3px rgb(0 0 0 / 0.35);
  --shadow-2: 0 6px 20px rgb(0 0 0 / 0.55);
  --shadow-3: 0 16px 40px rgb(0 0 0 / 0.65);

  /* --- Der Fokusring aus Abschnitt 4 --- */
  --ring: hsl(328, 78%, 74%);
  --ring-schatten: 0 0 0 3px hsl(328, 78%, 74%, 0.32);
}

/* KOPIE des Blocks darueber, fuer data-theme="auto" plus dunkle
   Systemeinstellung. Aenderungen immer an BEIDEN Stellen. */
@media (prefers-color-scheme: dark) {
  :root[data-theme="auto"] {
    color-scheme: dark;

    --bg:        hsl(28, 16%, 9%);
    --bg-2:      hsl(28, 13%, 13%);
    --surface:   hsl(28, 12%, 16%);
    --surface-2: hsl(28, 11%, 20%);
    --surface-3: hsl(28, 10%, 25%);
    --line:        hsl(28, 10%, 27%);
    --line-strong: hsl(28, 10%, 44%);

    --ink:   hsl(36, 24%, 93%);
    --ink-2: hsl(36, 15%, 76%);
    --ink-3: hsl(36, 12%, 64%);

    --primary:       hsl(328, 68%, 72%);
    --primary-hover: hsl(328, 72%, 80%);
    --primary-soft:  hsl(328, 26%, 22%);
    --primary-ink:   hsl(328, 66%, 80%);

    --gold:      hsl(44, 80%, 64%);
    --gold-soft: hsl(44, 24%, 18%);

    --danger:      hsl(4, 72%, 70%);
    --danger-soft: hsl(4, 30%, 20%);
    --danger-ink:  hsl(4, 78%, 80%);

    --success:      hsl(122, 40%, 64%);
    --success-soft: hsl(122, 22%, 17%);
    --success-ink:  hsl(122, 48%, 74%);

    --warn:      hsl(38, 78%, 64%);
    --warn-soft: hsl(38, 28%, 18%);
    --warn-ink:  hsl(40, 82%, 76%);

    --info:      hsl(200, 62%, 66%);
    --info-soft: hsl(200, 28%, 19%);
    --info-ink:  hsl(200, 68%, 78%);

    --ton-blau:        hsl(205, 60%, 68%);
    --ton-gruen:       hsl(122, 40%, 64%);
    --ton-orange:      hsl(28, 78%, 68%);
    --ton-blau-soft:   hsl(205, 26%, 20%);
    --ton-gruen-soft:  hsl(122, 22%, 18%);
    --ton-orange-soft: hsl(28, 26%, 20%);

    --accent:       hsl(24, 52%, 68%);
    --accent-light: hsl(24, 24%, 20%);
    --accent-dark:  hsl(24, 50%, 78%);
    --accent-text:  hsl(24, 55%, 78%);

    --prem:      hsl(38, 70%, 66%);
    --prem-hell: hsl(38, 26%, 18%);
    --prem-rand: hsl(38, 30%, 36%);
    --ans:      hsl(212, 45%, 72%);
    --ans-hell: hsl(212, 22%, 20%);
    --ans-rand: hsl(212, 24%, 38%);

    --shadow-1: 0 1px 2px rgb(0 0 0 / 0.55), 0 1px 3px rgb(0 0 0 / 0.35);
    --shadow-2: 0 6px 20px rgb(0 0 0 / 0.55);
    --shadow-3: 0 16px 40px rgb(0 0 0 / 0.65);

    --ring: hsl(328, 78%, 74%);
    --ring-schatten: 0 0 0 3px hsl(328, 78%, 74%, 0.32);
  }
}


/* --- Was die Tokens allein nicht erledigen --------------------------- */

/* Die durchscheinende Kopfzeile aus Abschnitt 12 nennt ihre Farbe fest
   mit Deckkraft - eine Token-Variable kann keinen Alphawert annehmen.
   Im Dunkeln braucht sie deshalb ihren eigenen Gegenwert. */
:root[data-theme="dunkel"] .app-header,
:root[data-theme="dunkel"] .app-bottombar {
  background: hsl(28, 16%, 9%, 0.86);
}

@media (prefers-color-scheme: dark) {
  :root[data-theme="auto"] .app-header,
  :root[data-theme="auto"] .app-bottombar {
    background: hsl(28, 16%, 9%, 0.86);
  }
}

/* Fotos bekommen im Dunkeln etwas weniger Leuchten, damit ein weisser
   Bildhintergrund nicht aus der Seite springt. Die Bildmarke ist bewusst
   ausgenommen - sie IST die Marke und soll hell bleiben. */
:root[data-theme="dunkel"] img:not(.app-brand-mark img):not(.logo-icon img) {
  filter: brightness(0.94);
}

/* Der weisse Kreis der Bildmarke bleibt hell, aber sein Rand darf nicht
   mehr die helle Linienfarbe sein - sonst leuchtet er doppelt. */
:root[data-theme="dunkel"] .app-brand-mark,
:root[data-theme="dunkel"] .logo-icon {
  background: hsl(36, 30%, 94%);
  border-color: hsl(28, 10%, 30%);
}

/* Gedruckt wird immer hell. Ein Dunkelmodus auf Papier waere eine
   schwarze Seite - im Fall dieser App ein Notfallblatt, das niemand
   lesen kann. */
@media print {
  :root[data-theme="dunkel"],
  :root[data-theme="auto"] {
    color-scheme: light;
    --bg: #ffffff;
    --bg-2: #ffffff;
    --surface: #ffffff;
    --surface-2: #ffffff;
    --surface-3: #f4f4f4;
    --line: #cccccc;
    --line-strong: #999999;
    --ink: #000000;
    --ink-2: #333333;
    --ink-3: #555555;
  }
}


/* --- Die festen Farben, die im Dunkeln stehenbleiben ------------------
 *
 * Gemessen im Browser statt gezaehlt im Code: ein Skript geht im
 * Dunkelmodus jedes sichtbare Element durch und rechnet die Helligkeit
 * seiner Hintergrundfarbe aus. Was heller als 0,45 ist, steht auf der
 * Liste. Das findet genau die Stellen, die wirklich stoeren - und nicht
 * die 355 Farbwerte im Quelltext, von denen die meisten in Druckregeln,
 * Schatten oder ohnehin dunklen Toenen stehen.
 *
 * Die zwei Regeln hier tragen !important. Das ist in dieser Datei sonst
 * verboten (siehe Kopf), hier aber unvermeidlich: Sie muessen gegen
 * Utility-Klassen bestehen, die selbst !important tragen. Eine Regel
 * ohne !important haette gegen sie keine Chance, ganz gleich wie spaet
 * sie kommt.
 * -------------------------------------------------------------------- */

/* Wetterkarte (utilities-runde6.css:88) und Check-In-Karte
   (utilities-runde7.css:124). Beide sind ein heller Verlauf, der im
   Dunkeln als leuchtender Block stehen bleibt. Sie bekommen denselben
   Verlauf in dunkel - die Karte behaelt damit ihre leichte Tiefe. */
:root[data-theme="dunkel"] .u-bgs-linear-gradient-135deg-hfdfbf7-hf5f2eb,
:root[data-theme="dunkel"] .u-bgs-linear-gradient-135deg-hfff-hfdfbf7 {
  background-color: hsl(28, 12%, 16%) !important;
  background-image: linear-gradient(135deg, hsl(28, 12%, 18%), hsl(28, 12%, 14%)) !important;
}

@media (prefers-color-scheme: dark) {
  :root[data-theme="auto"] .u-bgs-linear-gradient-135deg-hfdfbf7-hf5f2eb,
  :root[data-theme="auto"] .u-bgs-linear-gradient-135deg-hfff-hfdfbf7 {
    background-color: hsl(28, 12%, 16%) !important;
    background-image: linear-gradient(135deg, hsl(28, 12%, 18%), hsl(28, 12%, 14%)) !important;
  }
}


/* ==========================================================================
   19. WAS DAS AUDIT VOM 20.09.2026 GEFUNDEN HAT

   Zwoelf Linsen haben die Oberflaeche durchgesehen, 178 Befunde. Ein
   grosser Teil davon ist in den Abschnitten 1-18 schon erledigt. Was hier
   folgt, sind die Stellen, die dort nicht hineinpassten - jeweils mit der
   Zeilennummer der Fundstelle, damit nachvollziehbar bleibt, worauf sich
   die Regel bezieht.
   ========================================================================== */

/* --- Der unsichtbare Spinner (parent.js:5226) -------------------------
 *
 * `btn.innerHTML = '<span class="spinner"></span> Antwort wird gesendet...'`
 * Die Klasse .spinner ist in KEINEM Stylesheet des Projekts definiert -
 * nachgesehen in allen fuenf. Der Span ist damit 0x0 Pixel gross: Wer
 * beim Abstimmen auf "Senden" tippt, sieht nur Text und keinerlei
 * Bewegung. An zwei anderen Stellen (admin.js:3447, parent.js:1808)
 * traegt derselbe Span zusaetzlich eine Kette von acht Utility-Klassen,
 * die ihm von Hand Groesse, Rand und Drehung geben - dort ist er
 * sichtbar. Dreimal dieselbe Sache, zweimal ausgeschrieben, einmal
 * vergessen.
 *
 * Die Regel hier gibt der Klasse selbst, was sie braucht. Die
 * Utility-Ketten an den beiden anderen Stellen tragen !important und
 * gewinnen weiterhin - sie werden dadurch ueberfluessig, aber nichts
 * aendert sich an ihrem Aussehen.
 *
 * @keyframes spin gibt es bereits (index.css:2277).
 * -------------------------------------------------------------------- */
.spinner {
  display: inline-block;
  width: 1em;
  height: 1em;
  vertical-align: -0.15em;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

/* Wer Bewegung abgestellt hat, bekommt ihn langsam statt gar nicht: Ein
   stehender Kreis saehe aus wie ein Zeichen, nicht wie ein Vorgang. */
@media (prefers-reduced-motion: reduce) {
  .spinner {
    animation-duration: 2.4s;
  }
}


/* --- Die Meldung gehoert nach unten (index.css:2244) ------------------
 *
 * `#alert-box, #parent-alert { position: fixed; top: 16px; ... }`
 * Die Rueckmeldung auf jede Handlung - "gespeichert", "ging nicht" -
 * erscheint am oberen Rand und legt sich dort ueber die Kopfzeile. Auf
 * einem Telefon ist das die Stelle, die am weitesten vom Daumen entfernt
 * ist und auf die gerade niemand sieht: Getippt wurde unten.
 *
 * Auf dem Telefon kommt sie deshalb von unten, ueber der unteren Leiste.
 * Am Rechner bleibt sie oben - dort wandert der Blick beim Speichern
 * nach oben zur Ueberschrift, und unten ist keine Leiste, an der sie
 * sich festhalten koennte.
 * -------------------------------------------------------------------- */
@media (max-width: 1024px) {
  #alert-box,
  #parent-alert {
    top: auto;
    /* Ueber der unteren Leiste, nicht dahinter. */
    bottom: calc(var(--bottombar-h) + env(safe-area-inset-bottom) + var(--s-3));
    /* Etwas schmaler als der Bildschirm, damit sie als Schwebendes
       erkennbar bleibt und nicht als neue Leiste. */
    max-width: min(560px, calc(100vw - 2 * var(--rand)));
    box-shadow: var(--shadow-2);
  }
}

/* Die Meldung springt an und aus (meldung.js:55: ziel.hidden = false).
   Mit allow-discrete blendet sie auf, auch aus display:none heraus. */
@supports (transition-behavior: allow-discrete) {
  #alert-box,
  #parent-alert {
    transition: opacity var(--dauer-kurz) var(--bahn-standard),
                transform var(--dauer-kurz) var(--bahn-hinein),
                display var(--dauer-kurz) allow-discrete;
  }

  #alert-box[hidden],
  #parent-alert[hidden] {
    display: block;
    opacity: 0;
    pointer-events: none;
    visibility: hidden;
  }

  @starting-style {
    #alert-box:not([hidden]),
    #parent-alert:not([hidden]) {
      opacity: 0;
    }
  }

  /* Von unten kommend faehrt sie ein Stueck hoch, von oben kommend
     ein Stueck herunter - immer aus der Richtung, aus der sie kommt.
     translateX(-50%) muss dabei erhalten bleiben, sonst rutscht sie
     aus der Mitte. */
  @media (max-width: 1024px) {
    @starting-style {
      #alert-box:not([hidden]),
      #parent-alert:not([hidden]) {
        transform: translateX(-50%) translateY(12px);
      }
    }
  }

  @media (min-width: 1025px) {
    @starting-style {
      #alert-box:not([hidden]),
      #parent-alert:not([hidden]) {
        transform: translateX(-50%) translateY(-12px);
      }
    }
  }
}

@media (prefers-reduced-motion: reduce) {
  #alert-box,
  #parent-alert {
    transition: none;
  }
}


/* --- Fotos: laden und Platz halten (parent.js:3602) -------------------
 *
 * Die Fotogalerie laedt alle Bilder sofort und ohne Angabe ihres
 * Seitenverhaeltnisses. Beides zusammen ergibt den Effekt, den jeder
 * kennt: Die Seite steht, man fangt an zu lesen, und dann schiebt ein
 * nachgeladenes Bild alles nach unten.
 *
 * aspect-ratio haelt den Platz frei, BEVOR das Bild da ist. Das ist
 * keine Frage der Geschwindigkeit, sondern der Ruhe.
 * -------------------------------------------------------------------- */
.foto-raster img,
.gallery img,
.photo-grid img,
[class*="foto"] img,
[class*="bild-"] img {
  aspect-ratio: 4 / 3;
  object-fit: cover;
  width: 100%;
  height: auto;
  /* Solange das Bild laedt, steht dort eine ruhige Flaeche statt eines
     weissen Lochs. */
  background: var(--surface-2);
}

/* Bilder, deren Seitenverhaeltnis der Inhalt bestimmt (ein Ausweis, ein
   Diagramm), nehmen diese Klasse und behalten ihre eigene Form. */
img.eigenes-mass {
  aspect-ratio: auto;
  object-fit: contain;
}


/* --- Tippflaechen auf dem Tablet quer (index.css:602) -----------------
 *
 * Die Sammelregel, die 44px sichert, haengt an Media Queries fuer
 * schmale Schirme. Ein Tablet im Querformat ist 1024-1194px breit und
 * faellt damit heraus - dort schrumpfen Knoepfe auf die Masse, die fuer
 * eine Maus gedacht sind, waehrend bedient wird wie auf dem Telefon.
 *
 * Die richtige Frage ist nicht "wie breit", sondern "womit wird getippt".
 * pointer: coarse beantwortet genau die - unabhaengig von der Breite.
 * -------------------------------------------------------------------- */
@media (pointer: coarse) {
  .btn,
  .app-nav-item,
  .app-menu-item,
  .chip,
  .segment,
  button:not(.btn-icon-klein),
  [role="button"],
  a.knopf {
    min-height: 44px;
  }

  /* Knoepfe, die nur ein Symbol tragen, brauchen die 44px auch in der
     Breite - sonst ist die Flaeche hoch und schmal. */
  .btn-icon,
  .icon-btn,
  .modal-close {
    min-width: 44px;
    min-height: 44px;
  }

  /* Zeilen in Listen und Tabellen, die als Ganzes angetippt werden. */
  .tabelle-karten tr,
  .liste-zeile,
  .offen-zeile {
    min-height: 44px;
  }
}


/* --- Tabellen: Zebra, klebender Kopf (index.css:2563) -----------------
 *
 * Eine Tabelle ohne Zeilenwechsel verliert das Auge nach der vierten
 * Zeile, und ohne klebende Kopfzeile weiss nach dem Scrollen niemand
 * mehr, welche Spalte was war. Beides kostet zwei Regeln.
 * -------------------------------------------------------------------- */
.table tbody tr:nth-child(even) > * {
  background: var(--surface-2);
}

/* Der klebende Kopf gilt nur, wo die Tabelle wirklich eine Tabelle ist.
   Unter 768px werden .tabelle-karten zu Karten, und ihr thead ist dann
   ohnehin ausgeblendet. */
@media (min-width: 768px) {
  .table thead th {
    position: sticky;
    top: var(--header-h);
    z-index: 1;
    background: var(--surface);
    /* Ohne eigenen Rand verschwindet die Trennung, sobald Zeilen
       darunter durchlaufen - border-collapse frisst den Rahmen einer
       klebenden Zelle. */
    box-shadow: inset 0 -1px 0 var(--line-strong);
  }
}

/* In einem Dialog oder einer Karte mit eigenem Rollbereich waere
   top: var(--header-h) falsch - dort klebt der Kopf am Anfang des
   Rollbereichs, nicht unter der Kopfzeile der Seite. */
.modal-container .table thead th,
.scroll-bereich .table thead th {
  top: 0;
}


/* --- Der erste Eindruck: das Startgeruest (index.html) ---------------
 *
 * Bis src/app.js geladen und einmal gezeichnet hat, steht diese Flaeche
 * auf dem Bildschirm. Auf einer schnellen Verbindung ist das ein
 * Wimpernschlag, auf einer langsamen die erste Sekunde jedes Aufrufs -
 * und auf dem Telefon, das die Datei aus dem Offline-Cache holt, jedes
 * Mal die erste Sekunde ueberhaupt.
 *
 * Vorher stand hier "Die App lädt …" in der Systemschrift, mittig auf
 * 100vh. Das ist eine Ansage. Jetzt steht hier die Form dessen, was
 * gleich kommt - die Seite baut sich nicht auf, sie fuellt sich.
 *
 * Die Masse sind bewusst dieselben wie im echten Geruest (--header-h,
 * --inhalt-max, --rand), damit nichts springt, wenn die App uebernimmt.
 * -------------------------------------------------------------------- */
.start-geruest {
  min-height: 100vh;
  min-height: 100dvh;
  background: var(--bg);
}

.start-kopf {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  height: var(--header-h);
  padding: 0 var(--rand);
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}

/* An der Stelle, an der gleich der Loewe und der Name stehen. */
.start-marke {
  width: 128px;
  height: 18px;
  border-radius: var(--radius-sm);
}

.start-inhalt {
  display: grid;
  gap: var(--s-4);
  max-width: var(--inhalt-max);
  margin: 0 auto;
  padding: var(--s-5) var(--rand);
}

@media (min-width: 900px) {
  .start-inhalt {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}


/* --- Dialoge: was der Browser allein nicht regelt ---------------------
 *
 * Zwei Befunde aus dem Audit, die zusammengehoeren:
 *
 * (index.css:2297) Ein geschlossener Dialog bleibt in der
 * Tabulatorreihenfolge. .modal-overlay wird ueber opacity:0 und
 * pointer-events:none versteckt, nicht ueber display - fuer die Maus ist
 * er damit weg, fuer die Tabulatortaste nicht. Wer sich mit der Tastatur
 * durch die Seite bewegt, laeuft durch die Knoepfe eines Dialogs, den
 * niemand sehen kann.
 *
 * Der Bottom-Sheet-Umbau aus Abschnitt 17 macht das nicht besser - er
 * aendert nur die Lage. Beides braucht dieselbe Antwort.
 * -------------------------------------------------------------------- */
.modal-overlay:not(.open) {
  /* Nimmt den ganzen Teilbaum aus der Tabulatorreihenfolge UND aus dem
     Baum der Vorlesehilfen - genau das, was man von "geschlossen"
     erwartet. Anders als display:none laesst es die Uebergaenge aus
     Abschnitt 17 weiterlaufen. */
  visibility: hidden;
}

.modal-overlay.open {
  visibility: visible;
}

/* Die Sichtbarkeit muss VERZOEGERT umschalten, sonst ist der Dialog
   schon weg, bevor er ausgeblendet hat. */
.modal-overlay {
  transition: opacity var(--transition-normal),
              visibility 0s linear var(--dauer-mittel);
}

.modal-overlay.open {
  transition: opacity var(--transition-normal),
              visibility 0s linear 0s;
}


/* ==========================================================================
   20. KONTRAST: --ink-3 VERFEHLT AA, UEBERALL

   Gemessen am 21.09.2026 im Browser, quer durch alle 22 Reiter der
   Verwaltung: In sieben davon steht Text mit einem Kontrast von 3,88:1
   auf weissem Grund. Gefordert sind 4,50:1 (WCAG 1.4.3, Normalschrift).
   Immer derselbe Wert, immer dieselbe Ursache - der Token --ink-3.

   Betroffen sind unter anderem .ist-leer ("Heute noch nichts
   eingetragen"), .entw-kind-doku, .ew-karte-bereich, .entw-kopf-zusatz,
   .entw-perle-datum, .entw-fussnote und .dl-link-adresse: 39 Stellen im
   Stylesheet, die alle auf denselben Token zeigen.

   DER KOMMENTAR IM BESTAND WAR ZU OPTIMISTISCH

   In index.css steht an --ink-3: "nur fuer Nebentext ab 0.85rem, darunter
   reicht der Kontrast nicht mehr". Die Annahme dahinter ist, dass ab
   einer gewissen Groesse weniger Kontrast genuegt. Das stimmt - aber die
   Grenze liegt bei 18,66px FETT oder 24px normal, nicht bei 13,6px.
   Gemessen wurde 3,88:1 auch bei 15,2px. Der Token verfehlt AA in jeder
   Groesse, in der er tatsaechlich vorkommt.

   DIE RECHNUNG

     hsl(200, 10%, 50%)  = #73848c   auf Weiss 3,89:1   (Bestand)
     hsl(200, 10%, 45%)  = #63777f   auf Weiss 4,65:1
     hsl(200, 10%, 44%)  = #63747d   auf Weiss 4,86:1   auf --bg 4,63:1

   Gewaehlt ist 44%: --ink-3 steht nicht nur auf Weiss, sondern auch auf
   dem cremefarbenen --bg und auf --bg-2. 45% bestuende auf Weiss knapp,
   auf Creme nicht mehr sicher.

   WAS SICH SONST NOCH AENDERT

   An vier Stellen ist --ink-3 keine Schrift, sondern eine Flaeche
   (Statuspunkt "aus", index.css:1634/4289/4297). Die werden um sechs
   Prozentpunkte dunkler - ein grauer Punkt, der etwas kraeftiger grau
   ist. Das ist die gewollte Richtung: Auch ein Statuspunkt muss sich vom
   Grund abheben.
   ========================================================================== */
:root {
  --ink-3: hsl(200, 10%, 44%);
}


/* ==========================================================================
   21. WAS DER DUNKELMODUS AUFGEDECKT HAT

   Gemessen am 21.09.2026 quer durch alle 22 Reiter der Verwaltung, im
   Dunkelmodus, mit einem Skript, das jede Flaeche und jeden Textkontrast
   ausrechnet. Vier Reiter fielen auf. Die Befunde sind interessanter als
   der Dunkelmodus selbst: Es sind alles Stellen, an denen eine Farbe
   fest verdrahtet ist statt ueber einen Token zu laufen. Der Dunkelmodus
   macht sie nur sichtbar - falsch waren sie vorher schon.
   ========================================================================== */

/* --- Die gelbe Erinnerungszeile (index.css:5664) ---------------------
 *
 *   .offen-zeile.offen-achtung {
 *     border-left-color: #d97706;
 *     background: #fef3c7;
 *     color: #713f12;
 *   }
 *
 * Drei feste Farben, wo es drei Tokens gibt (--warn, --warn-soft,
 * --warn-ink). Im Dunkelmodus bleibt die Flaeche hellgelb stehen,
 * waehrend .offen-text darin seine Farbe vom umgebenden --ink erbt und
 * damit hell wird: gemessener Kontrast 1,11:1 an sieben Zeilen. Das ist
 * kein "sieht nicht schoen aus", das ist unlesbar - und es steht auf der
 * ersten Seite, die Joana morgens oeffnet.
 *
 * Auf Tokens umgestellt stimmt es in beiden Modi und folgt kuenftigen
 * Aenderungen an den Warnfarben von selbst.
 * -------------------------------------------------------------------- */
.offen-zeile.offen-achtung {
  border-left-color: var(--warn);
  background: var(--warn-soft);
  color: var(--warn-ink);
}

.offen-zeile.offen-achtung .offen-marke {
  color: var(--warn-ink);
}

/* Dieselbe Sache in Rot: color: #500 war ein fast schwarzes Dunkelrot. */
.offen-zeile.offen-warnung {
  color: var(--danger-ink);
}

/* .offen-text und .offen-nebensatz erben ihre Farbe vom umgebenden
   Kasten - das ist richtig so, sobald der Kasten eine hat. Ohne diese
   beiden Regeln holten sie sie sich vom Seitengrund, und genau dort
   entstand der 1,11:1-Fall. */
.offen-zeile.offen-achtung .offen-text,
.offen-zeile.offen-warnung .offen-text {
  color: inherit;
}

.offen-zeile.offen-achtung .offen-nebensatz,
.offen-zeile.offen-warnung .offen-nebensatz {
  color: inherit;
  opacity: 0.82;
}


/* --- Die Wochenkoepfe des Speiseplans (index.css:6733) ---------------
 *
 *   background: var(--meal-bg, var(--bg-secondary));
 *
 * --meal-bg wird im JavaScript als Inline-Stil gesetzt, mit festen
 * hellen Farben je Mahlzeit (Fruehstueck blaeulich, Mittag roetlich,
 * Vesper gruenlich). Im Dunkeln bleiben sie hell stehen.
 *
 * Ein Inline-Stil laesst sich aus dem Stylesheet nur mit !important
 * schlagen - das ist hier der Grund fuer die Ausnahme von der Regel im
 * Dateikopf. Die Toenung geht verloren, die Zuordnung ueber den farbigen
 * Balken links (--meal-color) bleibt.
 * -------------------------------------------------------------------- */
:root[data-theme="dunkel"] .meal-week-rowhead {
  background: var(--surface-2) !important;
  color: var(--ink) !important;
}

@media (prefers-color-scheme: dark) {
  :root[data-theme="auto"] .meal-week-rowhead {
    background: var(--surface-2) !important;
    color: var(--ink) !important;
  }
}


/* --- Der Merkknopf auf der Angebotskarte (index.css:9029) ------------
 *
 *   background: rgba(255, 255, 255, 0.88);
 *
 * Ein fast weisser Kreis, der auf der Illustration der Karte sitzt. Die
 * Illustrationsflaeche selbst (.ang-karte-bild mit --ang-hell) bleibt im
 * Dunkeln bewusst hell - sie ist ein Bild, und ein Bild wird nicht
 * dunkel gefaerbt. Der Knopf darauf braucht dann aber auch im Dunkeln
 * seinen hellen Grund, sonst verschwindet er in der Zeichnung.
 *
 * Er bekommt hier nur seine Schrift mit: currentColor war im Dunkeln
 * hell und stand damit auf hellem Grund.
 * -------------------------------------------------------------------- */
:root[data-theme="dunkel"] .ang-merk {
  color: hsl(28, 16%, 16%);
  border-color: hsl(28, 10%, 70%);
}

@media (prefers-color-scheme: dark) {
  :root[data-theme="auto"] .ang-merk {
    color: hsl(28, 16%, 16%);
    border-color: hsl(28, 10%, 70%);
  }
}


/* --- Papier bleibt Papier (Reiter "Dokumente") -----------------------
 *
 * Die Vertragsvorschau zeigt am Bildschirm, wie der Vertrag auf Papier
 * aussieht: weisses Blatt, schwarze Schrift, feste Farben - und das ist
 * dort richtig, es IST eine Vorschau.
 *
 * Im Dunkelmodus wurde daraus ein Zwitter: Das Blatt blieb weiss (feste
 * Farben), die Schrift wurde hell (Token). Gemessener Kontrast 1,15:1
 * an vierzehn Stellen - die Vorschau war schlicht leer.
 *
 * Die Antwort ist nicht, das Blatt dunkel zu machen, sondern es ganz
 * hell zu lassen: Innerhalb dieser Bereiche gelten wieder die hellen
 * Tokens, auch wenn ringsum alles dunkel ist. Ein Blatt Papier auf einem
 * dunklen Tisch.
 * -------------------------------------------------------------------- */
:root[data-theme="dunkel"] .print-area,
:root[data-theme="dunkel"] .print-only,
:root[data-theme="dunkel"] .contract-preview,
:root[data-theme="dunkel"] .invoice-print-container,
:root[data-theme="dunkel"] [id$="-print-view"],
:root[data-theme="auto"] .print-area,
:root[data-theme="auto"] .print-only,
:root[data-theme="auto"] .contract-preview,
:root[data-theme="auto"] .invoice-print-container,
:root[data-theme="auto"] [id$="-print-view"] {
  color-scheme: light;
  --bg: hsl(36, 40%, 98%);
  --bg-2: hsl(36, 30%, 95%);
  --surface: #ffffff;
  --surface-2: hsl(36, 30%, 97%);
  --surface-3: hsl(36, 26%, 94%);
  --line: hsl(36, 20%, 88%);
  --line-strong: hsl(36, 16%, 78%);
  --ink: hsl(200, 20%, 20%);
  --ink-2: hsl(200, 12%, 38%);
  --ink-3: hsl(200, 10%, 44%);
  /* Die Marke bleibt die dunklere Fassung - auf weissem Papier. */
  --primary: hsl(328, 52%, 48%);
  --primary-soft: hsl(328, 60%, 95%);
  --primary-ink: hsl(328, 50%, 34%);
  --danger-ink: hsl(0, 60%, 34%);
  --success-ink: hsl(122, 45%, 24%);
  --warn-soft: hsl(40, 90%, 92%);
  --warn-ink: hsl(35, 80%, 30%);
  background: #ffffff;
  color: hsl(200, 20%, 20%);
}

/* Damit das Blatt auf dem dunklen Grund auch als Blatt erkennbar ist. */
:root[data-theme="dunkel"] .contract-preview,
:root[data-theme="dunkel"] [id$="-print-view"] {
  border-radius: var(--radius-md);
  box-shadow: 0 8px 28px rgb(0 0 0 / 0.55);
}


/* --- Nachtrag zu Abschnitt 21: die echten Klassennamen ---------------
 *
 * Die Papier-Regel oben zielte auf .print-area/.contract-preview - so
 * heissen die Bereiche aber nicht. Nachgesehen im laufenden Browser: Das
 * Blatt der Vertragsvorschau ist #doc-paper.doc-preview-paper, und seine
 * Schrift steht fest auf #333. Im Dunkelmodus wurde der Grund dunkel
 * (er zeigt auf einen Token), die Schrift blieb - gemessener Kontrast
 * 1,15:1 an vierzehn Stellen.
 *
 * Dieser Nachtrag ergaenzt die tatsaechlichen Namen. Die Liste oben
 * bleibt stehen: .print-area und [id$="-print-view"] gibt es wirklich,
 * sie tragen die Druckansichten von Quittung, Mahnung und Steuerbericht.
 * -------------------------------------------------------------------- */
:root[data-theme="dunkel"] .doc-preview-paper,
:root[data-theme="auto"] .doc-preview-paper,
:root[data-theme="dunkel"] .nw-blatt,
:root[data-theme="auto"] .nw-blatt {
  color-scheme: light;
  --bg: hsl(36, 40%, 98%);
  --bg-2: hsl(36, 30%, 95%);
  --surface: #ffffff;
  --surface-2: hsl(36, 30%, 97%);
  --surface-3: hsl(36, 26%, 94%);
  --white: #ffffff;
  --line: hsl(36, 20%, 88%);
  --line-strong: hsl(36, 16%, 78%);
  --ink: hsl(200, 20%, 20%);
  --ink-2: hsl(200, 12%, 38%);
  --ink-3: hsl(200, 10%, 44%);
  --text-main: hsl(200, 20%, 20%);
  --text-muted: hsl(200, 12%, 38%);
  --primary: hsl(328, 52%, 48%);
  --primary-soft: hsl(328, 60%, 95%);
  --primary-ink: hsl(328, 50%, 34%);
  background: #ffffff;
  color: hsl(200, 20%, 20%);
  border-radius: var(--radius-md);
  box-shadow: 0 8px 28px rgb(0 0 0 / 0.55);
}


/* --- Die Bereichsfarben der Angebote im Dunkeln ----------------------
 *
 * Jede Angebotskarte bekommt ihre Bereichsfarbe als Inline-Stil
 * (--ang-farbe, gesetzt in src/components/angebote.js). Diese zwoelf
 * Farben sind fuer weissen Grund gerechnet - sie sind dunkel, teilweise
 * sehr dunkel (Garten 34% Helligkeit). Auf dem dunklen Kartengrund des
 * Dunkelmodus ergibt das 2,77:1 bis 2,83:1 an 141 Stellen.
 *
 * Sie einfach durch --ink-2 zu ersetzen waere die einfache Antwort und
 * die falsche: Die Farbe IST hier die Information - sie sagt, zu welchem
 * Bereich ein Angebot gehoert, und zwar auf 364 Karten.
 *
 * color-mix() mischt stattdessen jede Farbe mit Weiss auf. Die
 * Unterscheidung bleibt, die Helligkeit stimmt. In oklab gerechnet, weil
 * das Aufhellen dort die Farbigkeit besser haelt als in sRGB - ein in
 * sRGB aufgehelltes Dunkelgruen wird milchig.
 *
 * Wo der Browser color-mix nicht kennt (vor Chrome 111 / Safari 16.2),
 * greift die Regel gar nicht und es bleibt beim Bestand. Das ist
 * vertretbar: Der Dunkelmodus ist neu und muss nicht rueckwirkend auf
 * alten Browsern stimmen.
 * -------------------------------------------------------------------- */
@supports (color: color-mix(in oklab, red, white)) {
  :root[data-theme="dunkel"] .ang-karte-bereich,
  :root[data-theme="dunkel"] .ang-bereich-name,
  :root[data-theme="dunkel"] .ang-bereich-zahl,
  :root[data-theme="dunkel"] .ang-bereich-kurz,
  :root[data-theme="dunkel"] .ang-block h3,
  :root[data-theme="dunkel"] .ang-block h3 i,
  :root[data-theme="auto"] .ang-karte-bereich,
  :root[data-theme="auto"] .ang-bereich-name,
  :root[data-theme="auto"] .ang-bereich-zahl,
  :root[data-theme="auto"] .ang-bereich-kurz,
  :root[data-theme="auto"] .ang-block h3,
  :root[data-theme="auto"] .ang-block h3 i {
    color: color-mix(in oklab, var(--ang-farbe, var(--primary)), white 52%);
  }

  /* Der farbige Balken und die Flaechen bleiben, wie sie sind - dort
     traegt die Farbe keine Schrift und darf ihre volle Tiefe behalten. */
}

/* Die Illustrationsflaeche der Karte (--ang-hell) bleibt im Dunkeln
   bewusst hell: Sie ist das Bild des Angebots, und ein Bild wird nicht
   eingefaerbt. Damit die dunkle Schrift, die darauf sitzt, stimmt,
   bekommt dieser Bereich die hellen Tokens zurueck. */
:root[data-theme="dunkel"] .ang-karte-bild,
:root[data-theme="auto"] .ang-karte-bild {
  --ink: hsl(200, 20%, 20%);
  --ink-2: hsl(200, 12%, 38%);
  --ink-3: hsl(200, 10%, 44%);
  --surface: #ffffff;
  --white: #ffffff;
}


/* --- Nachtrag: die Bereichskacheln behalten ihren hellen Grund -------
 *
 * Die color-mix-Regel oben hellt die Bereichsfarbe auf, weil sie sonst
 * dunkel auf dunkel staende. Das stimmt fuer die Angebotskarten - ihr
 * Grund wird im Dunkelmodus dunkel.
 *
 * Fuer die zwoelf Bereichskacheln der Uebersicht stimmt es NICHT: Die
 * tragen ihren Grund als --ang-hell inline aus dem JavaScript, eine
 * feste helle Toenung, die im Dunkelmodus hell bleibt. Dort traf die
 * aufgehellte Schrift auf hellen Grund - gemessen 1,81:1.
 *
 * Gemessen, nicht vermutet: .ang-bereich hat im Dunkelmodus
 * rgb(251, 239, 245) als Hintergrund, also unveraendert die helle
 * Toenung.
 *
 * Diese Kacheln behalten deshalb die urspruengliche, dunkle
 * Bereichsfarbe - und mit ihr die hellen Tokens fuer alles, was sonst
 * noch darin steht. Eine helle Kachel auf dunklem Grund, wie das
 * Angebotsbild daneben.
 * -------------------------------------------------------------------- */
:root[data-theme="dunkel"] .ang-bereich,
:root[data-theme="auto"] .ang-bereich {
  --ink: hsl(200, 20%, 20%);
  --ink-2: hsl(200, 12%, 38%);
  --ink-3: hsl(200, 10%, 44%);
  --surface: #ffffff;
  --white: #ffffff;
  color: hsl(200, 20%, 20%);
}

/* Die color-mix-Aufhellung hier wieder zuruecknehmen. Die Auswahl ist
   um eine Ebene spezifischer als die Regel oben (.ang-bereich davor),
   gewinnt also. */
@supports (color: color-mix(in oklab, red, white)) {
  :root[data-theme="dunkel"] .ang-bereich .ang-bereich-name,
  :root[data-theme="dunkel"] .ang-bereich .ang-bereich-zahl,
  :root[data-theme="dunkel"] .ang-bereich .ang-bereich-kurz,
  :root[data-theme="auto"] .ang-bereich .ang-bereich-name,
  :root[data-theme="auto"] .ang-bereich .ang-bereich-zahl,
  :root[data-theme="auto"] .ang-bereich .ang-bereich-kurz {
    color: var(--ang-farbe, var(--primary));
  }

  /* .ang-bereich-kurz ist der Beschreibungssatz und laeuft ueber
     --ink-2, nicht ueber die Bereichsfarbe - der Token oben regelt ihn
     schon. Die Zeile hier stellt nur sicher, dass er nicht doch die
     aufgehellte Farbe erbt. */
  :root[data-theme="dunkel"] .ang-bereich .ang-bereich-kurz,
  :root[data-theme="auto"] .ang-bereich .ang-bereich-kurz {
    color: hsl(200, 12%, 38%);
  }
}

/* --- --success verfehlt AA auf dem zweiten Grund ---------------------
 *
 * .u-c-success ("Bestätigt (0)" im Schwarzen Brett) steht auf --bg-2 und
 * erreicht dort 4,18:1. Auf Weiss reicht der Bestandswert knapp (4,63),
 * auf dem cremefarbenen Grund nicht mehr.
 *
 * Durchgerechnet im Browser, Helligkeit fuer Helligkeit:
 *     38% (Bestand)  Weiss 4,63   --bg 4,45   --bg-2 4,18
 *     37%            Weiss 4,87   --bg 4,68   --bg-2 4,40
 *     36%            Weiss 5,07   --bg 4,87   --bg-2 4,58   <- gewaehlt
 *
 * 36% ist der hoechste Wert, der auf allen drei Gruenden besteht.
 * -------------------------------------------------------------------- */
:root {
  --success: hsl(122, 35%, 36%);
}


/* --- Die Grau-Hilfsklassen im Dunkelmodus ----------------------------
 *
 * In den utilities-*.css stehen 19 Klassen, die eine Textfarbe als
 * festen Hexwert setzen (.u-c-h333 { color: #333 !important }). Acht
 * davon sind dunkle Grautoene - im Dunkelmodus bleiben sie dunkel und
 * stehen damit auf dunklem Grund. Gemessen im Elternbereich:
 * "QR-Ausweis digital" mit .u-c-h333, Kontrast 1,15:1.
 *
 * Sie tragen alle !important, weil sie gegen Inline-Stile bestehen
 * muessen - eine Regel ohne !important koennte sie nicht ersetzen. Das
 * ist die zweite begruendete Ausnahme von der Regel im Dateikopf.
 *
 * Die Zuordnung folgt der Helligkeit des Originals: #000 und #111 waren
 * die kraeftigste Schrift und werden --ink, die mittleren Graus werden
 * --ink-2, die hellsten --ink-3. So bleibt die Abstufung erhalten, die
 * jemand gemeint hat, als er die Klasse gesetzt hat.
 *
 * NICHT angefasst: .u-c-hfff, .u-c-h999, .u-c-h9ca3af und die farbigen.
 * Weisse Schrift steht in dieser App auf farbigen Flaechen, die im
 * Dunkeln farbig bleiben - sie umzudrehen waere falsch.
 * -------------------------------------------------------------------- */
:root[data-theme="dunkel"] .u-c-h000,
:root[data-theme="dunkel"] .u-c-h111,
:root[data-theme="auto"] .u-c-h000,
:root[data-theme="auto"] .u-c-h111 {
  color: var(--ink) !important;
}

:root[data-theme="dunkel"] .u-c-h222,
:root[data-theme="dunkel"] .u-c-h333,
:root[data-theme="dunkel"] .u-c-h374151,
:root[data-theme="auto"] .u-c-h222,
:root[data-theme="auto"] .u-c-h333,
:root[data-theme="auto"] .u-c-h374151 {
  color: var(--ink) !important;
}

:root[data-theme="dunkel"] .u-c-h4b5563,
:root[data-theme="dunkel"] .u-c-h555,
:root[data-theme="dunkel"] .u-c-h666,
:root[data-theme="auto"] .u-c-h4b5563,
:root[data-theme="auto"] .u-c-h555,
:root[data-theme="auto"] .u-c-h666 {
  color: var(--ink-2) !important;
}

:root[data-theme="dunkel"] .u-c-h777,
:root[data-theme="auto"] .u-c-h777 {
  color: var(--ink-3) !important;
}

/* Die hellen Inseln drehen es wieder zurueck: Dort ist der Grund weiss
   geblieben (Papiervorschau, Angebotsbild, Bereichskachel), und dunkle
   Schrift ist dort richtig. Die Auswahl ist um einen Vorfahren
   spezifischer und gewinnt deshalb gegen die Regeln darueber - beide
   tragen !important, also entscheidet die Spezifitaet. */
:root[data-theme="dunkel"] .doc-preview-paper [class*="u-c-h"],
:root[data-theme="dunkel"] .print-area [class*="u-c-h"],
:root[data-theme="dunkel"] .print-only [class*="u-c-h"],
:root[data-theme="dunkel"] .ang-bereich [class*="u-c-h"],
:root[data-theme="dunkel"] .ang-karte-bild [class*="u-c-h"],
:root[data-theme="auto"] .doc-preview-paper [class*="u-c-h"],
:root[data-theme="auto"] .print-area [class*="u-c-h"],
:root[data-theme="auto"] .print-only [class*="u-c-h"],
:root[data-theme="auto"] .ang-bereich [class*="u-c-h"],
:root[data-theme="auto"] .ang-karte-bild [class*="u-c-h"] {
  color: hsl(200, 20%, 20%) !important;
}

/* Fuer den Druck braucht es hier NICHTS. Naheliegend waere
   `@media print { [class*="u-c-h"] { color: #333 !important } }` - das
   waere aber zu grob: Es traefe auch .u-c-hfff, also weisse Schrift auf
   farbiger Flaeche, und machte sie unlesbar.
   Es ist auch unnoetig: Die Regeln oben zeigen auf var(--ink) und
   var(--ink-2), und der Druckblock in Abschnitt 18 setzt genau diese
   Tokens fuer data-theme="dunkel"/"auto" auf Schwarz und Dunkelgrau
   zurueck. Wer im Dunkelmodus druckt, bekommt schwarze Schrift - ohne
   eine einzige weitere Regel. */


/* --- Der Chat (index.css:2855) ---------------------------------------
 *
 * Drei Sachen an einer Stelle:
 *
 * (1) `.chat-messages { background: #faf8f5 }` - eine feste helle Farbe.
 *     Im Dunkelmodus bleibt die Flaeche hell, waehrend der Leertext
 *     "Noch keine Nachrichten" seine Farbe vom Token holt und hell wird:
 *     gemessen 1,63:1.
 *
 * (2) `.chat-bubble.sent { background: var(--primary); color: #fff }`
 *     Die eigene Sprechblase. Die Flaeche folgt dem Token, die Schrift
 *     nicht - im Dunkeln wird das Pink hell, und Weiss darauf faellt auf
 *     2,4:1. Dieselbe Sache, die .btn-primary richtig macht: Dort steht
 *     `color: var(--surface)`, und genau das gehoert auch hierher.
 *
 * (3) `.chat-container { height: 450px }` - eine feste Hoehe, die nicht
 *     weiss, wie gross der Bildschirm ist. Auf einem iPhone SE (667px
 *     hoch, abzueglich Kopfzeile, unterer Leiste und Eingabefeld) bleibt
 *     fuer die Nachrichten weniger uebrig als die 450px fordern; auf
 *     einem iPad quer verschenkt sie die halbe Seite.
 *     Ersetzt durch eine Spanne: mindestens 320px, hoechstens so viel,
 *     wie nach Kopfzeile und unterer Leiste wirklich da ist.
 * -------------------------------------------------------------------- */
.chat-messages {
  background: var(--surface-2);
  border-color: var(--line);
}

.chat-bubble.sent {
  color: var(--surface);
}

.chat-bubble.sent .chat-time {
  color: var(--surface);
}

.chat-container {
  height: auto;
  min-height: 320px;
  /* 100dvh minus Kopfzeile, unterer Leiste, Eingabezeile und Raendern.
     dvh statt vh, weil die Adressleiste des Telefons mitwandert. */
  max-height: calc(100dvh - var(--header-h) - var(--bottombar-h) - 190px);
  /* Ohne eine Mindesthoehe faellt der Kasten auf dem Telefon im
     Querformat auf ein paar Zeilen zusammen - dann lieber scrollen. */
  height: max(320px, calc(100dvh - var(--header-h) - var(--bottombar-h) - 190px));
}

/* Am Rechner gibt es keine untere Leiste, dafuer mehr Hoehe. */
@media (min-width: 1025px) {
  .chat-container {
    height: max(420px, calc(100dvh - var(--header-h) - 230px));
    max-height: calc(100dvh - var(--header-h) - 230px);
  }
}

/* Nachtrag zum Papier-Block: Die Ueberschrift der Vertragsvorschau stand
   weiterhin in hellem Rosa auf weissem Blatt (gemessen rgb(238,170,206)
   = der Dunkelmodus-Wert). Sie zieht ihre Farbe ueber einen der alten
   Alias-Namen, nicht ueber --primary selbst - und die Aliase standen im
   Papier-Block nicht drin. Statt zu suchen, welcher es ist: alle, die
   auf die Marke zeigen. Sie kosten nichts und schliessen die Luecke
   vollstaendig. */
:root[data-theme="dunkel"] .doc-preview-paper,
:root[data-theme="auto"] .doc-preview-paper,
:root[data-theme="dunkel"] .print-area,
:root[data-theme="auto"] .print-area,
:root[data-theme="dunkel"] .print-only,
:root[data-theme="auto"] .print-only,
:root[data-theme="dunkel"] [id$="-print-view"],
:root[data-theme="auto"] [id$="-print-view"] {
  --primary-hover: hsl(328, 52%, 42%);
  --primary-light: hsl(328, 60%, 95%);
  --primary-dark:  hsl(328, 50%, 34%);
  --primary-text:  hsl(328, 50%, 34%);
  --border: hsl(36, 20%, 88%);
  --border-color: hsl(36, 20%, 88%);
  --text: hsl(200, 20%, 20%);
  --bg-primary: hsl(36, 40%, 98%);
  --bg-secondary: hsl(36, 30%, 95%);
  --accent:       hsl(24, 45%, 52%);
  --accent-light: hsl(24, 50%, 94%);
  --accent-dark:  hsl(24, 45%, 38%);
  --accent-text:  hsl(24, 50%, 32%);
  --danger-text: hsl(0, 60%, 34%);
  --success-text: hsl(122, 45%, 24%);
  --danger-light: hsl(0, 70%, 95%);
  --success-light: hsl(120, 40%, 93%);
}


/* ==========================================================================
   22. ZWEI KNOEPFE, DIE HINTER DER UNTEREN LEISTE LIEGEN

   Derselbe Fehler an zwei weiteren Stellen, nachdem er am 20.09.2026 fuer
   .eg-tag-fuss schon einmal behoben wurde (index.css:12008-12024): Eine
   Fussleiste klebt mit position:sticky bei bottom:0, die .app-bottombar
   liegt fest bei bottom:0 mit z-index 100 - also darueber.

   Der Schaden ist nicht "sieht schlecht aus", sondern: Die Leiste faengt
   den Tipp ab. Gemessen mit document.elementFromPoint auf der Mitte des
   Knopfes "Weiter" liefert der Browser .app-bottombar-mehr - wer das
   Stammdaten-Formular weiterblaettern will, oeffnet stattdessen das
   Mehr-Sheet. Auf 1024x768 UND auf 768x1024, also auf jedem Tablet.

   Beide bekommen hier denselben Weg wie .eg-tag-fuss: fest ueber der
   Leiste statt klebend dahinter, mit Schatten als Kante und z-index 90
   (unter Kopfzeile und Leiste bei 100, ueber dem Karteninhalt).
   ========================================================================== */

/* --- Stammdaten-Formular: "Zurück"/"Weiter" (index.css:12317) -------- */
@media (max-width: 1024px) {
  .formular-schritt-nav {
    position: fixed;
    left: 0;
    right: 0;
    bottom: calc(var(--bottombar-h) + env(safe-area-inset-bottom));
    margin-top: 0;
    padding: var(--s-3) var(--s-4);
    background: var(--surface);
    box-shadow: var(--shadow-2);
    border-top: 1px solid var(--line);
    z-index: 90;
  }

  /* Damit das letzte Feld des Formulars nicht dauerhaft hinter der
     Knopfzeile liegt, die jetzt darueber schwebt. */
  .formular-schritt {
    padding-bottom: calc(var(--s-7) * 2);
  }
}

/* --- Aufnahmebogen: die Speichern-Zeile (index.css:8052) ------------- */
@media (max-width: 1024px) {
  .intake-foot {
    bottom: calc(var(--bottombar-h) + env(safe-area-inset-bottom) + var(--s-2));
    z-index: 90;
    background: var(--surface);
    padding: var(--s-3);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-2);
  }
}

/* Das Kurzwort in der Seitenleiste ist nur fuer die schmale Tablet-Leiste
   gedacht (Abschnitt 23). Ueberall sonst steht der volle Name, und das
   Kurzwort bleibt unsichtbar - aria-hidden hat es ohnehin schon aus dem
   Baum der Vorlesehilfen genommen. */
.app-nav-kurz {
  display: none;
}


/* ==========================================================================
   23. DAS TABLET BEKOMMT SEINE BEREICHE ZURUECK

   Gemessen am 21.09.2026 auf 768x1024: Die Seitenleiste steht auf
   display:none (index.css:1317, @media max-width:1024px), die untere
   Leiste uebernimmt - mit vier Eintraegen und einem "Mehr". Die
   Verwaltung hat 22 Bereiche. Achtzehn davon liegen also hinter einem
   Sheet, das man erst oeffnen muss, auf einem Bildschirm, der
   1024 Pixel hoch ist und Platz fuer alle haette.

   Die Grenze bei 1024px stammt aus der Zeit, als "kein Rechner" gleich
   "Telefon" hiess. Ein iPad im Hochformat ist aber weder das eine noch
   das andere: zu breit fuer eine Daumenleiste, zu schmal fuer 248px
   Seitenleiste plus Inhalt.

   DIE ANTWORT: EINE SCHMALE LEISTE, DIE SICH AUFKLAPPEN LAESST

   Zwischen 768px und 1024px steht links eine 76px breite Leiste mit
   Symbol und Kurzwort - alle 22 Bereiche untereinander, sichtbar ohne
   einen einzigen Klick. Sie ist hoeher als der Bildschirm und scrollt;
   sichtbar sind rund sechzehn auf einmal. Das ist nicht perfekt, aber
   der Unterschied zu "vier und ein Sheet" ist der zwischen Suchen und
   Sehen.

   Wem das Kurzwort nicht reicht, klappt sie mit dem Knopf oben auf
   248px auf. Dann legt sie sich UEBER den Inhalt, statt ihn zu
   verdraengen - so springt beim Auf- und Zuklappen kein Text um, und
   der Inhalt behaelt seine Spaltenaufteilung. Die Wahl merkt sich die
   App (localStorage, siehe shell.js).

   Unter 768px bleibt alles, wie es war: untere Leiste und Mehr-Sheet.
   Auf einem Telefon ist das richtig - dort ist der Daumen unten.
   ========================================================================== */

:root {
  /* Breite der schmalen Leiste. 76px tragen ein 22px-Symbol, ein
     Kurzwort in --fs-xs und beidseitig Luft - und bleiben ueber der
     44px-Tippflaeche. */
  --sidebar-schmal: 84px;
}

@media (min-width: 768px) and (max-width: 1024px) {

  /* --- Das Raster: Leiste links statt Leiste unten ------------------ */
  .app-shell {
    grid-template-columns: var(--sidebar-schmal) 1fr;
  }

  .app-shell-ohne-nav {
    grid-template-columns: 1fr;
  }

  .app-sidebar {
    display: block;
    width: var(--sidebar-schmal);
    padding: var(--s-2) var(--s-1);
    /* Die Leiste ist laenger als der Bildschirm und scrollt. Damit das
       Scrollen dort bleibt und nicht die Seite mitnimmt: */
    overscroll-behavior: contain;
  }

  .app-bottombar {
    display: none;
  }

  .app-main {
    grid-column: 2;
    /* Der Abstand unten galt der Leiste, die es hier nicht mehr gibt. */
    padding-bottom: var(--s-5);
  }

  /* html hat wegen der unteren Leiste ein scroll-padding-bottom; ohne
     sie ist es nur noch unnoetiger Leerraum beim Tabben. */
  html {
    scroll-padding-bottom: var(--s-4);
  }

  /* --- Die Eintraege: Symbol oben, Kurzwort darunter ---------------- */
  .app-nav-item {
    flex-direction: column;
    gap: 2px;
    padding: var(--s-2) 2px;
    text-align: center;
    min-height: 52px;
    border-radius: var(--radius-md);
  }

  /* In der schmalen Leiste zeigt das Kurzwort, nicht der volle Name.
     Beide stehen im Markup (shell.js), die Anzeige entscheidet hier. */
  .app-nav-item:has(> .app-nav-kurz) > .app-nav-label {
    display: none;
  }

  /* width:100% und min-width:0 sind hier das Entscheidende, nicht der
     Umbruch selbst: .app-nav-item ist ein Flex-Container mit
     align-items:center (geerbt aus index.css:1023). Darin schrumpfen
     Kinder NICHT auf die Spaltenbreite - sie nehmen ihre Inhaltsbreite
     und laufen seitlich heraus. Gemessen am 21.09.2026: sieben von 22
     Beschriftungen ragten bis zu 26px ueber den Eintrag hinaus und
     wurden links und rechts abgeschnitten ("Schwarzes Bre",
     "tern-Zugaeng"). Erst mit voller Breite greift der Umbruch. */
  .app-nav-kurz,
  .app-nav-label {
    width: 100%;
    min-width: 0;
    font-size: var(--fs-xs);
    line-height: 1.15;

    /* DREI Dinge muessen hier zusammenkommen, sonst passiert nichts:
       (1) white-space: normal - der Bestand setzt an .app-nav-label
           nowrap (index.css:1048), weil die Beschriftung dort in EINER
           Zeile neben dem Symbol steht. In einer 84px-Spalte muss sie
           umbrechen duerfen.
       (2) width: 100% und min-width: 0 - .app-nav-item ist ein
           Flex-Container mit align-items:center (index.css:1023). Darin
           schrumpfen Kinder nicht auf die Spaltenbreite, sondern nehmen
           ihre Inhaltsbreite und laufen seitlich heraus. Gemessen:
           sieben von 22 Beschriftungen ragten bis zu 26px hinaus und
           wurden links und rechts angeschnitten.
       (3) overflow-wrap: anywhere - "Eingewoehnung" ist EIN Wort und
           breiter als die Spalte; ohne das bricht auch mit (1) nichts.

       Bewusst OHNE -webkit-line-clamp: Das braucht display:-webkit-box,
       und der computed style meldete dort flow-root - die Regel kam gar
       nicht an. Zwei Zeilen werden stattdessen ueber max-height
       begrenzt, was vorhersagbarer ist und in jedem Browser gilt. */
    /* display: block ist Pflicht - .app-nav-kurz steht ausserhalb dieser
       Media Query auf display:none, damit es am Rechner und im
       Telefon-Sheet nicht auftaucht. Ohne diese Zeile bleiben genau die
       drei Eintraege mit Kurzwort ganz ohne Beschriftung stehen (einmal
       passiert, gemessen: Protokoll, Wickeln, Elternwissen zeigten nur
       ihr Symbol). */
    display: block;
    white-space: normal;
    /* break-word statt anywhere: anywhere trennt mitten im Wort und ohne
       Trennstrich - aus "Stammdaten" wurde "Stammdat|en". break-word
       laesst der Silbentrennung (hyphens: auto, <html lang="de">) den
       Vortritt und greift erst, wenn auch die nicht weiterkommt. */
    overflow-wrap: break-word;
    hyphens: auto;
    max-height: 2.4em;
    overflow: hidden;
    text-overflow: clip;
  }

  /* --- Die Gruppen: Trennlinie statt Ueberschrift ------------------- */
  .app-nav-group-label {
    /* Der Gruppenname passt nicht in 76px und wuerde die Leiste noch
       laenger machen. Die Gruppe bleibt trotzdem erkennbar - als
       Trennlinie. Nicht display:none, damit Vorlesehilfen die
       Gliederung behalten. */
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .app-nav-group + .app-nav-group {
    margin-top: var(--s-2);
    padding-top: var(--s-2);
    border-top: 1px solid var(--line);
  }

  /* Die Zahl am Eintrag sitzt am Symbol, nicht am Zeilenende - in einer
     Spalte gibt es kein Zeilenende. */
  .app-nav-item .app-nav-badge:not([hidden]) {
    position: absolute;
    top: 4px;
    right: 50%;
    margin: 0 -20px 0 0;
    min-width: 16px;
    height: 16px;
    font-size: 11px;
  }

  .app-nav-item {
    position: relative;
  }

  /* --- Aufgeklappt: 248px ueber dem Inhalt -------------------------- */
  .app-shell.nav-offen .app-sidebar {
    position: fixed;
    top: var(--header-h);
    left: 0;
    width: var(--sidebar-w);
    height: calc(100dvh - var(--header-h));
    z-index: var(--z-dropdown);
    padding: var(--s-4) var(--s-3);
    box-shadow: var(--shadow-3);
    background: var(--surface);
  }

  .app-shell.nav-offen .app-nav-item {
    flex-direction: row;
    text-align: left;
    gap: var(--s-2);
    padding: 10px var(--s-3);
    min-height: 44px;
  }

  .app-shell.nav-offen .app-nav-item > .app-nav-kurz {
    display: none;
  }

  .app-shell.nav-offen .app-nav-item:has(> .app-nav-kurz) > .app-nav-label {
    display: block;
  }

  /* Aufgeklappt gilt wieder, was der Bestand vorsieht: eine Zeile neben
     dem Symbol, bei Bedarf mit Ellipse. */
  .app-shell.nav-offen .app-nav-label {
    font-size: var(--fs-md);
    max-height: none;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    overflow-wrap: normal;
    hyphens: manual;
  }

  .app-shell.nav-offen .app-nav-group-label {
    position: static;
    width: auto;
    height: auto;
    clip-path: none;
  }

  .app-shell.nav-offen .app-nav-group + .app-nav-group {
    border-top: 0;
    padding-top: 0;
    margin-top: var(--s-5);
  }

  .app-shell.nav-offen .app-nav-item .app-nav-badge:not([hidden]) {
    position: static;
    margin-left: auto;
  }

  /* Der Ruecken hinter der aufgeklappten Leiste: ein Tipp daneben
     schliesst sie. Das Markup setzt shell.js. */
  .nav-ruecken {
    position: fixed;
    inset: var(--header-h) 0 0 0;
    z-index: calc(var(--z-dropdown) - 1);
    background: rgb(30 20 10 / 0.3);
  }
}

/* Unter 768px und ab 1025px gibt es den Ruecken nicht - dort ist die
   Leiste entweder gar nicht da oder dauerhaft ausgeklappt. */
@media (max-width: 767px), (min-width: 1025px) {
  .nav-ruecken {
    display: none;
  }
}

/* Der Knopf zum Auf- und Zuklappen steht in der Kopfzeile und gilt nur
   fuer das Tablet: Auf dem Telefon gibt es keine Seitenleiste, am
   Rechner ist sie ohnehin offen. */
.nav-schalter {
  display: none;
}

@media (min-width: 768px) and (max-width: 1024px) {
  .nav-schalter {
    display: inline-flex;
  }
}


/* ==========================================================================
   24. DIE FORMEN DER LADESKELETTE

   Abschnitt 10 hat die Bausteine (.skelett, .skelett-zeile,
   .skelett-titel, .skelett-kreis, .skelett-karte). Hier stehen die
   Anordnungen, die src/skelett.js daraus baut - eine je Reiterart.
   ========================================================================== */
.skelett-seite {
  display: grid;
  gap: var(--s-4);
}

/* Die Kachelreihe der beiden Startseiten. */
.skelett-reihe {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 140px), 1fr));
  gap: var(--s-2);
}

.skelett-kachel {
  padding: var(--s-3) var(--s-4);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-1);
  min-height: 72px;
}

/* Eine Listenzeile: Kreis links, zwei Textzeilen daneben. */
.skelett-listenzeile {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  border-radius: var(--radius-md);
  background: var(--surface);
  box-shadow: var(--shadow-1);
}

.skelett-listentext {
  flex: 1;
  min-width: 0;
}

/* Karten nebeneinander - dieselben Haltepunkte wie .karten-raster. */
.skelett-raster {
  display: grid;
  gap: var(--s-4);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr));
}

/* Die Masse der einzelnen Balken. Als Klassen statt als style="" am
   Element: Das Projekt hat seine Inline-Stile abgebaut, und
   tools/design/inline_stile.py zaehlt jeden neuen mit. */
.skelett-wert {
  height: 1.6em;
  width: 38%;
}

.skelett-name {
  height: 1em;
  width: 42%;
}

.skelett-unterzeile {
  height: 0.85em;
  width: 64%;
  margin-top: 6px;
}
