/* ===========================================================================
   FINFLOW -- GESTALTUNGSSYSTEM

   Drei Ebenen, und die Reihenfolge ist die Reihenfolge der Kosten:

     1. Tokens in :root        -- Farbe, Schrift, Grad, Abstand. Eine Datei.
     2. Komponentenklassen     -- Form der Bauteile. Eine Regel, wirkt ueberall.
     3. Markup in index.php    -- verteilt ueber 3300 Zeilen.

   Ins PHP kommen ausschliesslich KLASSENNAMEN. Kein style=-Attribut, kein
   Hexcode, keine Pixelangabe. Daran haengt, ob ein spaeterer Wechsel des
   Erscheinungsbilds Arbeit an dieser Datei ist oder an der Anwendung.

   PAKET 15 hat die Tokens NEU BELEGT (Entwurf v4) und vier Bauteile
   hinzugefuegt: die Meldungsleiste, die einklappbare Navigation, das
   Filterblatt mit Reitern und die Bestandsleiste. Die Klassennamen der
   uebrigen Bauteile sind dieselben wie in Paket 13 -- deshalb sehen die noch
   nicht umgestellten Ansichten nicht kaputt aus, sondern nur anders.

   SEIT PAKET 24 IST DAS DIE EINZIGE DATEI. style.css ist geloescht; elf
   Pakete lang war sie die zweite Fassung derselben Sache. Was hier an
   manchen Stellen umstaendlich aussieht, stammt aus der Zeit, in der beide
   nebeneinander galten -- etwa `input.input` statt `.input`, weil
   `input[type=text]` als Attributselektor schwerer wiegt. Es bleibt stehen:
   Die Schreibweise ist harmlos, und sie erklaert, warum eine Regel so heisst,
   wie sie heisst.
   =========================================================================== */

:root {
  /* -- Farbe. Benannt nach ROLLE, nie nach Aussehen: eine dunkle Fassung
        belegt spaeter dieselben Token neu, und `--color-hellgrau`, das dann
        dunkel ist, waere eine Luege im Code. --------------------------- */
  --color-bg:           #f4f4f1;
  --color-surface:      #ffffff;
  --color-text:         #2f3336;

  --color-accent:       #46687f;   /* Verweise, bedienbare Symbole */
  --color-accent-700:   #5b7b96;   /* gefuellte Knoepfe, Marken, aktiver Reiter */
  --color-accent-100:   #b9cbd8;   /* Rahmen an blauen Bauteilen */
  --color-accent-50:    #f2f6f9;   /* Grund des Infotexts */

  --color-divider:      #efeee9;   /* Haarlinie zwischen Tabellenzeilen */
  --color-rahmen:       #c6c4bd;   /* Rahmen an Feldern und Knoepfen */
  --color-rahmen-leise: #ddd9ce;   /* Rahmen im Filterblatt */

  --color-neutral-200:  #eceae5;
  --color-neutral-300:  #d8d6cf;
  --color-neutral-600:  #6f7a80;   /* Feldbeschriftung, Nebentext */
  --color-neutral-700:  #8a8579;   /* Spaltenkoepfe, Legende */
  --color-neutral-800:  #4a545a;

  --color-navi:         #eceae5;
  --color-navi-aktiv:   #dedbd3;
  --color-navi-hover:   #e3e0d9;
  --color-blatt:        #edeade;   /* Grund des Filterblatts */
  --color-blatt-kopf:   #efece3;
  --color-blatt-fuss:   #f7f5f0;
  --color-tabellenkopf: #f2f5f6;

  /* Betrag. Gruen und Rot sind mit Paket 15 GEWOLLT -- Entscheidung des
     Auftraggebers, die den Beschluss aus Paket 13 aufhebt. */
  --color-plus:         #4f9366;
  --color-minus:        #a03b3b;

  /* Drei Toene fuer die Meldungsleiste. Je Ton drei Rollen: Zeichen, Grund,
     Rahmen -- sonst stehen die zusammengehoerenden Werte an drei Stellen. */
  --color-ok:           #4f9366;
  --color-ok-grund:     #f1f7f3;
  --color-ok-rahmen:    #bcd8c6;
  --color-warn:         #b8892b;
  --color-warn-grund:   #f6e9cf;
  --color-warn-rahmen:  #d9c48e;
  --color-warn-text:    #5a4718;
  --color-fehler:        #b0342f;
  --color-fehler-grund:  #fbf2f2;
  --color-fehler-rahmen: #d7b3b3;

  /* Rollen, keine Farben: Text AUF einer gefuellten Flaeche und der Grund
     eines Eingabefelds sind in einer dunklen Fassung zwei verschiedene
     Werte -- als gemeinsames `#fff` waeren sie dort beide falsch. */
  --color-auf-akzent:   #ffffff;
  --color-feld-grund:   #ffffff;

  /* -- Schrift -- */
  --font-heading: "Barlow Semi Condensed", system-ui, sans-serif;
  --font-body:    "Barlow", system-ui, sans-serif;
  --font-zahl:    "IBM Plex Mono", ui-monospace, monospace;

  /* Sechs Grade, nicht die zwoelf des Entwurfs: Die 9 px und 13,5 px dort
     sind Zeichenungenauigkeit, keine Absicht. */
  --text-xs:  11px;   /* Spaltenkoepfe, Marken, Kicker */
  --text-s:   12px;
  --text-m:   13px;
  --text-l:   14px;   /* Tabellenzeile, Felder, Grundtext */
  --text-xl:  15px;
  --text-marke: 19px;   /* Wortmarke */
  --titel:    30px;
  --titel-klein: 23px;  /* zweite Kopfzeile einer Ansicht -- `h2` im
                          Seitenkopf, z. B. "Vertragsgruppen" */
  --text-winzig: 10px;  /* das `i` am Feld, die Marke `A` -- unter --text-xs,
                           weil beide in einem Kreis bzw. Kaestchen von
                           15 px stehen und sonst anstossen */

  /* -- Abstand -- */
  --space-1: 3.4px;
  --space-2: 6.8px;
  --space-3: 10.2px;
  --space-4: 13.6px;
  --space-6: 20.4px;
  --space-8: 27.2px;
  /* Der Seitenabstand der Tabelle. Er sitzt NICHT am Kasten um die Tabelle,
     sondern in den aeusseren Zellen -- siehe .tabelle-rahmen. Deshalb braucht
     er einen eigenen Namen: Die Spaltenbreiten muessen ihn mitrechnen. */
  --rand-tabelle: var(--space-6);
  /* Hoehe der klebenden Meldungsleiste -- 0, solange keine ansteht.
     Der Tabellenkopf klebt um diesen Betrag tiefer, sonst schiebt er sich
     darunter. GERECHNET aus denselben Tokens, aus denen die Leiste gebaut
     ist (.meldung: Polster oben und unten plus die Hoehe des x-Knopfes plus
     die Trennlinie) -- eine zweite Zahl wuerde bei der ersten Aenderung an
     der Leiste auseinanderlaufen. */
  --meldung-hoehe: 0px;

  /* -- Mass. `--nav-breite` und `--seiten-breite` stehen an GENAU EINER
        Stelle im Einsatz, damit Einklappen und Breitenwechsel eine
        Neubelegung sind und kein Umbau. -------------------------------- */
  --nav-breite:     220px;
  --seiten-breite: 1480px;
  --kopf-hoehe:      37px;   /* Hoehe des klebenden Tabellenkopfs */
  --tipp-flaeche:    44px;   /* Mindesthoehe fuer alles, was angetippt wird */
  --zeilen-hoehe:    36px;   /* Hoehe einer Tabellenzeile -- die Dichte haengt
                                daran, siehe finflow-15-v1.md, Abnahme 53 */
}

/* Eingeklappte Navigation: EINE Neubelegung, kein zweites Layout. */
.navi-zu { --nav-breite: 58px; }
/* PHP gibt .meldungen nur bei anstehenden Meldungen aus, app.js setzt hidden,
   sobald die letzte weggeklickt ist. `:has` liest beides ohne eine Zeile
   JavaScript -- der Tabellenkopf rueckt nach oben, sowie die Leiste geht. */
html:has(.meldungen:not([hidden])) {
  --meldung-hoehe: calc(var(--space-8) + 2 * var(--space-2) + 1px);
}

/* GLOBAL, nicht mehr `.app *` (Paket 24). Solange style.css lebte, hat sie
   `* { box-sizing: border-box }` und den Koerper der Seite gestellt; hier
   stand nur die Fassung fuer die Anwendung, weil die Anmeldeseite drueben
   bedient wurde. Jetzt gibt es nur noch eine Datei -- und damit nur noch
   eine Stelle, an der die Grundlage steht. */
*,
*::before,
*::after { box-sizing: border-box; }

body {
  margin: 0;
  padding: 0;
  background: var(--color-neutral-200);
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: var(--text-l);
  line-height: 1.5;
}

/* style.css setzt `margin-top: 1rem` auf JEDES blanke <button>. Das war
   harmlos, solange nur .btn-Knoepfe im neuen System standen -- die setzen
   margin selbst zurueck. Paket 15 bringt ein Dutzend Knoepfe, die keine
   .btn sind: Reiter, Infoschalter, „leeren", das × der Meldung. Bei jedem
   davon schob der Erbwert 16 px Luft ueber das Bauteil.
   EINMAL hier zurueckgenommen, nicht zwoelfmal einzeln. */
/* ZWEI ERBLASTEN AUS `style.css` AN JEDEM BLANKEN <button>, hier einmal
   abgeraeumt statt an jedem Bauteil erneut:
   `margin-top: 1rem` setzte ueber jeden neuen Knopf 16 px Luft -- das kostete
   die Liste zwei Zeilen. `border-radius: 4px` rundete jeden ab, obwohl das
   Gestaltungssystem durchgehend eckig ist; sichtbar wurde das ueberall dort,
   wo ein Knopf eine Flaeche oder einen Rahmen traegt (Segmentschalter,
   Navigationsschalter) und an jedem Fokusring.
   Die eine gewollte Ausnahme ist der runde Infoschalter; seine Regel steht
   deshalb als `.app .info-schalter` und gewinnt gegen diese hier. */
.app button { margin: 0; border-radius: 0; }

.app h1, .app h2, .app h3 {
  font-family: var(--font-heading);
  font-weight: 600;
  margin: 0;
}
.app a { color: var(--color-accent); }

table.liste .num,
.blaetter .num,
.anmelde-blatt .num {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  white-space: nowrap;
  text-align: right;
}
/* ----------------------------------------------------------- Betrag und Maske
   JEDER Betrag der Anwendung kommt aus betrag() und sieht so aus:

       <span class="betrag pos"><span class="betrag-zahl">12,30 EUR</span>
                                <span class="betrag-maske">••• EUR</span></span>

   DER DISKRETIONSMODUS SCHALTET MIT `display`, NICHT MIT `color`.
   Bis Paket 23 stand in style.css `body.discreet .betrag { color: transparent }`
   plus ein `::after` mit drei Sternen. Das wog (0,2,1) und verlor gegen die
   beiden Regeln weiter unten in DIESER Datei -- `.kopf-zahlen .summe .betrag`
   und `table.liste td.num .betrag`, beide `color: inherit`, beide schwerer.
   Der Betrag blieb also sichtbar, waehrend drei Sterne ueber seinen ersten
   Zeichen lagen. Ein Verdeckungsmechanismus, der von der Kaskade abhaengt,
   verliert frueher oder spaeter -- und er verliert lautlos.

   `display` kann keine Farbregel ueberstimmen. Und weil die Maske ihre Farbe
   SELBST setzt, erbt sie das Vorzeichenrot nicht: Eine rote Maske wuerde
   verraten, was sie verdeckt. */
.betrag { font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1; white-space: nowrap; }
.betrag.neg { color: var(--color-minus); }
.betrag.pos { color: var(--color-plus); }
.betrag-maske { display: none; }
body.discreet .betrag-zahl  { display: none; }
body.discreet .betrag-maske { display: inline; color: var(--color-text);
                              letter-spacing: .05em; user-select: none; }

.kicker {
  font-family: var(--font-heading);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--color-neutral-700);
}
.nur-vorlesen {                        /* sichtbar nur fuer Screenreader */
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}
/* WARUM DAS NOETIG IST -- und es ist kein Beiwerk:
   `.nur-vorlesen` ist `position: absolute`. Ohne positionierten Vorfahren ist
   sein umgebender Block der WURZELRAHMEN -- und damit entkommt es jeder
   Klammer darum, auch `overflow: hidden`. In der Tabelle steht so ein Span in
   jedem `+`, in jedem Stift und im Kopf der Auswahlspalte, und die Tabelle ist
   916 px breit: Bei 390 px Fensterbreite scrollte deshalb die SEITE um 439 px
   seitlich, obwohl jeder einzelne Vorfahre 390 px breit war und
   `.tabelle-rahmen` bereits `overflow-x: auto` trug. Gemessen; body und .app
   meldeten brav 390, allein das <html> meldete 829.

   Paket 13 hatte das Problem nicht, weil dort kein `.nur-vorlesen` INNERHALB
   der Tabelle stand. Die Regel hier deckt beide Faelle ab: Was in einem
   dieser Kaesten steht, wird von ihm geklemmt. */
.app, .tabelle-rahmen, .navi-punkt, .meldung, table.liste td, table.liste th { position: relative; }
/* Ausnahme: der klebende Tabellenkopf. `sticky` muss `relative` ueberschreiben
   und steht deshalb NACH dieser Regel -- siehe unten bei table.liste th. */

/* -------------------------------------------------------------------- Rahmen */

/* DIE SEITE SCROLLT (Paket 17) -- UMGEKEHRT ZU PAKET 13 UND 15.
   Dort stand: nicht die Seite scrollt, sondern .app-inhalt in seinem eigenen
   Kasten. Begruendet war das mit dem klebenden Tabellenkopf, der einen festen
   Abstand von oben braucht, und mit einer Kopfzeile, die je nach Breite
   59/100/178 px hoch war.

   BEIDE GRUENDE SIND WEG. Die Kopfzeile gibt es seit Paket 15 nicht mehr, ihr
   Inhalt steht in der Navigation. Und der Tabellenkopf klebte nie -- siehe
   unten bei .tabelle-rahmen.

   Geblieben war der Preis: Bei 1920 px war das Mausrad auf 34 % der
   Fensterbreite wirkungslos, bei 2560 px auf 51 %, weil alles ausserhalb des
   Scrollers tot ist. Schwerer noch: Bild-ab, Ende und Leertaste bewegten gar
   nichts, solange der Fokus nicht im Inhalt stand -- ein innerer Scroller ist
   fuer die Tastatur nur mit Fokus erreichbar. Und die Bildlaufleiste sass
   221 px vom Fensterrand entfernt.

   Jetzt scrollt das Dokument: Rad und Tastatur wirken ueberall, die
   Bildlaufleiste sitzt am Rand. Die Breitenbegrenzung wandert von hier an die
   INHALTSBAHN (.meldungen und .app-inhalt), die Navigation wird eine feste
   Schiene. */
.app {
  background: var(--color-bg);
}

/* Zwei klebende Ebenen -- Meldungsleiste und Tabellenkopf. Ohne
   scroll-padding landet alles, wohin die Seite springt, DARUNTER: ein
   Sprungziel ist unsichtbar, ein Knopf nicht anklickbar.
   AM DOKUMENT, nicht mehr am Scroller: Seit Paket 17 ist das Dokument der
   Scroller, und scroll-padding wirkt nur an dem. */
html { scroll-padding-top: calc(var(--meldung-hoehe) + var(--kopf-hoehe) + var(--space-6)); }

/* scroll-padding allein genuegt nicht: Es greift beim Sprunganker, nicht
   wenn der Browser ein Element von sich aus in den Blick holt -- beim
   Tabben etwa, oder wenn ein Formularfehler den Fokus setzt. */
.app-inhalt :is(a, button, input, select, textarea, summary, [tabindex]) {
  scroll-margin-top: calc(var(--meldung-hoehe) + var(--kopf-hoehe) + var(--space-6));
  scroll-margin-bottom: var(--space-6);
}

.app-koerper { display: block; }

/* DIE INHALTSBAHN -- Meldungsleiste und Inhalt teilen sie sich.
   Zentriert im FENSTER (Entscheidung des Auftraggebers), nicht in dem, was
   rechts der Schiene uebrig bleibt.

   DAS max() IST DER GANZE PUNKT. Fenstermitte und feste Schiene vertragen
   sich erst ab 1920 px: Dort ist (1920 - 1480) / 2 = 220 genau die
   Schienenbreite. Darunter laege die Bahn zum Teil UNTER der Schiene. Das
   max() schiebt sie dann nach rechts, statt sie zu verdecken -- sie ist dort
   nicht mehr fenstermittig, aber vollstaendig sichtbar. Oberhalb 1920 px ist
   sie exakt mittig, und genau dort war die tote Zone am groessten.

   --nav-breite traegt das Einklappen auf 58 px bereits; die Bahn folgt ohne
   eigene Regel. */
.meldungen,
.app-inhalt {
  max-width: var(--seiten-breite);
  margin-left: max(var(--nav-breite), calc((100% - var(--seiten-breite)) / 2));
  margin-right: auto;
  /* Ohne Kanten stuende der Inhalt bei 2560 px als Flaeche im Grau. Die
     Linien sassen bis Paket 16 an .app und wandern mit der Begrenzung mit. */
  border-left: 1px solid var(--color-neutral-300);
  border-right: 1px solid var(--color-neutral-300);
}
/* Die Leiste klebt am Fensterrand, nicht an einem Kasten -- und liegt ueber
   dem Tabellenkopf, der seinen Abstand von ihr nimmt (siehe table.liste th). */
.meldungen { position: sticky; top: 0; z-index: 3; background: var(--color-bg); }
/* DIE BAHN TRAEGT DEN GRUND UND REICHT BIS ZUM FENSTERBODEN.
   Ohne beides hoerte das Blatt dort auf, wo der Inhalt aufhoert: Bei "Kein
   Treffer" nach 435 von 900 px, bei drei Zeilen nach 601 -- darunter der
   graue Grund der Seite, ohne die seitlichen Kanten. Die Seite sah aus, als
   waere sie in der Mitte abgeschnitten.

   `100dvh - --meldung-hoehe`, weil die Meldungsleiste als Geschwister
   darueber liegt und die Bahn um ihre Hoehe nach unten schiebt. Steht keine
   Meldung an, ist der Wert 0 und die Rechnung bleibt richtig. */
.app-inhalt {
  min-width: 0;
  min-height: calc(100dvh - var(--meldung-hoehe));
  /* NICHT weiss, sondern der Ton des Blattfusses. Weiss liess die
     Blaetterleiste als Streifen im Nichts stehen -- sie hat denselben Ton,
     und unter ihr ging es weiss weiter. So schliesst sie den Tisch ab und
     der Grund laeuft darunter durch. Die Tabelle bleibt weiss und liegt als
     Blatt darauf. */
  background: var(--color-blatt-fuss);
}

/* Die Schrittfolge unter Werkzeuge (Paket 37): drei Schritte, von denen der
   zweite an einer anderen Stelle der Anwendung liegt. Eine nummerierte Liste
   statt dreier Absaetze -- die Reihenfolge IST die Aussage. */
.schrittfolge {
  margin: var(--space-3) 0;
  padding-left: var(--space-5);
}
.schrittfolge li { margin-bottom: var(--space-1); }

/* ------------------------------------------------------------- Navigation --
   Die Kopfzeile aus Paket 13 ist ENTFALLEN. Wortmarke, Benutzer, Diskretion,
   Sperre und Abmelden stehen jetzt in der Navigation -- oben die Marke, unten
   der Fuss. Das ist nicht Umstellen um des Umstellens willen: Die Kopfzeile
   war 37 bis 178 px hoch (sie brach um) und lag QUER ueber der Seite, also
   ueber der Tabelle. Was selten angefasst wird, gehoert nicht dorthin.
   -------------------------------------------------------------------------- */

/* FESTE SCHIENE ueber die volle Fensterhoehe (Paket 17). `top: 0; bottom: 0`
   statt einer Hoehenangabe -- das folgt dem sichtbaren Ausschnitt von selbst,
   auch auf Handy-Browsern mit ein- und ausfahrender Leiste, und braucht kein
   zweites dvh.

   .navi-punkte traegt `overflow-y: auto`: Passen dreizehn Eintraege nicht ins
   Fenster, scrollt die Schiene in sich. Passen sie, laeuft das Rad ueber der
   Schiene an das DOKUMENT weiter -- Scroll-Chaining, die Vorgabe des Browsers.
   Genau deshalb darf hier niemals `overscroll-behavior: contain` stehen: Das
   waere die eine Zeile, die "ueber der Navigation scrollen" wieder kaputt
   macht. */
.app-navi {
  position: fixed; left: 0; top: 0; bottom: 0;
  width: var(--nav-breite);
  z-index: 4;
  display: flex; flex-direction: column;
  background: var(--color-navi);
  border-right: 1px solid var(--color-neutral-300);
  overflow: hidden;
}

.navi-marke {
  flex: none;
  display: flex; align-items: center; gap: var(--space-2);
  min-height: 57px;
  padding: 0 var(--space-3);
  border-bottom: 1px solid var(--color-neutral-300);
}
.navi-marke .wortmarke {
  flex: 1; min-width: 0;
  font-family: var(--font-heading); font-weight: 700;
  font-size: var(--text-marke); letter-spacing: .02em;
  white-space: nowrap; overflow: hidden;
}
/* DIE FASSUNG NEBEN DEM NAMEN -- klein, zurueckgenommen, aber lesbar. Sie
   sagt, welche Fassung der Anwendung man vor sich hat; ohne sie sind zwei
   Fassungen nebeneinander auf demselben Rechner nicht zu unterscheiden.
   KEINE Marke wie `.kennung`: Die Instanz ist eine Warnung ("das sind nicht
   die echten Daten"), die Fassung ist eine Angabe. */
.navi-marke .wortmarke .fassung {
  margin-left: var(--space-1);
  font-family: var(--font-body);
  font-size: var(--text-s); font-weight: 400; letter-spacing: 0;
  color: var(--color-neutral-600);
}
.navi-zu .navi-marke { justify-content: center; padding: 0; }
.navi-zu .navi-marke .wortmarke { display: none; }
.navi-marke .kennung {
  font-family: var(--font-heading);
  font-size: var(--text-xs); letter-spacing: .06em; text-transform: uppercase;
  padding: 1px var(--space-2);
  background: var(--color-warn-grund);
  border: 1px solid var(--color-warn-rahmen);
  color: var(--color-warn-text);
}
.navi-zu .navi-marke .kennung { display: none; }

.navi-schalter {
  flex: none;
  width: 28px; height: 28px; padding: 0; margin: 0;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--color-rahmen);
  background: var(--color-bg); color: var(--color-neutral-800);
  cursor: pointer;
}
.navi-schalter:hover { background: var(--color-surface); border-color: var(--color-accent-700); color: var(--color-accent-700); }
.navi-schalter svg { width: 15px; height: 15px; }
/* Ohne JavaScript gibt es nichts zu klappen -- dann ist der Schalter ein
   Knopf, der nichts tut. app.js macht ihn sichtbar. */
.navi-schalter[hidden] { display: none; }

.navi-punkte { flex: 1; min-height: 0; overflow-y: auto; padding: var(--space-2) 0 var(--space-4); }

/* Die Beschriftung ist seit Paket 35 ein KNOPF und sieht trotzdem aus wie
   vorher: kein Rahmen, kein Grund, dieselbe Schrift. Was sie tut, sagt der
   Pfeil -- nicht die Form des Elements. */
.navi-gruppe {
  display: flex; align-items: center; gap: var(--space-2);
  width: 100%;
  padding: var(--space-4) var(--space-4) var(--space-1);
  background: none; border: 0; text-align: left;
  font-family: var(--font-heading);
  font-size: var(--text-xs); font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--color-neutral-700);
  white-space: nowrap; overflow: hidden;
  cursor: pointer;
}
.navi-gruppe .wort { flex: 1; min-width: 0; overflow: hidden; }
.navi-gruppe .pfeil { flex: none; width: 13px; height: 13px; }
.navi-gruppe[aria-expanded="false"] .pfeil { transform: rotate(-90deg); }
.navi-gruppe:hover:not([disabled]) { color: var(--color-accent-700); }
/* Ohne JavaScript bleibt der Knopf gesperrt -- aber NICHT ausgegraut: Er ist
   dann eine Ueberschrift, und eine Ueberschrift sieht aus wie eine. app.js
   gibt ihn frei. */
.navi-gruppe[disabled] { cursor: default; opacity: 1; color: var(--color-neutral-700); }
.navi-gruppe[disabled] .pfeil { display: none; }

/* Der Block, den die Beschriftung zuklappt. Ein eigener Container ist noetig,
   weil "alle Eintraege bis zur naechsten Ueberschrift" kein Selektor ist. */
.navi-block { display: block; }

/* ZUGEKLAPPT. Die Klasse steht am <html> und kommt aus fruehstart.js, VOR dem
   ersten Malen -- sonst klappten die Bloecke bei jedem Seitenwechsel sichtbar
   zusammen (derselbe Weg wie `navi-zu` aus Paket 15).
   Drei Bloecke, drei Selektoren: Der Preis dafuer, dass der Zustand vor dem
   ersten Malen steht. Ein vierter Block braucht hier eine Zeile.

   Zwei Ausnahmen stecken im Selektor:
   `:not(.navi-zu)`   -- in der 58-px-Schiene ist die Ueberschrift nur noch
                         eine Trennlinie; verschwundene Symbole haetten dort
                         keinen sichtbaren Grund und keinen Weg zurueck.
   `:not(.hier-drin)` -- der Block mit der markierten Seite steht offen.
   Und die Media Query: Unter 720 px ist die Navigation eine umbrechende
   Leiste ueber dem Inhalt. Sie ist nicht zu lang, es gibt nichts zu klappen. */
@media (min-width: 721px) {
  html.navi-grp-zu-arbeiten:not(.navi-zu)  #navi-grp-arbeiten:not(.hier-drin),
  html.navi-grp-zu-auswerten:not(.navi-zu) #navi-grp-auswerten:not(.hier-drin),
  html.navi-grp-zu-verwalten:not(.navi-zu) #navi-grp-verwalten:not(.hier-drin) { display: none; }
}
/* Eingeklappt bleibt die Gruppe als TRENNLINIE stehen. Ihr Wort waere auf
   58 px ohnehin nicht zu lesen, aber die Gliederung in drei Bloecke ist die
   halbe Orientierung -- sie ganz wegzulassen macht aus dreizehn Symbolen
   eine Reihe ohne Anfang. */
.navi-zu .navi-gruppe {
  padding: var(--space-3) var(--space-3) 0;
  font-size: 0;
  border-top: 1px solid var(--color-neutral-300);
  margin-top: var(--space-2);
}
/* Eingeklappt gibt es nichts zu klappen: kein Pfeil, kein Zeiger. app.js
   laesst den Klick in diesem Zustand ebenfalls liegen -- der Zeiger allein
   waere nur ein Versprechen. */
.navi-zu .navi-gruppe { cursor: default; }
.navi-zu .navi-gruppe .pfeil { display: none; }

.navi-punkt {
  position: relative;
  display: flex; align-items: center; gap: var(--space-2);
  min-height: var(--tipp-flaeche);
  padding: var(--space-2) var(--space-4);
  text-decoration: none; color: var(--color-text);
  font-family: var(--font-heading); font-size: var(--text-xl);
  border-left: 3px solid transparent;
  white-space: nowrap;
}
.navi-punkt:hover { background: var(--color-navi-hover); }
.navi-punkt.hier  {
  background: var(--color-navi-aktiv);
  border-left-color: var(--color-accent-700);
  font-weight: 600;
}
.navi-punkt svg { flex: none; width: 17px; height: 17px; color: var(--color-neutral-600); }
.navi-punkt.hier svg { color: var(--color-accent); }
.navi-punkt .wort { flex: 1; min-width: 0; overflow: hidden; }
.navi-punkt .zahl {
  font-family: var(--font-body);
  font-size: var(--text-s); color: var(--color-neutral-600);
  font-variant-numeric: tabular-nums;
}
.navi-punkt .zahl.warn { color: var(--color-warn); font-weight: 600; }

/* Eingeklappt: nur das Symbol, und die Zahl wird zur MARKE oben rechts.
   Ohne sie waere die Information, dass 114 Posten liegen, im eingeklappten
   Zustand verschwunden -- und genau das verlangt Paket 13, Abnahme 8. */
.navi-zu .navi-punkt { justify-content: center; padding: var(--space-2) 0; }
.navi-zu .navi-punkt .wort { display: none; }
.navi-zu .navi-punkt .zahl {
  position: absolute; top: 6px; right: 11px;
  width: 6px; height: 6px; padding: 0;
  border-radius: 50%;
  background: var(--color-accent-700);
  font-size: 0; color: transparent;      /* die Zahl steht im title */
  overflow: hidden;
}
.navi-zu .navi-punkt .zahl.warn { background: var(--color-warn); }

.navi-fuss {
  flex: none;
  padding: var(--space-3);
  border-top: 1px solid var(--color-neutral-300);
  display: flex; flex-direction: column; gap: var(--space-2);
  font-size: var(--text-m); color: var(--color-neutral-600);
}
.navi-fuss .marken { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.navi-zu .navi-fuss { align-items: center; padding: var(--space-3) 0; }
.navi-zu .navi-fuss .marken { flex-direction: column; }

/* Ein quadratisches Zeichen -- benutzt vom Diskretionsschalter, von den
   Marken (Gast, Demo) und vom Abmelden-Knopf. Eingeklappt schrumpft es. */
.navi-zeichen {
  width: 30px; height: 30px; flex: none; padding: 0; margin: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--color-rahmen);
  background: var(--color-bg); color: var(--color-accent);
  font-family: var(--font-body); font-size: var(--text-s); font-weight: 600;
  cursor: pointer;
}
.navi-zu .navi-zeichen { width: 26px; height: 26px; }
.navi-zeichen svg { width: 15px; height: 15px; }
button.navi-zeichen:hover { background: var(--color-surface); border-color: var(--color-accent-700); }
.navi-zeichen.wer { color: var(--color-text); cursor: default; }
.navi-zeichen.an  { background: var(--color-accent-700); border-color: var(--color-accent-700); color: var(--color-auf-akzent); }
.navi-zeichen.nurlesen {
  border-color: var(--color-fehler-rahmen); background: var(--color-fehler-grund);
  color: var(--color-fehler); cursor: default;
}
.navi-zeichen.demo {
  border-color: var(--color-warn-rahmen); background: var(--color-warn-grund);
  color: var(--color-warn); cursor: default;
}

/* Die Sperre: "Sperre nach [90] Min." -- das Zahlenfeld bleibt ein FELD.
   Der Entwurf zeigt die 90 als Text, weil er das Zahnrad "Einstellungen"
   daneben hat; das gibt es nicht (finflow-15-v1.md, "Nicht in diesem
   Paket"). Ein Wert, der angezeigt, aber nicht aenderbar ist, waere ein
   Rueckschritt. */
.navi-sperre {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-1);
  font-size: var(--text-m);
}
.navi-sperre input.input {
  width: 4.5ch; min-height: 0; padding: 1px var(--space-1);
  font-size: var(--text-m); text-align: right;
}
.navi-sperre label { margin: 0; font-weight: 400; font-size: var(--text-m); }
.navi-zu .navi-sperre { display: none; }

.navi-fuss form.inline { display: block; margin: 0; }
.navi-fuss .btn-abmelden { width: 100%; justify-content: center; }
.navi-zu .navi-fuss .btn-abmelden { display: none; }
.navi-zu .navi-fuss .abmelden-zeichen { display: inline-flex; }
.navi-fuss .abmelden-zeichen { display: none; }

/* --------------------------------------------------------- Meldungsleiste --
   EINE Zeile, immer. Vorher standen drei Meldungen an drei Stellen: die
   HTTPS-Warnung vor der Navigation, Fehler und Hinweis dahinter.

   UEBER DEM GANZEN INHALT UND KLEBEND -- Entscheidung des Auftraggebers.

   MIT PAKET 17 LIEGT SIE IN DER INHALTSBAHN, nicht mehr quer ueber alles: Die
   Navigation ist eine feste Schiene geworden, die Leiste steht rechts daneben
   und ist so breit wie der Inhalt. Dass sie frueher auch ueber der Navigation
   lag, war eine Nebenwirkung der alten Geometrie, keine Festlegung.

   Klebend ist sie jetzt mit `position: sticky` (oben bei der Bahn), weil das
   Dokument scrollt und ein `flex: none` nichts mehr haelt.

   Der Preis ist eine Tabellenzeile, solange eine Meldung ansteht. Mit der
   letzten verschwindet die Leiste und die Zeile kommt zurueck.
   -------------------------------------------------------------------------- */

.meldung {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-3);
  margin: 0;
  padding: var(--space-2) var(--space-6);
  border-bottom: 1px solid var(--color-rahmen-leise);
  font-family: var(--font-heading);
  font-size: var(--text-l);
}
.meldung[hidden] { display: none; }
.meldung .zeichen {
  flex: none;
  width: 18px; height: 18px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  font-family: var(--font-body); font-size: var(--text-s); font-weight: 700;
  color: var(--color-auf-akzent);
}
.meldung .text { flex: 1; min-width: 0; }
.meldung .weitere {
  flex: none; padding: 0 var(--space-2); margin: 0;
  border: 0; background: none;
  font-family: inherit; font-size: var(--text-m);
  color: inherit; text-decoration: underline; cursor: pointer;
}
.meldung .zu {
  flex: none;
  width: var(--space-8); height: var(--space-8); padding: 0; margin: 0;
  display: flex; align-items: center; justify-content: center;
  border: 0; background: none;
  font-size: var(--text-xl); line-height: 1;
  color: inherit; cursor: pointer;
}
.meldung .zu[hidden] { display: none; }

.meldung.ist-fehler  { background: var(--color-fehler-grund); border-bottom-color: var(--color-fehler-rahmen); color: var(--color-fehler); }
.meldung.ist-fehler  .zeichen { background: var(--color-fehler); }
.meldung.ist-warn    { background: var(--color-warn-grund);   border-bottom-color: var(--color-warn-rahmen);   color: var(--color-warn-text); }
.meldung.ist-warn    .zeichen { background: var(--color-warn); }
.meldung.ist-ok      { background: var(--color-ok-grund);     border-bottom-color: var(--color-ok-rahmen);     color: var(--color-ok); }
.meldung.ist-ok      .zeichen { background: var(--color-ok); }

/* --------------------------------------------------------------- Seitenkopf */

.seiten-kopf {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: var(--space-3) var(--space-4);
  /* SYMMETRISCH GEPOLSTERT, und das ist der Unterschied zwischen "mittig" und
     "rechnerisch mittig": Bis zum 13.09.2026 stand hier `var(--space-4)
     var(--space-6) 0` -- oben 13,6 px, unten 0. Die Ueberschrift sass damit mit
     19,1 px Luft oben und 5,5 px unten in ihrer Zeile. In der Arbeitsliste
     faellt das nicht auf, weil die Bestandsleiste den Raum fast fuellt; neben
     einer 33 px hohen Ueberschrift sieht man es sofort. Gemeldet vom
     Auftraggeber.

     6,8 px oben UND unten halten die Gesamthoehe bei 58 px -- die Zeilendichte
     der Arbeitsliste haengt daran (pruef_system.js, Punkt 23, Schwelle 10). */
  padding: var(--space-2) var(--space-6);
  /* EINE HOEHE FUER JEDEN SEITENKOPF. Ohne sie richtet sich die Zeile nach
     ihrem groessten Kind, und das ist je Ansicht ein anderes: die
     Bestandsleiste der Arbeitsliste ist 35 px hoch, ein `.btn` 44 --
     gemessen 49 gegen 58 px. Beim Blaettern durch die Ansichten sprang die
     Ueberschrift. Die 44 px sind nicht verhandelbar, sie sind die
     Beruehrflaeche (pruef_system.js, Punkt 32); also richtet sich der Kopf
     nach ihnen, auch wo gerade kein Knopf steht.

     DIE POLSTERUNG GEHOERT IN DIE RECHNUNG: `box-sizing: border-box` gilt
     global, `min-height` meint also die GESAMThoehe. Mit blossem
     `var(--tipp-flaeche)` tat die Regel gar nichts -- der Kopf war mit 49 px
     ohnehin ueber 44. */
  min-height: calc(var(--tipp-flaeche) + 2 * var(--space-2));
}
/* ZWEI STUFEN, EIN KOPF. Eine Ansicht kann einen zweiten Abschnitt haben --
   "Vertragsgruppen" unter "Vertraege" --, und der bekommt denselben Kopf,
   nur eine Stufe kleiner. Bis zum 14.09.2026 galt die Hoehe nur fuer `h1`;
   der zweite Kopf war dadurch niedriger und anders zentriert als der erste,
   obwohl er im Entwurf danebensteht. */
.seiten-kopf h1,
.seiten-kopf h2 { font-size: var(--titel); letter-spacing: -.005em; line-height: 1.1; }
.seiten-kopf h2 { font-size: var(--titel-klein); }

/* EIN NEUER ABSCHNITT MUSS ALS SOLCHER ZU ERKENNEN SEIN. Der zweite Kopf
   folgte bisher unmittelbar auf die Fusszeile der Liste darueber, und die
   beiden Toene liegen zu dicht beieinander, um eine Grenze zu sein:
   Seitenkopf #f4f4f1 auf durchsichtigem Grund, Fusszeile #f7f5f0. "Man soll
   erkennen, dass Vertragsgruppen ein weiterer Bereich ist" -- gemeldet vom
   Auftraggeber am 14.09.2026.

   GETRENNT WIRD MIT ABSTAND UND EINEM STRICH AM BLOCK DARUEBER, nicht mit
   einem Rahmen am Kopf: Ein Rahmen waere 1 px Hoehe, und dass jeder
   Seitenkopf GENAU gleich hoch ist, ist eine gepruefte Zusage
   (pruef_rahmen.js K1, K9). Der Strich gehoert ohnehin dorthin -- er
   schliesst die Liste ab, statt den naechsten Abschnitt einzuleiten. */
.seiten-kopf ~ .seiten-kopf { margin-top: var(--space-8); }
.blaetter:has(+ .seiten-kopf),
.tabelle-rahmen:has(+ .seiten-kopf) { border-bottom: 1px solid var(--color-neutral-300); }
.seiten-kopf .meta { color: var(--color-neutral-600); font-size: var(--text-m); }

/* --------------------------------------------------------- Arbeitsleiste */

/* WAS MAN HIER TUN KANN -- eine eigene Zeile fuer die Knoepfe, unter dem
   Einleitungstext und ueber der Tabelle.
   Bis zum 14.09.2026 sassen sie als `.seiten-kopf .knopfe` rechts im Kopf.
   Das ging, solange rechts sonst nichts stand. Seit die Ansichten dort ihre
   Kopfzahlen tragen, ist der Platz belegt: Zahlen und Knoepfe nebeneinander
   waeren in der schmalsten Ansicht ein Umbruch und in der breitesten eine
   Zeile mit drei Schwerpunkten. Der Entwurf des Auftraggebers
   (finflow-19-v1-entwurf-vertraege.png) trennt beides, und die Reihenfolge
   darin ist die richtige: Der Text sagt, WAS die Ansicht ist, die Leiste,
   was man darin TUN kann.

   GRUND UND RAHMEN WIE DER TABELLENKOPF: Die Leiste gehoert optisch zur
   Tabelle darunter, nicht zum Text darueber.

   DIESELBE HOEHENRECHNUNG WIE DER SEITENKOPF -- die 44 px Beruehrflaeche
   (pruef_system.js, Punkt 32) gelten hier genauso, und die Polsterung zaehlt
   wegen `box-sizing: border-box` mit hinein. */
.arbeits-leiste {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: var(--space-3) var(--space-4);
  padding: var(--space-2) var(--space-6);
  min-height: calc(var(--tipp-flaeche) + 2 * var(--space-2));
  background: var(--color-tabellenkopf);
  border-top: 1px solid var(--color-neutral-300);
  border-bottom: 1px solid var(--color-neutral-300);
}
.arbeits-leiste .knopfe {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2);
}
/* EIN FORMULAR IN DER LEISTE IST EINE ZEILE. Das Anlegefeld der Tags steht
   mit seinem Knopf in der Leiste; `input[type=text] { width: 100% }` aus
   style.css hat den Knopf darunter geschoben und die Leiste von 60 auf 99 px
   aufgezogen. Dieselbe Falle wie in der Listenzelle, deshalb dieselbe Antwort
   an derselben Art von Stelle. */
.arbeits-leiste form.inline { display: flex; align-items: center; gap: var(--space-2); }
.arbeits-leiste form.inline input[type=text] { width: auto; min-width: 14rem; }

/* DIE KOPFZAHLEN -- rechts im Seitenkopf, eine Reihe von Feldern mit
   Trennstrich, jedes mit Aufschrift und Wert. Sie sagen etwas ueber die
   Ansicht und sind KEIN Bedienelement; was man tun kann, steht in der
   Arbeitsleiste darunter.

   HIESS BIS ZUM 14.09.2026 `.bestand-leiste`. Das Bauteil war nie auf
   Bestaende beschraenkt -- in der Partnerliste haette der Name einen
   behauptet, wo gar keiner gemeint ist. In der Arbeitsliste beschreiben
   Abschnitt 1 und 2 weiterhin den einbezogenen BESTAND, Abschnitt 3 das
   ERGEBNIS der Filterung; der Entwurf widersprach sich dort ("gesamter
   Zeitraum · alle Quellen" bei vier gesetzten Filtern) und ist ein
   Beispielbild. */
.kopf-zahlen {
  margin-left: auto;
  display: flex; align-items: stretch; flex-wrap: wrap;
  border: 1px solid var(--color-neutral-300);
  background: var(--color-blatt-fuss);
  font-size: var(--text-m); color: var(--color-neutral-600);
}
.kopf-zahlen > * {
  display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  margin: 0;
}
.kopf-zahlen > * + * { border-left: 1px solid var(--color-neutral-300); }
.kopf-zahlen .ergebnis { background: var(--color-neutral-200); }
.kopf-zahlen strong { color: var(--color-text); font-weight: 600; }
/* DIE WERTE VON kopf_zahlen() SIND ZAHLEN -- Anzahlen oder Betraege -- und
   stehen deshalb in der Ziffernschrift. Nicht `.kopf-zahlen strong`: In der
   Leiste der Arbeitsliste steht dort ein KONTONAME. */
.kopf-zahlen .wert { font-family: var(--font-zahl); font-size: var(--text-s); font-weight: 600; }
.kopf-zahlen .waehrung,
.kopf-zahlen .summe { font-family: var(--font-zahl); font-size: var(--text-s); font-weight: 500; }
.kopf-zahlen .waehrung { color: var(--color-accent); }
.kopf-zahlen .summe.pos { color: var(--color-plus); }
.kopf-zahlen .summe.neg { color: var(--color-minus); }
/* Ein Betrag IN der Kopfzeile behaelt die Schrift der Kopfzahl und deren
   Farbe -- gruen und rot gehoeren in die Liste, nicht in eine Zeile, die die
   Ansicht beschreibt. Seit dem 15.09.2026 kommen diese Werte durch betrag()
   und tragen damit ihre Maske fuer den Diskretionsmodus; vorher baute jede
   Kopfzeile ihre Betraege selbst und blieb im Diskretionsmodus offen. */
.kopf-zahlen .wert .betrag { color: inherit; font: inherit; }

/* betrag() liefert <span class="betrag pos|neg">, und style.css faerbt das.
   Hier faerbt die UMGEBUNG -- der Span erbt, sonst gewinnt die alte Regel. */
.kopf-zahlen .summe .betrag { color: inherit; font: inherit; }
.kopf-zahlen .leise { color: var(--color-neutral-700); }
/* "Mehrere Konten…" springt auf Reiter 4, wo die Quellen stehen. */
.kopf-zahlen button.quellen-sprung {
  padding: 0; border: 0; background: none;
  font-family: inherit; font-size: inherit; font-weight: 600;
  color: var(--color-accent); text-decoration: underline; cursor: pointer;
}

/* ----------------------------------------------------------- Knopf und Feld */

.btn {                            /* style.css setzt margin-top auf button */
  font-family: var(--font-body); font-size: var(--text-l);
  min-height: var(--tipp-flaeche);
  /* INLINE-FLEX, DAMIT `<a class="btn">` GENAUSO AUSSIEHT WIE `<button
     class="btn">`. Ein Browser zentriert den Inhalt eines Knopfes von selbst,
     den eines Verweises nicht: Als `display: block` sass der Text mit
     6,8 + 21 + 6,8 = 34,6 px oben in einer 44 px hohen Flaeche und liess
     9,4 px Luft darunter. In der Arbeitsliste faellt das nicht auf, weil
     "Filtern" ein echter Knopf ist -- eine Klasse, zwei Elemente, zwei
     Ergebnisse. Gemeldet vom Auftraggeber am 13.09.2026. */
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--space-2);
  text-decoration: none;
  margin: 0;
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--color-rahmen); border-radius: 0;
  background: var(--color-bg); color: var(--color-text);
  cursor: pointer;
}
.btn:hover { background: var(--color-surface); border-color: var(--color-accent-700); color: var(--color-accent); }
.btn-primary {
  background: var(--color-accent-700); border-color: var(--color-accent-700);
  color: var(--color-auf-akzent); font-weight: 600;
}
.btn-primary:hover { background: var(--color-accent); border-color: var(--color-accent); color: var(--color-auf-akzent); }
/* DER ROTE KNOPF ALS UMRISS, nicht als Flaeche. style.css faerbt
   `button.gefahr` kraeftig rot -- in einer Listenzeile mit einem Datensatz
   je Zeile ist das ein Alarm neben jeder Zeile, und was neben jeder Zeile
   steht, alarmiert niemanden mehr. Gefuellt wird er erst unter dem Zeiger.
   `.btn.gefahr` ist spezifischer als `button.gefahr` und gewinnt damit auch
   auf einem `<button>`. */
.btn-gefahr {
  background: var(--color-fehler-grund); border-color: var(--color-fehler-rahmen);
  color: var(--color-fehler);
}
.btn-gefahr:hover {
  background: var(--color-fehler); border-color: var(--color-fehler);
  color: var(--color-auf-akzent);
}

/* `.app a { color: var(--color-accent) }` faerbt JEDEN Verweis im Akzent und
   ist mit (0,1,1) spezifischer als `.btn` (0,1,0). Ein `<a class="btn
   btn-primary">` stand damit in Akzentfarbe AUF Akzentfarbe -- gemessen
   #46687f auf #5b7b96, kaum lesbar und wie abgeschaltet aussehend.

   DIESELBE FALLE WIE BEI DER ZENTRIERUNG IN PAKET 18: Der Knopf der
   Arbeitsliste ist ein echtes `<button>` und deshalb nie betroffen. Eine
   Klasse, zwei Elemente, zwei Ergebnisse -- und sichtbar wird es erst dort,
   wo das andere Element steht. */
.app a.btn          { color: var(--color-text); }
.app a.btn:hover    { color: var(--color-accent); }
.app a.btn-primary,
.app a.btn-primary:hover { color: var(--color-auf-akzent); }
.app a.btn-gefahr   { color: var(--color-fehler); }
.app a.btn-gefahr:hover { color: var(--color-auf-akzent); }

.btn:focus-visible,
.input:focus-visible,
.navi-punkt:focus-visible,
.navi-gruppe:focus-visible,
.navi-schalter:focus-visible,
.navi-zeichen:focus-visible,
.filter-reiter:focus-visible,
.app .info-schalter:focus-visible,
.meldung button:focus-visible,
.mehr > summary:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}

.feld { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
/* "leeren" wandert in die Beschriftungszeile -- IN das <label>. Unter dem Feld
   kostete es eine eigene Rasterzeile von 40 px, und das ist genau eine
   Tabellenzeile. Der Selektor greift deshalb im Label, nicht am .feld:
   style.css setzt Regeln auf blanke <button>, und die machten daraus sonst
   einen fingerdicken blauen Knopf mitten in der Beschriftung. */
.feld .leeren {
  margin-left: auto;
  min-height: 0; padding: 0 var(--space-1);
  border: 0; background: none;
  font-family: var(--font-body); font-size: var(--text-s);
  color: var(--color-accent); text-decoration: underline; cursor: pointer;
}
.feld .leeren:hover { color: var(--color-fehler); }
.feld > label {                        /* style.css: display:block, margin, bold */
  display: flex; align-items: center; gap: var(--space-2);
  margin: 0;
  font-size: var(--text-s); font-weight: 400;
  color: var(--color-neutral-600);
}
input.input, select.input, textarea.input {   /* input[type=text] ist spezifischer
                                                 als .input -- deshalb mit Element */
  font-family: var(--font-body); font-size: var(--text-l);
  width: 100%; min-width: 0;
  min-height: var(--tipp-flaeche);
  padding: var(--space-2);
  border: 1px solid var(--color-rahmen); border-radius: 0;
  background: var(--color-feld-grund); color: var(--color-text);
}
input.input.num { text-align: right; }

/* Der Infoschalter am Feld. Ein `i` im Kreis, 17-mal, mit 17 eigenen
   Kennungen -- im Entwurf v3 hingen Tags und Waehrung beide an h16.
   Es ist immer HOECHSTENS EINER offen: Sonst schiebt sich das Filterblatt
   ueber den halben Bildschirm. */
.app .info-schalter {
  flex: none;
  width: 15px; height: 15px; padding: 0; margin: 0;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--color-accent-100); border-radius: 50%;
  background: var(--color-feld-grund); color: var(--color-accent);
  font-family: var(--font-heading);
  font-size: var(--text-winzig); font-weight: 700; line-height: 1;
  cursor: pointer;
}
.app .info-schalter:hover { background: var(--color-accent-50); border-color: var(--color-accent); }
.app .info-schalter[hidden] { display: none; }
.info-text {
  display: block;
  padding: var(--space-2);
  margin-bottom: var(--space-1);
  background: var(--color-accent-50);
  border-left: 2px solid var(--color-accent-100);
  font-size: var(--text-s); line-height: 1.45;
  color: var(--color-neutral-800);
}
.info-text[hidden] { display: none; }

/* ------------------------------------------------------------ Filterblatt --
   Vier Reiter, pro Reiter ein Thema. Vorher: fuenf Felder oben, die uebrigen
   zwoelf hinter <details class="mehr">.

   Die Klappe war nicht falsch -- sie arbeitete ohne Skript. Aber sie
   beantwortete die Frage "welche Filter gibt es" mit einer Zahl, und die
   Antwort "zwoelf" half niemandem. Vier Themen tun es.
   -------------------------------------------------------------------------- */

.filterblatt {
  margin: var(--space-4) 0 0;
  background: var(--color-blatt);
  border-top: 1px solid var(--color-rahmen-leise);
  border-bottom: 1px solid var(--color-rahmen-leise);
}
.filter-kopf {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-3);
  padding: var(--space-2) var(--space-6);
  background: var(--color-blatt-kopf);
  border-bottom: 1px solid var(--color-neutral-300);
}
.filter-kopf .marke-aktiv {
  background: var(--color-accent-700); color: var(--color-auf-akzent);
  padding: 1px var(--space-2);
  font-size: var(--text-s); font-weight: 600;
}
/* .filter-kopf davor, damit die Regel spezifischer ist als `.app button`
   weiter oben -- sonst gewinnt dessen `margin: 0` und der Knopf klebt an
   „N aktiv" statt rechts zu stehen. */
.filter-kopf .filter-klappe {
  margin-left: auto; padding: 0;
  display: flex; align-items: center; gap: var(--space-1);
  min-height: var(--tipp-flaeche);
  border: 0; background: none;
  font-family: var(--font-body); font-size: var(--text-m); font-weight: 600;
  color: var(--color-accent); cursor: pointer;
}
.filter-kopf .filter-klappe svg { width: 14px; height: 14px; flex: none; }
.filter-kopf .filter-klappe[hidden] { display: none; }

/* DIE LINIE GEHOERT DER REITERZEILE, NICHT DER TAFEL.
   Vorher trug `.filter-tafel` ein `border-top`. Die Tafel steht im Quelltext
   hinter den Reitern, malt also spaeter -- ihre Linie lag ueber der
   Unterkante des aktiven Reiters und schnitt ihn unten ab, statt ihn in die
   Tafel uebergehen zu lassen. Kinder malen dagegen immer ueber den Rahmen
   ihres Elternteils: Traegt die Zeile die Linie, deckt der aktive Reiter sie
   mit seiner eigenen weissen Unterkante zu. */
.filter-reiter-zeile {
  display: flex; flex-wrap: wrap;
  gap: 0 var(--space-1);
  padding: var(--space-2) var(--space-6) 0;
  border-bottom: 1px solid var(--color-rahmen-leise);
}
/* Die ruhenden Reiter haben KEINEN sichtbaren Rahmen -- mit einem sehen sie
   wie Knoepfe aus, und eine Reihe Knoepfe sagt nicht, dass genau einer davon
   gerade offen ist. Der Rahmen bleibt als `transparent` liegen, damit der
   Reiter beim Wechsel nicht um 2 px springt. */
/* `.filter-reiter-zeile` DAVOR, damit die Regel spezifischer ist als
   `.app button` weiter oben -- derselbe Fall wie bei `.filter-klappe`.
   Sonst gewinnt dessen `margin: 0`, das `margin-bottom: -1px` faellt aus,
   und der Reiter endet 1 px UEBER der Linie der Zeile: Sie laeuft dann auch
   unter ihm durch und schneidet ihn von seiner Tafel ab. Genau die eine
   fehlende Zeile ist der sichtbare Unterschied zwischen Reiter und Knopf. */
.filter-reiter-zeile .filter-reiter {
  display: flex; align-items: center; gap: var(--space-2);
  min-height: var(--tipp-flaeche);
  padding: var(--space-2) var(--space-4);
  margin-bottom: -1px;
  border: 1px solid transparent;
  background: none;
  font-family: var(--font-body); font-size: var(--text-l); font-weight: 600;
  color: var(--color-neutral-600);
  white-space: nowrap; cursor: pointer;
}
.filter-reiter:hover { color: var(--color-accent); background: var(--color-blatt-kopf); }
.filter-reiter[aria-selected="true"] {
  border-color: var(--color-rahmen-leise);
  border-bottom-color: var(--color-surface);
  background: var(--color-surface);
  color: var(--color-text);
}
/* EINGEKLAPPT BLEIBEN DIE REITER STEHEN (Entscheidung des Auftraggebers).
   Dann schliesst sich unter dem aktiven Reiter aber keine Tafel an, an die er
   sich anlehnen koennte -- er wuerde als Kasten mit offener Unterkante
   dastehen. Also traegt er eingeklappt eine geschlossene Kante und bleibt
   trotzdem als der gewaehlte erkennbar. */
.filterblatt.ist-zu .filter-reiter[aria-selected="true"] {
  border-bottom-color: var(--color-rahmen-leise);
  background: var(--color-surface);
}
.filterblatt.ist-zu .filter-reiter-zeile { padding-bottom: var(--space-3); }
/* Eingeklappt folgt auf die Reiterzeile sofort die Chipzeile, und beide
   brachten eine eigene Haarlinie mit -- nebeneinander sind das 2 px. Die
   Linie der Reiterzeile bleibt, weil der aktive Reiter auf ihr steht. */
.filterblatt.ist-zu .chips { border-top: 0; }
.filter-reiter .zahl {
  background: var(--color-accent-700); color: var(--color-auf-akzent);
  padding: 0 var(--space-1);
  font-size: var(--text-xs); font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.filter-tafel {
  background: var(--color-surface);
  padding: var(--space-3) var(--space-6);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-3);
}
.filter-tafel[hidden] { display: none; }
/* Reiter 3 traegt zwei Reihen: oben die drei Zustaende, unten die drei
   Mehrfachauswahlen. Die Trennung ist der Punkt -- "hat es eine Kategorie"
   ist eine andere Frage als "welche Kategorie". */
.filter-tafel .reihe {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-3);
}
.filter-tafel .notiz { grid-column: 1 / -1; margin: 0; font-size: var(--text-s); color: var(--color-neutral-600); }

/* Ohne JavaScript gibt es keine Reiter: Dann stehen alle Felder untereinander,
   die Reiterzeile ist weg und das Formular sendet dieselben Werte.
   app.js setzt `js` am <html> -- ohne Skript greift diese Regel. */
html:not(.js) .filter-reiter-zeile { display: none; }
html:not(.js) .filter-tafel[hidden] { display: grid; }
html:not(.js) .filter-tafel + .filter-tafel { border-top: 1px solid var(--color-divider); }

/* Die Token-Auswahl ist eine HUELLE ueber <select multiple>. Das echte Feld
   bleibt liegen: Es sendet das Formular ab, es arbeitet ohne Skript,
   data-leeren wirkt weiter, und neun Pruefsuiten behalten ihre Kennungen.
   Ein handgebauter Ersatz muesste Tastaturbedienung, Formularuebergabe und
   Mehrfachauswahl neu erfinden -- fuer ein Aussehen, das auch als Huelle zu
   haben ist. */
.tokenfeld { position: relative; }
.tokenfeld .zeigen {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-1);
  min-height: var(--tipp-flaeche);
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--color-rahmen);
  background: var(--color-feld-grund);
  cursor: pointer;
}
.tokenfeld .zeigen:hover { border-color: var(--color-accent-700); }
.tokenfeld .zeigen[hidden] { display: none; }
.tokenfeld .token {
  display: inline-flex; align-items: center; gap: var(--space-1);
  background: var(--color-accent-50);
  border: 1px solid var(--color-accent-100);
  color: var(--color-text);
  font-size: var(--text-s);
  padding: 1px var(--space-1) 1px var(--space-2);
  white-space: nowrap;
}
.tokenfeld .token button {
  padding: 0 2px; border: 0; background: none;
  color: var(--color-neutral-600); font-size: var(--text-xs); cursor: pointer;
}
.tokenfeld .token button:hover { color: var(--color-fehler); }
.tokenfeld .nichts { color: var(--color-neutral-600); font-size: var(--text-m); }
.tokenfeld select.input[hidden] { display: none; }

.chips {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-6);
  background: var(--color-blatt-fuss);
  border-top: 1px solid var(--color-rahmen-leise);
}
.chips .kicker { flex: none; }
.chip {
  display: inline-flex; align-items: center; gap: var(--space-2);
  font-size: var(--text-m); padding: 2px var(--space-2);
  background: var(--color-surface);
  border: 1px solid var(--color-neutral-300);
  color: var(--color-text);
}
.chip .was { color: var(--color-neutral-600); }
.chip a {
  text-decoration: none; color: var(--color-neutral-700); font-weight: 600;
  display: inline-flex; align-items: center; justify-content: center;
  min-width: var(--space-6); min-height: var(--space-6);
}
.chip a:hover { color: var(--color-fehler); }
.chips .fuellen { flex: 1 1 auto; min-width: 0; }
/* "Zuruecksetzen" und "Filtern" stehen am RECHTEN Ende der Chip-Zeile, nicht
   in der Feldertafel: Die wechselt je Reiter, die Knoepfe gelten fuer alle
   siebzehn Felder zusammen. */
.chips .zuruecksetzen {
  flex: none; white-space: nowrap;
  font-size: var(--text-m); color: var(--color-accent);
  text-decoration: none;
  display: inline-flex; align-items: center;
  min-height: var(--tipp-flaeche); padding: 0 var(--space-2);
}
.chips .zuruecksetzen:hover { text-decoration: underline; }
.chips .btn { flex: none; }
.chips .hinweis { font-size: var(--text-s); color: var(--color-neutral-600); margin: 0; }

/* ------------------------------------------------------- Sammelaktionen ----
   Drei Aktionen auf DERSELBEN Markierung, jetzt in drei Reitern: sichtbar ist
   eine. Es bleiben DREI FORMULARE mit ihren bisherigen Feldern und Zielen --
   was sie tun, aendert sich nicht.
   -------------------------------------------------------------------------- */

.sammelaktionen {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--space-3) var(--space-4);
  padding: var(--space-3) var(--space-6);
  background: var(--color-accent-50);
  /* KEINE eigene Oberkante. Darueber endet das Filterblatt mit seinem
     `border-bottom` -- zwei Haarlinien in verschiedenen Farben stossen dort
     aneinander und lesen sich als eine dicke, schmutzige. Die Linie des
     Filterblatts bleibt, weil sie zur Sache gehoert: Das Blatt ist auf jeder
     Seite ein geschlossener Block, auch wo keine Sammelleiste folgt. Die
     Leiste haengt sich darunter -- richtig, denn sie wirkt auf das, was der
     Filter uebrig laesst. Unten braucht sie ihre Kante weiter: Dort folgt
     die Tabelle ohne eigene Oberlinie. */
  border-bottom: 1px solid var(--color-accent-100);
}
.sammelaktionen .wieviel {
  display: flex; align-items: baseline; gap: var(--space-2);
  white-space: nowrap;
}
.sammelaktionen .wieviel .zahl { font-size: var(--text-l); font-weight: 600; color: var(--color-text); }
.sammel-reiter-zeile {
  display: flex; flex: none; align-self: center;
  border: 1px solid var(--color-accent-100);
}
/* Die drei sind EIN Segmentschalter: Der Rahmen gehoert der Zeile, die Knoepfe
   darin haben nur eine Trennlinie. Dass sie eckig sind, kommt aus
   `.app button` -- gerundet zog sich die Fuellung des gewaehlten aus den
   Ecken des Rahmens zurueck und die Trennlinien standen frei wie Luecken. */
.sammel-reiter {
  padding: var(--space-2) var(--space-3);
  min-height: var(--tipp-flaeche);
  border: 0; background: var(--color-surface);
  font-family: var(--font-body); font-size: var(--text-m); font-weight: 600;
  color: var(--color-accent); cursor: pointer;
}
.sammel-reiter + .sammel-reiter { border-left: 1px solid var(--color-accent-100); }
.sammel-reiter[aria-selected="true"] { background: var(--color-accent-700); color: var(--color-auf-akzent); }
.sammel-reiter-zeile[hidden] { display: none; }

.sammelaktionen .aktionen {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--space-2);
  flex: 1 1 320px; min-width: 0;
  margin: 0;
}
.sammelaktionen .aktionen[hidden] { display: none; }
.sammelaktionen .aktionen > label {
  margin: 0; font-size: var(--text-s); font-weight: 400;
  color: var(--color-neutral-600);
}
.sammelaktionen .aktionen > .input { flex: 1 1 160px; }
/* Ohne JavaScript stehen alle drei untereinander -- das ist der Zustand von
   vor Paket 15 und vollstaendig bedienbar. */
html:not(.js) .sammelaktionen .aktionen[hidden] { display: flex; }
html:not(.js) .sammelaktionen { flex-direction: column; align-items: stretch; }

/* -------------------------------------------------------------------- Tabelle */

/* KEIN SEITENPOLSTER AM KASTEN. Mit einem lag der Grund des Tabellenkopfes
   links und rechts um --rand-tabelle hinter der Kante zurueck, waehrend jedes
   Band darueber und darunter (Sammelaktionen, Legende, Blaetterleiste) bis an
   den Rand durchlief -- der Kopf sah aus wie ein zu kurzes Brett.
   Der Abstand ist derselbe geblieben, er sitzt nur woanders: in der ersten
   und letzten Zelle. Die beiden Spaltenbreiten unten rechnen ihn mit, sonst
   waere die Auswahlspalte um diesen Betrag enger geworden.

   UND KEIN `overflow-x` MEHR, ausser wo es gebraucht wird (Paket 17).

   Der Kasten trug `overflow-x: auto`. Damit rechnet der Browser `overflow-y`
   ebenfalls auf `auto`, der Kasten wird zum naechsten Scrollcontainer -- und
   der klebende Tabellenkopf klebte an IHM statt an der Seite. Ein Kasten, der
   senkrecht nie scrollt: Der Kopf verschwand beim Scrollen, seit Paket 13,
   ohne dass eine Pruefung je darauf gesehen haette. Gemessen bei 1440 px und
   scrollTop 1200: th.top -773 statt 42. `overflow-y: clip` rettet es nicht,
   das wird zu `hidden` und bleibt ein Scrollcontainer.

   DIE GRENZE IST GERECHNET, NICHT GERATEN: Die Tabelle braucht
   1028px + 2 x --rand-tabelle = 1068,8 px, die Bahn ist Fensterbreite - 220
   minus ihre beiden Rahmenpixel. Gleich sind beide bei 1290,8 px. Darueber hat
   der Kasten nichts zu scrollen und kostet nur den Kopf; darunter verdient er
   sich sein Geld, und der Kopf klebt nicht -- dieselbe Abwaegung, die der
   Mobilblock bei 720 px trifft.

   1028 statt 1020 seit dem 13.09.2026: Die Datumsspalte ist um 8 px
   gewachsen. WER EINE SPALTE VERBREITERT, VERSCHIEBT DIESE GRENZE MIT --
   bleibt die Zahl stehen, scrollt die ganze SEITE seitlich, und zwar in einem
   schmalen Fensterbereich, in den man beim Entwickeln selten geraet. */
.tabelle-rahmen {
  padding: 0 0 var(--space-4);
  background: var(--color-surface);
}

/* DIESELBE FLAECHE, ANDERER ZUSCHNITT. `.tabelle-rahmen` hat keinen
   waagerechten Innenabstand -- den tragen die Zellen ueber --rand-tabelle.
   Fliesstext hat keine Zellen, also braucht er ihn hier.

   Ersetzt `.karte` aus style.css fuer die erklaerenden Absaetze ueber den
   Listen. `.karte` lieferte Flaeche, Breite und Polsterung in einem; die
   Breite kommt jetzt von der Inhaltsbahn, die Flaeche von hier.

   Die Zeilenlaenge wird begrenzt, und zwar hier und nicht am Absatz: Die Bahn
   ist 1480 px breit, weil die Buchungstabelle es verlangt. Ein Hinweissatz
   ueber die volle Breite waere so unlesbar wie eine Zeile mit 200 Zeichen --
   genau deshalb stand in style.css `.karte p { max-width: 90ch }`. */
.blatt {
  padding: var(--space-4) var(--space-6);
  background: var(--color-surface);
}
.blatt > :first-child { margin-top: 0; }
.blatt > :last-child  { margin-bottom: 0; }

/* Der Abstand zwischen Seitenkopf und dem, was folgt. In der Arbeitsliste
   bringt ihn der Filterkopf mit (gemessen 14 px); Blatt, Tabelle und
   Leerzustand haben keinen eigenen und klebten am Kopf. */
.seiten-kopf + .blatt,
.seiten-kopf + .tabelle-rahmen,
.seiten-kopf + .arbeits-leiste,
.seiten-kopf + .leer-hinweis { margin-top: var(--space-3); }
/* Die Leiste klebt an dem, was sie bedient -- die Tabelle folgt ohne Abstand,
   ein erklaerender Absatz danach braucht wieder Luft. */
.arbeits-leiste + .tabelle-rahmen { margin-top: 0; }
.arbeits-leiste + .blatt,
.arbeits-leiste + .leer-hinweis { margin-top: var(--space-3); }
/* DAS KACHELRASTER IST KEINE TABELLE. Eine Tabelle schliesst an die Leiste an
   -- sie ist das, was die Leiste bedient. Die Kacheln sind eigene Bloecke auf
   dem Seitengrund und klebten ohne diese Regel an der Zeile "Zeitraum".
   Gemeldet vom Auftraggeber am 15.09.2026.

   DERSELBE ABSTAND WIE ZWISCHEN ZWEI KACHELN: Was das Raster innen trennt,
   trennt es auch nach oben -- sonst haette der erste Zwischenraum eine andere
   Groesse als alle folgenden. */
.arbeits-leiste + .kacheln { margin-top: var(--space-4); }
/* KEIN DECKEL AUF DER ZEILENLAENGE. Hier stand `max-width: 80ch`, aus
   style.css uebernommen (`.karte p { max-width: 90ch }`) und gut gemeint: Eine
   Zeile ueber die volle Bahnbreite liest sich schlecht. Gemessen waren es
   640 px Text in 1337 px Flaeche -- der Absatz brach mitten in der weissen
   Flaeche um und sah aus wie abgeschnitten, nicht wie gesetzt. Der
   Auftraggeber hat entschieden: Der Text nutzt die Flaeche.

   Die Abwaegung bleibt wahr, sie faellt hier nur anders aus: Es sind kurze
   Hinweise von zwei bis drei Zeilen, kein Fliesstext. */

/* Ein Ast des Kategorienbaums. Hiess bis zum 13.09.2026 `.quelle` -- die
   Klasse der Quellenuebersicht, uebernommen, weil beide dieselbe Trennlinie
   brauchten. Ein Name, der die falsche Sache benennt, ueberlebt jede
   Umgestaltung und wird beim naechsten Mal mitkopiert. */
.kat-ast { border-top: 1px solid var(--color-rahmen-leise); padding-top: var(--space-4); margin-top: var(--space-4); }
.kat-ast:first-of-type { border-top: 0; margin-top: 0; }
.kat-ast h2 { margin: 0 0 var(--space-2); font-size: var(--text-xl); }
/* NUR UM DIE BUCHUNGSTABELLE. `:has()` statt einer zweiten Klasse am Kasten:
   Die Bedingung ist "steht da eine breite Tabelle drin", und genau das steht
   hier. Eine Partnerliste mit fuenf Spalten hat nichts zu scrollen, und ein
   Scrollkasten um sie herum waere nicht nur nutzlos -- er macht den Kasten zum
   Scrollcontainer und damit jeden klebenden Kopf darin wirkungslos. */
@media (max-width: 1300px) {
  .tabelle-rahmen:has(table.buchungen) { overflow-x: auto; }
}
/* DAS AUSSEHEN -- gilt fuer JEDE Liste der App. Was hier steht, darf keine
   Aussage ueber Spalten treffen; die trifft `.buchungen` unten. */
table.liste {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-l);
  margin: 0;
}

/* DER SPALTENPLAN -- gilt NUR fuer die Buchungstabelle der Arbeitsliste.
   Bis zum 13.09.2026 stand beides an `table.liste`, und es ist nur deshalb
   nie aufgefallen, weil keine andere Ansicht die Klasse benutzte: Die
   Kategorienliste mit ihren DREI Spalten haette elf Breitenangaben und eine
   Mindestbreite von 1028 px geerbt.

   `table-layout: fixed` gehoert dazu -- ohne den Spaltenplan waere es eine
   Anweisung, gleich breite Spalten zu bauen, und eine Partnerliste saehe
   danach aus wie ein Gitter. */
table.liste.buchungen {
  min-width: calc(1028px + 2 * var(--rand-tabelle));
  table-layout: fixed;                 /* die Spaltenbreiten stehen unten in
                                          CSS, nicht als <col width> im PHP */
}
table.liste th {
  text-align: left; vertical-align: middle;
  font-family: var(--font-heading);
  font-size: var(--text-xs); font-weight: 700;
  letter-spacing: .11em; text-transform: uppercase;
  color: var(--color-neutral-700);
  padding: var(--space-2);
  border-bottom: 1px solid var(--color-neutral-300);
  background: var(--color-tabellenkopf);
  white-space: nowrap; overflow: hidden;
}

/* DER KLEBENDE KOPF gehoert zum Spaltenplan, nicht zum Aussehen: Er haengt an
   einer Fenstergrenze, die aus der BREITE DER BUCHUNGSTABELLE gerechnet ist
   (1300 px, siehe unten). Eine Partnerliste mit acht Zeilen hat nichts davon
   und braechte die Grenze nur in eine zweite Ansicht, in der sie niemand
   nachrechnet.

   Er klebt an der SEITE, um die Hoehe der Meldungsleiste tiefer -- sonst
   schoebe er sich darunter. --meldung-hoehe ist 0, solange keine ansteht. In
   jedem Fall, in dem der Kopf klebt, ist die Leiste einzeilig und damit
   konstant hoch; die Fallunterscheidung, die sonst einen ResizeObserver
   gebraucht haette, entfaellt mit der Grenze. */
table.liste.buchungen th {
  position: sticky; top: var(--meldung-hoehe); z-index: 2;
}
table.liste th.zuordnung { color: var(--color-accent); }
table.liste th.num { text-align: right; }
table.liste td {
  padding: var(--space-1) var(--space-2);
  vertical-align: middle; text-align: left;
  border-bottom: 1px solid var(--color-divider);
}

/* DICHTE UND EINZEILIGKEIT SIND EINE AUSSAGE UEBER DEN INHALT, nicht ueber
   Listen an sich -- und deshalb gehoeren sie zu `.buchungen`.
   `height: var(--zeilen-hoehe)` presst die Zeile auf 36 px, `nowrap` und
   `overflow: hidden` schneiden ab, was laenger ist. Fuer 128 Buchungen ist das
   genau richtig; fuer die Kategoriengruppen, die in ihren Zellen EINGABEFELDER
   MIT KNOPF tragen, ist es falsch.

   Gemessen am 13.09.2026, nachdem beides zunaechst an `table.liste` stand:
   Playwright bekam den Knopf "umbenennen" nicht mehr gedrueckt --
   "<form class='inline'> intercepts pointer events". Das Formular quoll aus
   seiner 36 px hohen Zelle und lag ueber der Nachbarzelle. Im Bild war davon
   fast nichts zu sehen; gefunden hat es der Klick, der ins Leere ging. */
table.liste.buchungen td {
  height: var(--zeilen-hoehe);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
table.liste td.num { text-align: right; }
table.liste th:first-child,
table.liste td:first-child { padding-left: calc(var(--space-2) + var(--rand-tabelle)); }
table.liste th:last-child,
table.liste td:last-child { padding-right: calc(var(--space-2) + var(--rand-tabelle)); }
table.liste tbody tr:hover { background: var(--color-accent-50); }
/* EIN FORMULAR IN EINER LISTENZELLE IST EINE ZEILE -- Feld und Knopf
   nebeneinander, so wie es der Entwurf zeigt. `input[type=text]
   { width: 100% }` aus style.css hat den Knopf in die naechste Zeile
   gedrueckt, seit `.karte` mit ihrer Breitenbegrenzung weg ist (Paket 18).
   Die Begrenzung kommt hier zurueck, aber an der Stelle, an die sie gehoert:
   ein 900 px breites Namensfeld ist so unlesbar wie eine Zeile mit
   200 Zeichen. */
table.liste form.inline { display: flex; align-items: center; gap: var(--space-2); }
table.liste form.inline input[type=text] { flex: 1 1 auto; min-width: 0; max-width: 32rem; }

/* Die elf Spalten des Entwurfs. Mit table-layout:fixed genuegt die Angabe
   am Kopf -- der Rest richtet sich danach. NUR an `.buchungen`, siehe oben. */
table.liste.buchungen thead th.auswahl { width: calc(28px + var(--rand-tabelle)); }
/* 86 statt 78 px, und das ist gerechnet: Der Innenraum der Zelle ist die
   Breite minus 2 x 6,8 px Polsterung. Bei 78 px waren das 64,4 px -- und ein
   Datum misst in Barlow zwischen 55 und 72 px, weil die Ziffern NICHT gleich
   breit sind (0, 2 und 4 sind 8 px, die 1 nur 5). "03.09.2026" kam auf 64,00
   und passte mit 0,4 px Luft; "20.10.2020" braucht 72 und wurde abgeschnitten.
   Wer nur den heutigen Bestand ansieht, haelt die Spalte fuer knapp, aber
   heil. 86 px geben 72,4 px Innenraum -- der schlimmste Fall passt, auch ohne
   die Tabellenziffern unten. */
table.liste.buchungen thead th.datum { width: 86px; }
table.liste.buchungen thead th.gegenpartei { width: auto; }
table.liste.buchungen thead th.zweck { width: auto; }
table.liste.buchungen thead th.buchungstext { width: 100px; }
table.liste.buchungen thead th.betrag-zelle { width: 102px; }
table.liste.buchungen thead th.kategorie { width: 136px; }
table.liste.buchungen thead th.vertrag { width: 136px; }
table.liste.buchungen thead th.tags { width: 100px; }
table.liste.buchungen thead th.zeilen-aktion { width: 66px; }
table.liste.buchungen thead th.quelle { width: calc(74px + var(--rand-tabelle)); }

/* NACH den th-Regeln, nicht davor: Ein Medienblock erhoeht die Spezifitaet
   nicht, also entscheidet die Reihenfolge -- weiter oben gewann `position:
   sticky` aus der Hauptregel und `static` fiel wirkungslos aus.

   DIE GRENZE: Die Tabelle braucht 1028px + 2 x --rand-tabelle = 1068,8 px.
   Die Bahn hat Fensterbreite - --nav-breite - 2 px (ihre beiden Rahmenlinien)
   an Innenraum. Gleich sind beide bei 1290,8 px -- bei 1281 px gemessen (mit
   der alten, 8 px schmaleren Datumsspalte): die Tabelle stand 2 px ueber und
   die SEITE scrollte seitlich. Aufgerundet auf 1300 px, damit Teilpixel und
   eine Bildlaufleiste Platz haben.

   DIESELBE ZAHL STEHT OBEN AM `.tabelle-rahmen`. Die beiden muessen gleich
   sein: Der klebende Kopf funktioniert nur ausserhalb eines Scrollkastens. */
@media (max-width: 1300px) { table.liste.buchungen th { position: static; } }

/* TABELLENZIFFERN IN DER DATUMSSPALTE. Barlows Ziffern sind verschieden breit
   (5 bis 8 px), wodurch untereinanderstehende Daten gegeneinander verrutschen
   und die noetige Spaltenbreite vom Zufall des Bestands abhaengt. Mit `tnum`
   ist jedes Datum genau 64 px breit -- die Punkte stehen in einer Linie, und
   die Breite oben ist nachrechenbar statt gemessen.

   Die Spalte ist trotzdem fuer 72 px ausgelegt: `pruef_system.js` prueft die
   Seite auch OHNE die Schriftdateien, und was die Ersatzschrift tut, ist
   nicht zugesichert. */
table.liste td.datum { font-variant-numeric: tabular-nums; }

table.liste td.buchungstext { font-size: var(--text-m); color: var(--color-neutral-600); }
table.liste td.tags,
table.liste td.quelle,
table.liste td.zeilen-aktion { font-size: var(--text-m); }

/* Betrag: Eingang gruen, Abgang rot -- mit Paket 15 GEWOLLT. Die Zelle
   faerbt, der Span aus betrag() erbt. */
table.liste td.num .betrag { color: inherit; }
table.liste td.num.betrag-zelle {
  font-family: var(--font-zahl); font-size: var(--text-s); font-weight: 500;
  color: var(--color-minus);
}
table.liste td.num.betrag-zelle.eingang { color: var(--color-plus); }

/* Die Marke "A" ersetzt die Woerter "auto" und "manuell": Sie steht fuer
   "per Regel zugeordnet", keine Marke heisst "von Hand". Die Handzuordnung
   ist der Normalfall und braucht kein Wort. */
table.liste .marke-auto {
  font-family: var(--font-zahl);
  font-size: var(--text-winzig); font-weight: 600;
  color: var(--color-auf-akzent); background: var(--color-accent-700);
  padding: 0 3px;
  flex: none;
}

/* ZWEI TEILE IN EINER REIHE, KEIN TEXTFLUSS. Die Marke lag hinter dem Namen
   im selben Fluss, und `text-overflow: ellipsis` der Zelle schnitt beides ab.
   Gemessen am 13.09.2026: Spalte 136 px, Inhalt "GE1-Einnahmen / Stellplatz A"
   194 px -- das A endete 51 px hinter der Kante und war NIE zu sehen. Zu sehen
   blieb es allein bei kurzen Namen ohne Oberkategorie ("Lohn & Gehalt"), was
   wie ein Datenfehler aussah: 25 von 25 Zuordnungen sind AUTO, sichtbar war
   eine Handvoll.

   Jetzt kuerzt der NAME und die Marke behaelt ihren Platz. Der volle Name
   steht weiterhin im `title`. Das Kuerzen muss dafuer vom `td` auf den Namen
   wandern: Ein Flex-Container kennt kein `text-overflow`.

   DIE REIHE LIEGT IN EINER HUELLE, NICHT IM `td` SELBST. Zuerst stand das
   `display: flex` auf `td.kategorie`/`td.vertrag`, und damit war die Zelle
   keine Tabellenzelle mehr: Sie hoerte auf, sich auf die Zeilenhoehe zu
   strecken -- 36 px in einer 73 px hohen Zeile, in JEDER der 25 Zeilen, mit
   der Trennlinie mittendrin statt am Zeilenfuss. Gefunden wurde es nicht am
   Bild, sondern durch Nachmessen der Zellhoehen einer Zeile; sie muessen
   gleich sein.

   `zuordnung-ZELLE`, nicht `zuordnung`: Diesen Namen tragen bereits die vier
   KOPFZELLEN (`th.zuordnung`, oben). Die Regel hat sie mitgenommen und damit
   auch dort das Strecken abgeschaltet -- die Kopfzeile war 124 px hoch, die
   beiden betroffenen Kopfzellen 31, und in die Liste passten statt zehn nur
   noch acht Zeilen. Gefangen hat es `pruef_system.js` Punkt 23, der die
   sichtbaren Zeilen zaehlt und nicht das Aussehen beurteilt. */
table.liste .zuordnung-zelle {
  display: flex; align-items: center; gap: var(--space-1);
}
table.liste .zuordnung-text {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Eine fehlende Zuordnung ist ARBEITSVORRAT, keine Stoerung: Der `+` sagt
   "hier ist etwas zu tun" und ist gleich der Weg dorthin. Das ersetzt den
   Gedankenstrich in der Warnfarbe aus Paket 13, der "hier ist etwas kaputt"
   sagte. */
table.liste .anlegen {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: var(--space-6);
  padding: 0 var(--space-1);
  border: 1px dashed var(--color-accent-100);
  color: var(--color-accent);
  font-size: var(--text-m); font-weight: 600;
  text-decoration: none;
}
table.liste .anlegen:hover { border-style: solid; background: var(--color-accent-50); }
table.liste .leer { color: var(--color-neutral-700); }
table.liste .marke-storno {
  font-family: var(--font-heading);
  font-size: var(--text-xs); font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em;
  color: var(--color-fehler); background: var(--color-fehler-grund);
  border: 1px solid var(--color-fehler-rahmen);
  padding: 0 3px;
}
table.liste tfoot td {
  padding: var(--space-3) var(--space-2); font-weight: 600;
  border-top: 1px solid var(--color-neutral-300); border-bottom: 0;
}

/* Die Legende. Drei Eintraege -- der grosse gruene Punkt "Regel greift" ist
   mit Paket 15 entfallen: Er sagte dasselbe wie die Marke A. */
.legende {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: var(--space-2) var(--space-6);
  padding: var(--space-3) var(--space-6);
  background: var(--color-surface);
  border-top: 1px solid var(--color-divider);
  font-size: var(--text-m); color: var(--color-neutral-700);
}
.legende > span { white-space: nowrap; }
.legende .fuellen { flex: 1 1 auto; min-width: 0; }

/* DIE FUSSZEILE GEHOERT ZUR TABELLE, nicht zum Grund der Seite -- also steht
   sie auf derselben weissen Flaeche und ist nur durch den Strich oben
   abgesetzt. Bis zum 14.09.2026 trug sie `--color-blatt-fuss` (#f7f5f0), und
   das liegt so dicht am Seitengrund (#f4f4f1), dass ein zweiter Abschnitt
   darunter -- "Vertragsgruppen" -- wie eine Fortsetzung derselben Flaeche
   aussah. Gemeldet vom Auftraggeber. */
.blaetter {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2);
  /* KEIN AUSSENABSTAND. Die Leiste schliesst an die Tabelle an, sie steht
     nicht daneben. Als `<p>` brachte sie `margin: 1em 0` aus der
     Browservorgabe mit; das Element ist jetzt ein `<div>`, und die Null steht
     hier, damit es auch dann stimmt, wenn jemand das Element wechselt. */
  margin: 0;
  padding: var(--space-3) var(--space-6) var(--space-6);
  background: var(--color-surface);
  border-top: 1px solid var(--color-neutral-300);
  font-size: var(--text-l); color: var(--color-neutral-600);
}
.blaetter .wo { flex: none; white-space: nowrap; }
.blaetter .wo strong { color: var(--color-text); }
.blaetter .fuellen { flex: 1 1 auto; min-width: 0; }
.blaetter .seiten { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-1); }
/* Die Luecke im Seitenfenster (Paket 38): eine Angabe, kein Bedienelement.
   Deshalb ohne Rahmen und ohne Flaeche -- sie sieht aus wie Text, weil sie
   Text ist. Ein Kaestchen wie die Zahlen daneben waere ein Knopf, der nichts
   tut. */
.blaetter .luecke {
  padding: 0 var(--space-1);
  color: var(--color-neutral-600);
  user-select: none;
}
/* Die Pfeile tragen dieselbe Flaeche wie die Zahlen -- sie stehen in
   derselben Reihe und sollen nicht kleiner zu treffen sein. */
.blaetter .pfeil { font-size: var(--text-xl); line-height: 1; }
/* Zeilen je Seite -- dieselben Knoepfe wie die Seitenzahlen rechts, nur mit
   einer Beschriftung davor. Zwei verschiedene Aussehen fuer zwei Reihen
   Zahlen in derselben Leiste waeren zwei Dinge zu lernen statt einem. */
.blaetter .je-seite {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: var(--space-1); margin-left: var(--space-4);
}
.blaetter .je-seite .kicker { margin-right: var(--space-1); }
.blaetter a, .blaetter .hier {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: var(--tipp-flaeche); min-height: var(--tipp-flaeche);
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--color-rahmen);
  background: var(--color-surface);
  text-decoration: none; color: var(--color-text);
  font-variant-numeric: tabular-nums;
}
.blaetter a:hover { border-color: var(--color-accent-700); color: var(--color-accent); }
.blaetter .hier { background: var(--color-accent-700); border-color: var(--color-accent-700); color: var(--color-auf-akzent); font-weight: 600; }

/* DER ERKLAERENDE SATZ -- unter einem Feld, in einem Blatt, in einer Zelle.
   Lag bis zum 14.09.2026 in style.css und war damit die letzte Klasse, die den
   GRUNDTEXT der Formulare bestimmt hat: `vertrag-form` allein hat neun davon,
   und sie sind dort der eigentliche Inhalt.

   NUR FARBE UND GROESSE, kein Aussenabstand: Den bringt `<p>` mit, und ihn hier
   zu setzen haette jeden Hinweis der Anwendung verschoben. Wo ein anderer
   Abstand noetig ist, steht er am Ort -- `.form-feld > .notiz`,
   `.filter-tafel .notiz`, `.loeschen .notiz`. */
.notiz { font-size: var(--text-m); color: var(--color-neutral-600); }

/* --------------------------------------------------------------- Formular */

/* DIE FELDFOLGE. Sitzt in einem `.blatt` -- die weisse Flaeche und die
   Polsterung kommen von dort, hier steht nur, wie Felder stehen.

   LESEBREITE STATT BAHNBREITE: Ein 1337 px breites Namensfeld ist so unlesbar
   wie eine Zeile mit 200 Zeichen. Bis zum 14.09.2026 stand das als
   `.karte input { max-width: 32rem }` in style.css und haftete am Kasten;
   jetzt haftet es an der Sache. Die Erklaerungen unter den Feldern duerfen
   breiter laufen -- sie sind Text, kein Eingabeziel. */
.formular { max-width: 46rem; }
.formular h2 {
  margin: var(--space-8) 0 var(--space-2);
  font-size: var(--titel-klein);
}
.formular h2:first-of-type { margin-top: var(--space-6); }

/* EIN FELD IST EIN BLOCK aus Beschriftung, Eingabe und -- wo vorhanden --
   Erklaerung. Die Erklaerung steht NACH der Eingabe: Erst sieht man, worum es
   geht, dann warum. In `vertrag-form` sind es neun davon, und sie sind der
   eigentliche Inhalt dieser Formulare. */
/* `.form-feld`, NICHT `.feld`: Den Namen `.feld` traegt seit Paket 15 das
   Filterblatt (Zeile 768). Meine erste Fassung hiess so -- und machte aus
   65 px hohen Filterzeilen 99 und 118 px hohe, weil beide Regelsaetze auf
   dieselben Elemente trafen. Die Arbeitsliste verlor dadurch eine Zeile und
   `pruef_system.js` Punkt 23 wurde rot: die Latte hat den Sturz gefangen.
   Dritte Namenskollision der Reihe nach `.zuordnung` (Paket 18) und
   `.marke.ok` (Paket 19) -- ein Bauteil traegt den Namen SEINES Bauteils. */
.form-feld { margin: 0 0 var(--space-4); }
.form-feld > label {
  display: block; margin: 0 0 var(--space-1);
  font-family: var(--font-heading);
  font-size: var(--text-m); font-weight: 600;
  color: var(--color-text);
}
.form-feld > label .notiz { font-weight: 400; }
.form-feld input[type="text"],
.form-feld input[type="password"],
.form-feld input[type="date"],
.form-feld input[type="number"],
.form-feld select,
.form-feld textarea {
  font-family: var(--font-body); font-size: var(--text-l);
  width: 100%; min-width: 0;
  min-height: var(--tipp-flaeche);
  margin: 0;
  padding: var(--space-2);
  border: 1px solid var(--color-rahmen); border-radius: 0;
  background: var(--color-feld-grund); color: var(--color-text);
}
.form-feld textarea { min-height: calc(3 * var(--tipp-flaeche)); resize: vertical; }
.form-feld > .notiz { margin: var(--space-1) 0 0; }

/* ZWEI BIS DREI KURZE FELDER NEBENEINANDER -- Beginn/Ende, Betrag/Waehrung,
   Intervall/Anker. Ein Datumspaar auf zwei Zeilen zu verteilen macht aus einer
   Angabe zwei. Entschieden am 14.09.2026 mit dem Auftraggeber (Variante B).

   `auto-fit` mit Mindestbreite: Die Reihe bricht selbst um, statt sich auf
   eine Spaltenzahl festzulegen, die bei 700 px falsch waere. Sie gilt NUR fuer
   kurze Felder -- ein Textfeld mit Erklaerung bleibt einspaltig. */
.form-reihe {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 0 var(--space-4);
}
.form-reihe .form-feld { min-width: 0; }

/* Kaestchen mit Text daneben. Aus style.css uebernommen; das Kaestchen selbst
   bleibt in Browsergroesse, es ist keine Texteingabe. */
.schalter {
  display: inline-flex; align-items: center; gap: var(--space-2);
  font-family: var(--font-body); font-size: var(--text-l); font-weight: 400;
  min-height: var(--tipp-flaeche);
}
.form-feld > label.schalter { margin-bottom: 0; }
.schalter input[type="checkbox"] { width: auto; min-height: 0; margin: 0; }

/* DER FUSS. Abgesetzt vom letzten Feld, damit "Speichern" nicht wie ein
   weiteres Feld aussieht. */
.formular-fuss {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2);
  margin-top: var(--space-6);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-rahmen-leise);
}
/* DIE BEDINGUNGSZEILE der Regelmaske: Feld, Vergleich, Werte. Drei Angaben,
   die nur zusammen einen Satz ergeben -- sie stehen deshalb in einer Zeile und
   nicht als drei Felder untereinander. Aus style.css uebernommen; das Verhaeltnis
   1:1:2 bleibt, der Umbruch kommt jetzt aus derselben Regel wie bei
   `.form-reihe`. */
.bedingung {
  display: grid; grid-template-columns: 1fr 1fr 2fr;
  gap: var(--space-2); align-items: center;
  margin: 0 0 var(--space-2);
}
.bedingung select,
.bedingung input[type="text"] {
  font-family: var(--font-body); font-size: var(--text-l);
  width: 100%; min-width: 0; min-height: var(--tipp-flaeche); margin: 0;
  padding: var(--space-2);
  border: 1px solid var(--color-rahmen); border-radius: 0;
  background: var(--color-feld-grund); color: var(--color-text);
}
@media (max-width: 720px) { .bedingung { grid-template-columns: 1fr; } }

/* DAS KLAPPELEMENT. Die Vertragsansicht traegt sechs davon -- Masken, die man
   selten braucht und die die Seite sonst um das Dreifache verlaengerten.

   BIS PAKET 23 HAT style.css `details` und `summary` ALS BLANKE ELEMENTE
   gestaltet, ohne eine einzige Klasse. Eine Klassenzaehlung kann so etwas
   nicht sehen; beim Loeschen der Datei waere die Klappe still grau geworden
   und haette ausgesehen wie Text. Deshalb traegt sie jetzt einen Namen --
   `details.info` im Dashboard ist das andere Klappelement und eine andere
   Sache, es steht dort in der Kachelueberschrift. */
details.klappe { margin: var(--space-3) 0; }
details.klappe > summary {
  display: inline-flex; align-items: center; gap: var(--space-2);
  min-height: var(--tipp-flaeche);
  font-family: var(--font-heading); font-size: var(--text-m); font-weight: 600;
  color: var(--color-accent); cursor: pointer;
}
details.klappe > summary:hover { color: var(--color-accent-700); }
details.klappe[open] > summary { margin-bottom: var(--space-2); }

/* Das Loeschen ist ein EIGENES Formular -- es muss eines sein -- und steht
   deshalb nicht im Fuss, sondern darunter, mit sichtbarem Abstand. */
.loeschen {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3);
  margin-top: var(--space-6);
}
.loeschen .notiz { margin: 0; }

/* --------------------------------------------------------------- Dashboard */

/* DAS KACHELRASTER bricht selbst um. Eine feste Spaltenzahl waere nach der
   ersten Aenderung an der Zusammenstellung falsch -- und die aendert sich,
   solange man mit der Seite arbeitet. Aus style.css uebernommen, die Zahl
   300 px ist dieselbe geblieben. */
.kacheln {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: var(--space-4);
}
.kachel {
  border: 1px solid var(--color-rahmen);
  background: var(--color-surface);
  padding: var(--space-3) var(--space-4);
}
.kachel h3 {
  margin: var(--space-4) 0 var(--space-2);
  font-size: var(--text-m); color: var(--color-neutral-700);
}
/* EINE TABELLE IN EINER KACHEL HAT 300 px, nicht 1337. Der Seitenabstand der
   grossen Listen (`--rand-tabelle`, 20,4 px in den aeusseren Zellen) schiebt
   dort die letzte Spalte aus dem Kasten; gemessen ragte "netto" in der Kachel
   Vertragsgruppen ueber den rechten Rand hinaus. Innen genuegt die normale
   Zellenpolsterung, und was dann noch zu breit ist, scrollt im Kasten --
   NICHT die Seite. */
.kachel table { width: 100%; }
.kachel .tabelle-rahmen { overflow-x: auto; }
/* ZWEI KACHELN TRAGEN EINE ECHTE TABELLE und brauchen deshalb zwei Spalten:
   "Vertragsgruppen" mit fuenf, "Stimmt der Bestand?" mit vier. In 300 px
   scrollten beide seitlich, und eine Zeilenbeschriftung wie "Alle Buchungen
   (Vorzeichen)" brach dreizeilig um. Der Scrollkasten oben bleibt trotzdem --
   er ist die Rueckfallebene, keine Loesung.

   ZURUECKGESETZT, BEVOR DAS RASTER EINSPALTIG WIRD: Ein `span 2` in einem
   einspaltigen Raster erzeugt eine zweite Spalte, die es nicht geben soll, und
   die Seite schiebt seitlich. */
.kachel-breit { grid-column: span 2; }
@media (max-width: 900px) { .kachel-breit { grid-column: auto; } }
.kachel table.liste th:first-child,
.kachel table.liste td:first-child { padding-left: var(--space-2); }
.kachel table.liste th:last-child,
.kachel table.liste td:last-child { padding-right: var(--space-2); }
/* Ein Zeilenkopf ist keine Spaltenueberschrift: kein Versalsatz, keine
   Sperrung -- er benennt die Zeile und wird gelesen wie Text. */
.kachel table.liste th[scope="row"] {
  font-family: var(--font-body); font-size: var(--text-l); font-weight: 600;
  letter-spacing: 0; text-transform: none;
  color: var(--color-text); background: none;
  white-space: normal;
}
.kachel > :last-child { margin-bottom: 0; }

/* DER KACHELKOPF -- Titel links, das `i` rechts. `.seiten-kopf` taugt hier
   nicht: Der ist ein Band ueber die volle Bahnbreite und waere in einer 300 px
   breiten Kachel keine Zeile, sondern ein Block.

   AUFGEKLAPPT NIMMT DAS `i` DIE GANZE BREITE. Sonst stuende der Erklaertext in
   der Spalte des Symbols, also zwanzig Zeichen breit am rechten Rand. Der
   Preis ist, dass das Symbol beim Aufklappen nach links wandert -- das ist
   sichtbar und deshalb verstaendlich, ein zweispaltiger Textschlauch waere es
   nicht. */
/* EIN RASTER, WEIL DAS `i` STEHENBLEIBEN MUSS (finflow-22-v1-upd-1, gemeldet vom
   Auftraggeber). Vorher war der Kopf ein umbrechender Flexkasten, und das
   geoeffnete `<details>` nahm sich mit `flex: 1 1 100%` eine eigene Zeile --
   damit wanderte das `i` beim Klick 28 px nach unten und 287 px nach links,
   also von der Ecke an den Anfang der naechsten Zeile. Wer es wieder zuklappen
   will, sucht es dort, wo es war.

   `display: contents` am `<details>` ist der Kniff: Das Element selbst
   verschwindet aus dem Raster, seine beiden Kinder werden zu Rasterfeldern.
   Das `<summary>` sitzt fest in Zeile 1 rechts, der Erklaertext in Zeile 2
   ueber die volle Breite. Das Auf- und Zuklappen bleibt Sache des Browsers --
   kein JavaScript, wie schon zuvor (die Seite sendet script-src 'self').

   GEMESSEN, NICHT ANGENOMMEN: dass ein geschlossenes `<details>` mit
   `display: contents` seinen Inhalt weiterhin verbirgt, ist keine
   Selbstverstaendlichkeit -- es haengt daran, wie der Browser den
   Inhaltsbereich baut. pruef_dashinfo.js haelt beides fest. */
.kachel-kopf {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  /* KEIN ZEILENABSTAND. Der Browser haengt unter das `<summary>` einen eigenen
     Inhaltsbereich, und der bleibt auch zugeklappt ein Rasterfeld -- 0 px hoch,
     aber mit Zeilenabstand davor. Der Kachelkopf war dadurch 6 px hoeher als
     zuvor. Den Abstand zum Erklaertext bringt der Text selbst mit. */
  gap: 0 var(--space-2);
  margin-bottom: var(--space-3);
}
.kachel-kopf h2 { grid-column: 1; grid-row: 1; min-width: 0; font-size: var(--text-xl); }
.kachel-kopf details.info { display: contents; }
/* DAS GESCHLOSSENE FELD MUSS AUS DEM RASTER, nicht nur aus dem Blick. Der
   Browser verbirgt den Inhalt eines zugeklappten `<details>` ueber
   `content-visibility` -- der Kasten bleibt dabei ein Rasterfeld und nahm
   seine Zeile samt Aussenabstand mit: Jeder Kachelkopf war zugeklappt 6 px
   hoeher als vorher. Gemessen, nicht vermutet. */
.kachel-kopf details.info:not([open]) > p { display: none; }
.kachel details.info > summary {
  grid-column: 2; grid-row: 1; justify-self: end;
  display: inline-block; width: 18px; height: 18px; line-height: 16px;
  text-align: center;
  border: 1px solid var(--color-rahmen); border-radius: 50%;
  font-family: var(--font-heading); font-size: var(--text-xs); font-style: italic;
  color: var(--color-accent);
  padding: 0; list-style: none; cursor: pointer;
}
.kachel details.info > summary::-webkit-details-marker { display: none; }
.kachel details.info > summary:hover { border-color: var(--color-accent-700); }
.kachel details.info p {
  grid-column: 1 / -1;
  margin: var(--space-2) 0 0;
  padding-left: var(--space-2);
  border-left: 2px solid var(--color-rahmen-leise);
  font-size: var(--text-m); color: var(--color-neutral-700);
}

/* BESCHRIFTUNG LINKS, ZAHL RECHTS -- die dritte Form neben der Kopfleiste und
   der Zahlentafel, und die einzige, die in eine Kachel passt. Eine
   `table.liste` mit Kopfzeile waere dort mehr Geruest als Inhalt; die
   Zahlentafel setzt den Wert UNTER die Aufschrift und braucht die Breite.
   Hiess bis zum 14.09.2026 `.zeilen` und lag in style.css. */
.wert-zeilen { list-style: none; margin: 0; padding: 0; }
.wert-zeilen li {
  display: flex; align-items: baseline; gap: var(--space-2);
  padding: var(--space-1) 0;
}
.wert-zeilen li .num,
.wert-zeilen li .klein { margin-left: auto; }
/* Eine Null verschwindet NICHT, sie steht nur blass da -- sonst waere nicht zu
   unterscheiden, ob nichts ansteht oder ob nicht geprueft wurde. */
.wert-zeilen li.leer { color: var(--color-neutral-600); }
.wert-zeilen .zahl {
  font-family: var(--font-zahl); font-weight: 600;
  min-width: 2.2em; text-align: right;
}
.wert-zeilen .num.warnzahl { color: var(--color-fehler); font-weight: 600; }
.kachel-summe { margin: var(--space-2) 0 0; font-weight: 600; }

/* DER VERLAUF IST EIN <svg>, und die Balkenhoehe steht in ATTRIBUTEN, nicht in
   einem style-Attribut: `style-src 'self'` ohne 'unsafe-inline' verwirft das
   lautlos, und genau daran sind die Balken beim ersten Bauen gescheitert --
   im Markup gesetzt, vom Browser auf 0 gerechnet. Farben kommen von hier,
   Geometrie aus dem Markup. Weiterhin ohne Bibliothek: `script-src 'self'`
   wuerde eine von aussen geladene lautlos blockieren. */
.verlauf { display: block; width: 100%; height: 130px; margin: var(--space-1) 0; }
.verlauf .ein { fill: var(--color-plus); }
.verlauf .aus { fill: var(--color-minus); }
.verlauf-achse { display: flex; gap: var(--space-1); margin: 0 0 var(--space-2); }
.verlauf-achse span {
  flex: 1; text-align: center;
  font-size: var(--text-winzig); color: var(--color-neutral-600);
}
.punkt { display: inline-block; width: 9px; height: 9px; vertical-align: middle; }
.punkt.ein { background: var(--color-plus); }
.punkt.aus { background: var(--color-minus); }

/* ----------------------------------------------------------- Zahlentafel */

/* EIN RASTER AUS AUFSCHRIFT UND WERT, fuer sich stehend -- die vier
   Kennzahlen je Quelle, und ab dem Dashboard-Paket auch die der Kacheln.
   Ersetzt `dl.kennzahlen` aus style.css.

   SIE IST NICHT `.kopf-zahlen`, und das ist keine Doppelung: Die Kopfleiste
   traegt WENIGE, KURZE Werte in EINER Zeile neben einer Ueberschrift; die
   Tafel traegt beliebig viele, auch lange, und bricht selbst um. Wer beides in
   ein Bauteil zwingt, bekommt entweder eine umbrechende Kopfzeile oder eine
   Tafel, die sich wie eine Zeile verhaelt. Entschieden am 14.09.2026 mit dem
   Auftraggeber, nachdem sechs Kennzahlen je Quelle die Kopfleiste bei 1100 px
   dreizeilig gemacht haetten.

   `auto-fit` statt einer festen Spaltenzahl: Die Tafel weiss nicht, wie viele
   Felder sie bekommt, und soll es auch nicht wissen muessen. */
.zahlen-tafel {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: var(--space-3) var(--space-6);
  /* Abstand NACH UNTEN, weil darunter die Warnungen und Hinweise stehen und
     beide ihren Aussenabstand selbst nur nach unten tragen. `.blatt >
     :last-child` nimmt ihn wieder weg, wenn die Tafel den Schluss bildet. */
  margin: 0 0 var(--space-4);
}
.zahlen-tafel > div { min-width: 0; }
.zahlen-tafel dt {
  font-family: var(--font-heading);
  font-size: var(--text-xs); font-weight: 700;
  letter-spacing: .11em; text-transform: uppercase;
  color: var(--color-neutral-700);
}
.zahlen-tafel dd {
  margin: var(--space-1) 0 0;
  font-size: var(--text-l); color: var(--color-text);
}

/* Der Kontrollpunkt sagt "stimmt" oder nennt die Differenz. Eigene Namen, weil
   `.ok` in style.css seit langem gruenen Text bedeutet und `.abweichung` roten
   -- zwei Regelwerke duerfen nicht ueber denselben Namen streiten. */
.ist-gut      { color: var(--color-ok);     font-weight: 600; }
.ist-schlecht { color: var(--color-fehler); font-weight: 600; }

/* Startsaldo und Kontrollpunkt, aufklappbar. Lag bis zum 14.09.2026 in
   style.css. Die beiden FORMULARE darin waren bis Paket 29 unveraendert --
   die letzten Masken der Anwendung mit blanken `<label>` und einem Knopf ohne
   Fuss. Sie tragen jetzt `.formular` wie jede andere; hier steht nur noch,
   dass sie NEBENEINANDER stehen. */
.salden { margin: 0; }
.salden > summary {
  cursor: pointer;
  font-family: var(--font-heading);
  font-size: var(--text-m); color: var(--color-accent);
}
.salden-formulare {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: var(--space-6); padding-top: var(--space-3);
}
.salden-formulare h3 { margin: 0 0 var(--space-2); font-size: var(--text-xl); }
/* Die Lesebreite kommt hier aus der Rasterspalte, nicht aus `.formular` --
   sonst stuenden zwei 46rem breite Formulare in zwei halben Spalten. */
.salden-formulare .formular { max-width: none; }
@media (max-width: 720px) { .salden-formulare { grid-template-columns: 1fr; } }

/* -------------------------------------------------------------------- Marken */

/* DIE MARKE -- ein kurzes Wort in einer Kapsel: ein Zustand, eine Gruppe, ein
   Schlagwort. Lag bis zum 14.09.2026 in style.css mit drei Hexcodes und war
   damit die letzte Klasse, die in beiden Regelwerken etwas zu sagen hatte.

   VARIANTEN STATT EIGENER KLASSEN JE ORT: `ok` fuer einen Zustand, der in
   Ordnung ist, `gefahr` fuer einen, der Aufmerksamkeit verlangt, `leise` fuer
   einen abgeschalteten. Der Entwurf des Auftraggebers zeigt die
   Zustandsspalte der Vertragsliste gruen -- "aktiv" ist eine Aussage und
   keine Aufzaehlung. */
.marke {
  display: inline-block; vertical-align: middle;
  padding: 1px var(--space-2);
  border-radius: 10px;
  background: var(--color-neutral-200); color: var(--color-neutral-700);
  font-size: var(--text-xs); font-weight: 600;
}
/* EIGENE NAMEN, KEINE ALLGEMEINEN BEIWOERTER. `.marke.ok` waere kuerzer --
   aber `.ok` und `.gefahr` gibt es in style.css bereits mit anderer Bedeutung
   (gruener Text, roter Knopf), und `pruef_css.php` zaehlt jede Klasse, die in
   beiden Regelwerken etwas zu sagen hat. Eine Variante, die nur im Verbund
   gilt, traegt deshalb den Namen ihres Bauteils. */
.marke-ok     { background: var(--color-ok-grund);    color: var(--color-ok); }
.marke-leise  { background: var(--color-neutral-200); color: var(--color-neutral-600); }
/* `.gefahr-marke` steht seit Paket 09 im Markup und behaelt seinen Namen --
   umbenennen waere eine Aenderung an dreizehn Stellen ohne Gegenwert. */
.gefahr-marke { background: var(--color-fehler-grund); color: var(--color-fehler); }
/* Die Instanz-Marke steht nur, wenn eine abweichende Datenbank laeuft --
   auf dem Produktivsystem erscheint sie nie. */
.marke.instanz {
  background: var(--color-fehler-grund); color: var(--color-fehler);
  font-size: var(--text-s); margin-left: var(--space-2);
}

/* ------------------------------------------------------------------ Hinweise */

/* Die Rueckfrage vor dem Zuordnen. Kraeftiger als ein Hinweis: Das ist keine
   Randbemerkung, sondern eine Frage, die eine Antwort braucht, bevor die
   Zuordnung zustande kommt. */
.rueckfrage {
  margin: 0; padding: var(--space-4) var(--space-6);
  background: var(--color-warn-grund);
  border-bottom: 1px solid var(--color-warn-rahmen);
  border-left: 3px solid var(--color-warn);
  color: var(--color-warn-text);
}
.rueckfrage p { margin: 0 0 var(--space-2); font-size: var(--text-m); }
.rueckfrage p:last-of-type { margin-bottom: 0; }
.rueckfrage strong { color: var(--color-warn); }
.rueckfrage form { margin-top: var(--space-3); }

/* Der Bestandssatz und die Testdaten-Warnung stehen zwischen Filter und
   Tabelle. Der erste ERKLAERT dauerhaft, die zweite WARNT bedingt -- deshalb
   traegt nur die zweite Farbe. */
.filterblatt ~ .bestand,
.app-inhalt > .bestand {
  margin: 0; padding: var(--space-3) var(--space-6);
  background: var(--color-bg);
  border-bottom: 1px solid var(--color-divider);
  border-left: 0; border-radius: 0;
  font-size: var(--text-m); color: var(--color-neutral-700);
}
/* DER WARNHINWEIS -- die Grundform. Lag bis zum 14.09.2026 nur in style.css
   mit drei Hexcodes; system.css kannte ihn ausschliesslich als BAND ueber der
   Liste (die beiden Regeln darunter). Sobald einer in einem `.blatt` stand --
   in der Auswertung stehen drei --, faerbte ihn wieder das alte Regelwerk.
   Ein Bauteil braucht seine Grundform dort, wo es beschrieben ist. */
.warnung-inline {
  margin: 0 0 var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--color-warn-grund);
  border: 1px solid var(--color-warn-rahmen);
  border-left: 3px solid var(--color-warn);
  font-size: var(--text-m); color: var(--color-warn-text);
}
/* ALS BAND ueber der Liste: randlos, volle Breite, nur unten abgesetzt. */
.filterblatt ~ .warnung-inline,
.app-inhalt > .warnung-inline {
  margin: 0; padding: var(--space-3) var(--space-6);
  background: var(--color-warn-grund);
  border: 0; border-bottom: 1px solid var(--color-warn-rahmen);
  border-left: 3px solid var(--color-warn);
  border-radius: 0;
  font-size: var(--text-m); color: var(--color-warn-text);
}

/* Die Zeile ueber der gefilterten Regelliste: worauf gefiltert ist und der
   Weg zurueck. Ohne sie sieht die Liste aus wie die vollstaendige, nur kuerzer.
   `.karte` ist noch aus style.css -- die Regelansicht ist nicht umgestellt.
   Diese Zeile gehoert trotzdem ins Tokensystem: Sie ist neu. */
.regel-filter {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: var(--space-1) var(--space-2);
  margin: 0 0 var(--space-4);
  padding: var(--space-2) var(--space-4);
  background: var(--color-accent-50);
  border: 1px solid var(--color-accent-100);
  font-size: var(--text-l); color: var(--color-neutral-700);
}
/* `.blatt p { max-width: 80ch }` deckelt sonst die Leiste auf
   zwei Drittel der Tabelle darunter -- sie saehe aus wie ein Absatz, nicht
   wie eine Leiste. `.karte` DAVOR, sonst verliert die Regel gegen jene:
   zwei Klassen schlagen eine Klasse plus einen Typ nicht, andersherum schon. */
.blatt .regel-filter { max-width: none; }
.regel-filter .leise { color: var(--color-neutral-600); }
.regel-filter .zurueck-link { margin-left: auto; }

/* PASST oder HAT ZUGEORDNET. Die gefuellte Marke ist die Antwort auf "warum
   hat diese Buchung diese Kategorie", die leise sagt "diese Regel haette es
   auch getan, kam aber zu spaet". Nicht `table.liste .marke-auto` benutzt:
   Die ist auf die Arbeitsliste beschraenkt und traegt EIN Zeichen. */
.regel-marke {
  display: inline-block; vertical-align: baseline;
  padding: 0 var(--space-1);
  border: 1px solid var(--color-rahmen-leise);
  background: var(--color-blatt-fuss); color: var(--color-neutral-700);
  font-family: var(--font-heading); font-size: var(--text-xs); font-weight: 600;
  white-space: nowrap;
}
.regel-marke.hat {
  border-color: var(--color-accent-700);
  background: var(--color-accent-700); color: var(--color-auf-akzent);
}

/* Ein Leerzustand ist kein Beisatz, sondern der ganze Inhalt der Seite --
   also steht er auch dort, wo man hinsieht: in der Mitte der freien Flaeche.
   40vh ist genug, dass er nicht oben klebt, und wenig genug, dass er bei
   kleinem Fenster nichts wegschiebt. */
.leer-hinweis {
  display: grid; place-content: center; justify-items: center;
  gap: var(--space-2);
  min-height: 40vh;
  padding: var(--space-8) var(--space-6);
  text-align: center;
}
.leer-titel {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-xl); font-weight: 600;
  color: var(--color-text);
}
.leer-text {
  margin: 0; max-width: 46ch;
  font-size: var(--text-l); color: var(--color-neutral-600);
}
.leer-text strong { color: var(--color-text); }

/* --------------------------------------------------------------- Anmeldeseite
   ARBEITET VOLLSTAENDIG OHNE JAVASCRIPT. Wer sich nicht anmelden kann, kann
   auch nichts melden -- das ist die eine Ausnahme, die auch nach der
   Freigabe von JS-only in Paket 15 bleibt. */

.anmeldung {
  min-height: 100vh;
  display: flex; align-items: center; justify-content: center;
  padding: var(--space-6);
  background: var(--color-bg);
}
/* HIESS BIS PAKET 24 `.karte`. Der Name war der letzte Halt, den style.css
   in dieser Ansicht hatte -- und mit ihm hing `.karte input { max-width:
   32rem }` an einem Kasten statt an einer Sache. */
.anmelde-blatt {
  width: min(360px, 100%);
  display: flex; flex-direction: column; gap: var(--space-3);
  padding: var(--space-8);
  background: var(--color-surface);
  border: 1px solid var(--color-neutral-300); border-radius: 0;
}
.anmelde-blatt h1 {
  font-family: var(--font-heading); font-weight: 700;
  font-size: var(--titel); letter-spacing: .02em; margin: 0;
}
.anmelde-blatt form { display: flex; flex-direction: column; gap: var(--space-3); }
.anmelde-blatt .btn { width: 100%; justify-content: center; }
.anmelde-blatt .notiz { font-size: var(--text-s); color: var(--color-neutral-600); margin: 0; }

/* ===========================================================================
   DIE LETZTEN REGELN AUS style.css -- Paket 24, 15.09.2026.

   style.css ist geloescht. Was hier steht, hat dort bis heute gewirkt und
   hatte anderswo keinen Platz: kleine Bauteile, die in keine der grossen
   Gruppen gehoeren. Uebernommen mit Tokens, nicht mit Hexcodes.

   ERSATZLOS ENTFALLEN sind alle ELEMENTSELEKTOREN -- `table`, `th, td`, `th`,
   `label`, `input[type=text|password|number]`, `button`, `details`, `summary`
   und `h1`. Sie waren der eigentliche Grund, warum die Datei bis zuletzt
   gebraucht wurde, und zugleich der Grund, warum niemand sagen konnte, was
   sie noch tut: Eine Klassenzaehlung sieht einen Elementselektor nicht.
   `th { text-transform: uppercase }` hat bis Paket 22 zwei Dashboard-Tabellen
   mitgestaltet, ohne in irgendeiner Auswertung aufzutauchen.

   Ebenso entfallen, weil ihr letzter Ort mit Paket 24 wegfiel: `.karte`,
   `.kennzahlen`, `.kompakt`, `.zaehler`, `.sekundaer`, `.filter`, `.ok`,
   `.werkzeuge` und `.aktionen` -- die Sammelleiste bringt ihre eigene Fassung
   mit.
   =========================================================================== */

/* Ein fehlender Wert. 69-mal im Markup -- der haeufigste Einzelfall der App.
   In der Liste gilt die eigene Fassung weiter (table.liste .leer). */
.leer { color: var(--color-neutral-600); }

/* Was nie umbrechen darf: Ein ISO-Datum ist zehn Zeichen und liest sich ueber
   zwei Zeilen als zwei Zahlen. */
.nowrap { white-space: nowrap; }

/* Ein Formular, das in einer Zeile steht statt als Block -- in Listenzellen,
   in Leisten, neben einem Text. Die Bauteile darum geben ihm seine Form;
   hier steht nur, dass es keine eigene Zeile bekommt. */
.inline { display: inline; }

/* Die kleine Schrift in der Listenzelle. Stand als `td.klein` in style.css
   und WIRKTE dort: `table.liste td` setzt weder Groesse noch Farbe, also gab
   es nichts, was sie ueberstimmt haette. */
table.liste td.klein {
  font-size: var(--text-m);
  color: var(--color-neutral-800);
  word-break: break-word;
}

/* Eine Zeile, die noch nicht gilt -- kuenftiger Soll-Termin, beendeter
   Vertrag. Gedaempft, nicht versteckt. */
table.liste tr.inaktiv td { opacity: .55; }

/* Eine Gruppe von Schaltern mit gemeinsamer Frage. Das `<fieldset>` ist hier
   die richtige Klammer und nicht Zierrat: Die Auswahl gilt nur zusammen. */
.wahl {
  border: 1px solid var(--color-rahmen-leise); border-radius: 0;
  padding: var(--space-3) var(--space-4);
  margin: 0 0 var(--space-4);
}
.wahl legend {
  padding: 0 var(--space-2);
  font-family: var(--font-heading); font-size: var(--text-xs); font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--color-neutral-700);
}

/* Der Ausgang eines Abrufs im Protokoll. Drei Zustaende, drei Toene -- eine
   Spalte, in der jede Zeile gleich aussieht, koennte man auch weglassen. */
.status {
  display: inline-block; padding: 1px var(--space-2);
  font-size: var(--text-s); font-weight: 600;
}
.status-erfolg      { background: var(--color-ok-grund);     color: var(--color-ok); }
.status-abgebrochen { background: var(--color-warn-grund);   color: var(--color-warn-text); }
.status-fehler      { background: var(--color-fehler-grund); color: var(--color-fehler); }

/* Die Unterkategorien unter ihrer Oberkategorie. */
.unterkat { margin: var(--space-2) 0 0; padding-left: var(--space-5); }
.unterkat li { margin: var(--space-1) 0; }

/* -------------------------------------------- Meldungen ausserhalb der App
   Die Meldungsleiste gibt es nur in der Anwendung. Diese beiden stehen, wo
   es keine gibt: auf der ANMELDESEITE, vor dem Eintippen des Kennworts. */
.hinweis {
  margin: 0; padding: var(--space-2) var(--space-3);
  background: var(--color-warn-grund); color: var(--color-warn-text);
  border: 1px solid var(--color-warn-rahmen);
  font-size: var(--text-m);
}
.fehler {
  margin: 0; padding: var(--space-2) var(--space-3);
  background: var(--color-fehler-grund); color: var(--color-fehler);
  border: 1px solid var(--color-fehler-rahmen);
  font-size: var(--text-m);
}

/* Die Sperrwarnung -- schwebt ueber allem, weil sie in dem Moment kommt, in
   dem die Sitzung ablaeuft, und dann nicht am Seitenende stehen darf. */
.warnung {
  position: fixed; left: 50%; bottom: var(--space-6); transform: translateX(-50%);
  z-index: 50;
  padding: var(--space-3) var(--space-5);
  background: var(--color-warn-grund); color: var(--color-warn-text);
  border: 1px solid var(--color-warn-rahmen);
  box-shadow: 0 2px 8px rgba(0, 0, 0, .12);
}

/* ---------------------------------------------------- Telefon und Tablet ----
   Bei 820 px traegt die Seitennavigation nachweislich. Unterhalb von 720 px
   wird daraus wieder eine Leiste ueber dem Inhalt -- also das Layout von
   frueher. Kein neuer Mechanismus, kein JavaScript, und dieselbe Schwelle,
   die style.css schon zweimal verwendet.
   -------------------------------------------------------------------------- */

@media (max-width: 720px) {
  /* Die feste Schiene wird wieder eine Leiste oben, und die Bahn beginnt am
     linken Rand statt hinter der Schiene. Eine Leiste kann nicht schmal
     werden -- der Einklapp-Schalter ist hier sinnlos und verschwindet. */
  .app-navi {
    position: static; z-index: auto;
    width: auto;
    flex-direction: column;
    border-right: 0;
    border-bottom: 1px solid var(--color-neutral-300);
  }
  .navi-schalter { display: none; }
  .meldungen, .app-inhalt {
    max-width: none; margin-left: 0; margin-right: 0;
    border-left: 0; border-right: 0;
  }
  /* Hier steht die Navigation als Leiste UEBER der Bahn. `100dvh` waere
     zusammen mit ihr hoeher als das Fenster und erzeugte eine Seite, die um
     die Hoehe der Leiste scrollt, ohne dass etwas zu sehen waere. */
  .app-inhalt { min-height: 0; }
  .navi-punkte {
    overflow: visible; padding: var(--space-2);
    display: flex; flex-wrap: wrap; align-items: center;
    gap: var(--space-1) var(--space-2);
  }
  .navi-gruppe { padding: 0 var(--space-2); width: auto; cursor: default; }
  .navi-gruppe .pfeil { display: none; }
  /* `contents` statt `block`: Die Eintraege bleiben direkte Flex-Kinder der
     Leiste und brechen weiter einzeln um. Ein Block-Container machte aus
     jedem Block EIN Flex-Element -- drei Reihen statt eines Flusses. */
  .navi-block { display: contents; }
  .navi-punkt {
    border-left: 0; border-bottom: 3px solid transparent;
    padding: var(--space-2) var(--space-3);
  }
  .navi-punkt.hier { border-left: 0; border-bottom-color: var(--color-accent-700); }
  .navi-fuss { flex-direction: row; flex-wrap: wrap; align-items: center; }
  .navi-fuss .btn-abmelden { width: auto; }

  .seiten-kopf,
  .arbeits-leiste,
  .filter-kopf,
  .filter-reiter-zeile,
  .filter-tafel,
  .chips,
  .sammelaktionen,
  .meldung,
  .legende,
  .blaetter { padding-left: var(--space-4); padding-right: var(--space-4); }
  /* .tabelle-rahmen steht bewusst NICHT in dieser Liste: Sein Seitenabstand
     sitzt in den aeusseren Zellen. Hier schrumpft er mit den Baendern mit. */
  :root { --rand-tabelle: var(--space-4); }

  .kopf-zahlen     { margin-left: 0; }
  .chips .hinweis     { flex-basis: 100%; }

  table.liste.buchungen th { position: static; }  /* auf kleinem Schirm frisst
                                            ein klebender Kopf zu viel Hoehe */

  /* DIE TABELLE MUSS SCHRUMPFEN KOENNEN. Mit table-layout:fixed, festen
     Spaltenbreiten und `white-space: nowrap` auf jeder Zelle kann sie das
     nicht -- sie bleibt 1028 px breit, und dann scrollt die SEITE seitlich,
     nicht der Kasten um die Tabelle. Nachgemessen bei 390 px: 911 statt 390.
     `overflow-x: auto` am Kasten allein genuegt dagegen nicht.

     Paket 13 hatte das nicht, weil dort nur Zweck und Buchungstext nicht
     umbrachen. Die festen Breiten sind eine Aussage ueber den Schreibtisch --
     unter 720 px gilt sie nicht, dort sortiert sich die Tabelle nach ihrem
     Inhalt. */
  table.liste { table-layout: auto; min-width: 0; }
  table.liste thead th { width: auto; }
  table.liste td, table.liste th { white-space: normal; }
  table.liste td.zweck,
  table.liste td.buchungstext { max-width: 14ch; white-space: nowrap; }
}
