/*
 * GSVBW Zion — Design-System (V1)
 *
 * Ersetzt `base.css` vollständig (ein einziges Stylesheet, siehe
 * docs/design/design-system.md für Begründung und Kontrastnachweise).
 *
 * Aufbau: Tokens (Primitive → Semantisch → Status) → Reset/Basis → Layout
 * → Typografie → Komponenten (Button, Formular, Tabelle, Badge, Hinweis,
 * Karte) → Hilfsklassen → Responsiv → Statuszeile/Datenliste/Timeline
 * (§14, mit der Antrags-Statusseite nachgetragen).
 *
 * Bewusst KEINE Webfonts (CSP `default-src 'self'`, kein externes Laden) —
 * reiner System-Font-Stack. Bewusst kein CSS-Framework: die Oberfläche ist
 * klein genug, dass ein handgeschriebenes System nachvollziehbarer bleibt.
 */

/* ------------------------------------------------------------------ */
/* 1. Tokens                                                           */
/* ------------------------------------------------------------------ */

:root {
  /* -- Primitive: Neutral (Text, Ränder, Hintergründe) -------------- */
  --c-weiss: #ffffff;
  --c-grau-900: #1f2933; /* Fließtext, Überschriften */
  --c-grau-700: #3e4c59; /* Sekundärtext, Labels */
  --c-grau-500: #7b8794; /* nur große/dekorative Elemente, KEIN Fließtext (3,66:1) */
  --c-grau-300: #cbd2d9; /* Ränder */
  --c-grau-100: #f0f2f4; /* zarte Flächen, Tabellenstreifen */
  --c-grau-50: #f8f9fa; /* Seitenhintergrund */

  /* -- Primitive: Petrol (Verbandsfarbe, von gsvbw.de abgeleitet) ---- */
  /* gsvbw.de (Theme "twentytwelve") nutzt #21759b als Linkfarbe und
     #0f3647 als Hover-Ton — das einzige bewusst wiederkehrende
     Gestaltungselement der bestehenden Seite. Für Buttons/Text wird der
     Ton etwas abgedunkelt (#155273), weil das den Kontrast auf Weiß von
     5,1:1 auf 8,5:1 hebt, ohne den Wiedererkennungswert zu verlieren. */
  --c-petrol-900: #0f3647; /* aktiv/hover, Fokusring */
  --c-petrol-700: #155273; /* Primärfarbe: Buttons, Links, Kopfzeile */
  --c-petrol-600: #1c6a8e; /* sekundäre Akzente */
  --c-petrol-100: #e3eef2; /* helle Flächen (Badge, Hinweis) */
  --c-petrol-50: #f2f7f9;

  /* -- Primitive: Rot (Gefahr/Fehler) -------------------------------- */
  --c-rot-800: #7a1414;
  --c-rot-700: #9c1c1c;
  --c-rot-100: #fbe4e4;

  /* -- Primitive: Grün (Erfolg/genehmigt) ---------------------------- */
  --c-gruen-700: #1e6b34;
  --c-gruen-100: #e2f2e6;

  /* -- Primitive: weitere Statustöne (Antragsstatus, siehe unten) ---- */
  --c-bernstein-700: #7a5a05;
  --c-bernstein-100: #fdf3d9;
  --c-orange-700: #8a3d0a;
  --c-orange-100: #fde8d9;
  --c-taupe-700: #6b4f3a;
  --c-taupe-100: #ece4da;
  --c-violett-700: #5b3a91;
  --c-violett-100: #ece4f5;

  /* -- Primitive: NUR Dunkelmodus (Phase E2, T1) ---------------------
     Eigene Stufen statt Wiederverwendung der Hell-Primitiven mit
     vertauschter Rolle — Zahl = Dunkelheitsgrad, wie beim Rest der Skala
     (900 dunkler als 700 dunkler als 500 …), hier bis 950 (sehr dunkel,
     Hintergründe) bzw. bis 40/200/300 (sehr hell, Text/Akzent) erweitert.
     Alle Werte gegen die jeweilige Hintergrund-Stufe mit der WCAG-Formel
     geprüft (Test-Hilfsskript `test/zion-css-kontrast.test.js`),
     Text/Text-Hintergrund-Paare liegen bei ≥ 7,2:1 (Ziel 4,5:1). */
  --c-grau-950: #12181d; /* Seitenhintergrund Dunkelmodus */
  --c-grau-850: #29333d; /* zarte Flächen Dunkelmodus (Tabellenstreifen) */
  --c-grau-820: #262f37; /* Status "unbestätigt"-Hintergrund Dunkelmodus */
  --c-grau-650: #4a5762; /* Ränder Dunkelmodus */
  --c-grau-350: #c3ccd3; /* Status "unbestätigt"-Text Dunkelmodus */
  --c-grau-250: #aab8c3; /* Sekundärtext Dunkelmodus */
  --c-grau-40: #eef1f3; /* Fließtext Dunkelmodus */
  /* `--c-grau-900` (Zeile oben, #1f2933) wird im Dunkelmodus als Karten-/
     Inhaltshintergrund wiederverwendet — als reine Farbe passt sie, nur
     ihre Rolle wechselt. */

  --c-petrol-300: #7cc3e0; /* Primärfarbe Dunkelmodus (Text/Akzent) */
  --c-petrol-200: #9ad2e8; /* Hover/Fokus Dunkelmodus (heller = Hervorhebung) */
  --c-petrol-950: #132029; /* zarte Flächen Dunkelmodus */

  --c-rot-300: #f3a6a6; /* Gefahr/Fehler Dunkelmodus */
  --c-rot-200: #f7c2c2; /* Gefahr-Hover Dunkelmodus */
  --c-rot-950: #3a1414; /* Gefahr/Fehler-Fläche Dunkelmodus */

  --c-gruen-300: #8fdba6; /* Erfolg/genehmigt Dunkelmodus */
  --c-gruen-950: #12331d;

  --c-orange-300: #f0b27a; /* Warnung Dunkelmodus */
  --c-orange-950: #3a2308;

  --c-bernstein-300: #e6c877; /* Status "in Prüfung" Dunkelmodus */
  --c-bernstein-950: #332a0a;

  --c-taupe-300: #d4bfa8; /* Status "verfallen" Dunkelmodus */
  --c-taupe-950: #33291c;

  --c-violett-300: #c6aee6; /* Status "zurückgezogen" Dunkelmodus */
  --c-violett-950: #291f3a;

  /* -- Semantisch ----------------------------------------------------- */
  --farbe-text: var(--c-grau-900);
  --farbe-text-schwach: var(--c-grau-700);
  --farbe-text-auf-primaer: var(--c-weiss);
  --farbe-rand: var(--c-grau-300);
  --farbe-hintergrund: var(--c-weiss);
  --farbe-hintergrund-seite: var(--c-grau-50);
  --farbe-hintergrund-zart: var(--c-grau-100);

  --farbe-primaer: var(--c-petrol-700);
  --farbe-primaer-hover: var(--c-petrol-900);
  --farbe-primaer-zart: var(--c-petrol-100);

  --farbe-gefahr: var(--c-rot-700);
  --farbe-gefahr-hover: var(--c-rot-800);
  --farbe-gefahr-zart: var(--c-rot-100);

  --farbe-erfolg-text: var(--c-gruen-700);
  --farbe-erfolg-bg: var(--c-gruen-100);
  --farbe-fehler-text: var(--c-rot-700);
  --farbe-fehler-bg: var(--c-rot-100);
  --farbe-hinweis-text: var(--c-petrol-700);
  --farbe-hinweis-bg: var(--c-petrol-100);
  --farbe-warnung-text: var(--c-orange-700);
  --farbe-warnung-bg: var(--c-orange-100);

  /* Phase S1: Staging-/Entwicklungs-Balken (`partials/umgebung-banner.ejs`)
     — eine VOLLE Fläche statt des zarten Warnungs-Tons, damit er nicht mit
     einem Hinweis-Kasten verwechselt wird. Weiß auf orange-700: 7,5:1. */
  --farbe-umgebung-text: var(--c-weiss);
  --farbe-umgebung-bg: var(--c-orange-700);

  /* E2-Review H2: eigene semantische Tokens statt der bisher hart auf
     `.button:disabled` kodierten `--c-grau-300`/`--c-grau-700` — die waren
     Hell-Primitive, die im Dunkelmodus unverändert hell blieben (ein
     leuchtender grauer Block auf dunklem Grund). grau-700 auf grau-300
     bleibt bei 5,77:1 (Test: `test/zion-css-kontrast.test.js`). */
  --farbe-deaktiviert-bg: var(--c-grau-300);
  --farbe-deaktiviert-text: var(--c-grau-700);

  --farbe-fokus: var(--c-petrol-900);

  /* -- Status: Antrags-Workflow (Spec §5, alle 8 — V1 nutzt nur
        "unbestaetigt" und "eingereicht", der Rest ist für Phase 2 bereits
        angelegt, damit später keine Tokens nachgezogen werden müssen). */
  --status-unbestaetigt-text: var(--c-grau-700);
  --status-unbestaetigt-bg: var(--c-grau-100);
  --status-eingereicht-text: var(--c-petrol-700);
  --status-eingereicht-bg: var(--c-petrol-100);
  --status-in_pruefung-text: var(--c-bernstein-700);
  --status-in_pruefung-bg: var(--c-bernstein-100);
  --status-aenderung_angefordert-text: var(--c-orange-700);
  --status-aenderung_angefordert-bg: var(--c-orange-100);
  --status-genehmigt-text: var(--c-gruen-700);
  --status-genehmigt-bg: var(--c-gruen-100);
  --status-abgelehnt-text: var(--c-rot-700);
  --status-abgelehnt-bg: var(--c-rot-100);
  --status-verfallen-text: var(--c-taupe-700);
  --status-verfallen-bg: var(--c-taupe-100);
  --status-zurueckgezogen-text: var(--c-violett-700);
  --status-zurueckgezogen-bg: var(--c-violett-100);

  /* -- Typo-Skala (rem, Basis 16px) ------------------------------------ */
  /* Ein einziger System-Font-Stack für Überschrift UND Fließtext (siehe
     design-system.md): Hierarchie entsteht über Größe/Gewicht/Abstand,
     nicht über eine zweite Familie — auf Windows/macOS/Android/Linux
     zeigt `system-ui` sonst vier unterschiedlich harmonierende
     Schriftbilder gleichzeitig. */
  --schrift-stack:
    system-ui, -apple-system, 'Segoe UI', Roboto, 'Noto Sans', Arial, sans-serif;
  --schrift-xs: 0.8125rem; /* 13px — Meta, Bildunterschriften */
  --schrift-sm: 0.9375rem; /* 15px — Hilfetext, Tabellen */
  --schrift-basis: 1rem; /* 16px — Fließtext */
  --schrift-md: 1.125rem; /* 18px — hervorgehobener Text */
  --schrift-lg: 1.375rem; /* 22px — h3 */
  --schrift-xl: 1.75rem; /* 28px — h2 */
  --schrift-xxl: 2.25rem; /* 36px — h1 */
  --zeilenhoehe-text: 1.5;
  --zeilenhoehe-ueberschrift: 1.25;

  /* -- Abstands-Skala (rem) --------------------------------------------- */
  --abstand-3xs: 0.25rem;
  --abstand-2xs: 0.5rem;
  --abstand-xs: 0.75rem;
  --abstand-sm: 1rem;
  --abstand-md: 1.5rem;
  --abstand-lg: 2rem;
  --abstand-xl: 3rem;

  /* -- Radius ------------------------------------------------------------ */
  --radius-sm: 0.25rem;
  --radius-md: 0.5rem;
  --radius-lg: 0.75rem;
  --radius-pill: 999px;

  /* -- Schatten (dezent, nur Karte/erhöhte Flächen) ----------------------- */
  --schatten-karte: 0 1px 3px rgba(31, 41, 51, 0.12), 0 1px 2px rgba(31, 41, 51, 0.08);

  /* -- Unterschriftsfeld (Phase R5, Teil C) ------------------------------
     Die Zeichenflaeche bleibt in JEDEM Erscheinungsbild weiss — sie ist kein
     Bedienelement, sondern das Papier, auf dem unterschrieben wird: was
     darauf entsteht, wird als Bild gespeichert und spaeter auf ein weisses
     Blatt gedruckt. Ein heller Strich auf dunklem Grund waere im PDF
     unsichtbar. Bewusst KEIN `--farbe-*`-Token: die schalten mit dem Thema
     um, und genau das soll dieser Wert nicht. */
  --unterschrift-flaeche: #ffffff;

  /* -- Breakpoint-Referenz (kein CSS-Custom-Property in @media nutzbar,
        hier nur dokumentiert): einspaltig < 40rem, siehe Media Queries
        unten. */

  /* Dark Mode (Phase E2, T1, Kai-Ruling F1a) — native Formularelemente
     (Checkbox, Select, Datepicker …) sollen dem gewählten Farbschema
     folgen, nicht immer hell bleiben. `light dark` heißt „beide Schemata
     werden unterstützt, der Browser wählt nach `prefers-color-scheme`
     bzw. dem aktiven Dunkelmodus-Block unten" — dieselbe Quelle, die auch
     die Tokens umschaltet. */
  color-scheme: light dark;
}

/* E2-Review H3: `color-scheme: light dark;` oben sagt dem Browser nur „ich
   unterstütze beides" — für native Formularelemente (Checkbox, Select,
   Datepicker, Scrollbar) muss der Browser trotzdem WISSEN, welches Schema
   gerade GILT, wenn ein angemeldeter Benutzer eine ausdrückliche Wahl
   getroffen hat (`[data-theme]`, s. u.): ohne eigene `color-scheme`-Regel je
   Zustand bliebe er auf die `prefers-color-scheme`-Systemvorgabe angewiesen
   und ein Nutzer mit hellem System + bewusst gewähltem Dunkelmodus in Zion
   bekäme trotzdem helle native Steuerelemente. Der bare `:root` deckt
   „System" (beide Schemata, Browser entscheidet nach `prefers-color-scheme`)
   bereits ab; diese beiden Regeln decken die zwei EXPLIZITEN Zustände.
   `@media print` weiter unten setzt `color-scheme: light;` bereits für den
   Ausdruck — hier die Bildschirm-Fassung. */
:root[data-theme='hell'] {
  color-scheme: light;
}

/* ------------------------------------------------------------------ */
/* 1b. Dark-Mode-Tokens (Phase E2, T1, Kai-Ruling F1a)                  */
/* ------------------------------------------------------------------ */
/*
 * Der bare `:root`-Block oben behält die VOLLSTÄNDIGE Hell-Palette — er
 * ist die Vorgabe, wenn kein Dunkelmodus greift (Systemvorgabe hell, oder
 * kein Konto/öffentlicher Bereich). Zwei Blöcke überschreiben die
 * SEMANTISCHEN `--farbe-*`-Tokens (nie die Primitive `--c-*`, die bleiben
 * unverändert benannt, nur andere Stufen werden referenziert):
 *
 * 1. `@media (prefers-color-scheme: dark) { :root:not([data-theme="hell"]) {…} }`
 *    — die Systemeinstellung „dunkel", außer ein angemeldeter Benutzer hat
 *    ausdrücklich „hell" gewählt (dann gewinnt `[data-theme="hell"]`, das
 *    NICHT im `:not()` steckt, und die Hell-Palette im bare `:root` bleibt
 *    aktiv). Gilt auch im öffentlichen Bereich (kein Konto, kein
 *    `data-theme`-Attribut) — Kai-Ruling: „folgt automatisch der
 *    Systemeinstellung".
 * 2. `:root[data-theme="dunkel"] {…}` — ein angemeldeter Benutzer hat
 *    ausdrücklich „dunkel" gewählt, UNABHÄNGIG von der Systemeinstellung
 *    (`header.ejs` setzt das Attribut nur für hell/dunkel, nie für
 *    „system", siehe dort).
 *
 * Beide Blöcke tragen DIESELBEN Werte (bewusste Dopplung statt
 * Verschachtelung/Import — CSS kennt kein „@media ODER Selektor", und ein
 * gemeinsamer Werte-Block ließe sich in reinem CSS nicht referenzieren,
 * ohne die Custom-Properties selbst zu einer dritten Ebene zu machen). Der
 * Klassen-Wächter (`test/zion-css-tokens.test.js`) prüft genau das: jede
 * `--farbe-*`-Variable aus dem bare `:root` taucht in BEIDEN Blöcken mit
 * einem eigenen Wert wieder auf — sonst bliebe ein Hell-Token aktiv und
 * ergäbe Hell-Text auf Dunkel-Grund (oder umgekehrt).
 *
 * Kontrastwerte: siehe die Primitiven-Kommentare weiter oben und
 * `test/zion-css-kontrast.test.js` (rechnet die WCAG-Formel gegen genau
 * diese Werte nach, schreibt das Ergebnis in die Assertion-Meldung).
 */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='hell']) {
    /* E2-Review H3: siehe Begründung bei `:root[data-theme='hell']` oben —
       die Systemvorgabe „dunkel" braucht dieselbe explizite Aussage für
       native Formularelemente. */
    color-scheme: dark;
    --farbe-text: var(--c-grau-40);
    --farbe-text-schwach: var(--c-grau-250);
    --farbe-text-auf-primaer: var(--c-grau-950);
    /* E2-Review N4: vorher `--c-grau-650` (1,99:1 auf grau-900, 2,41:1 auf
       grau-950 — unter der WCAG-1.4.11-Schwelle von 3:1 für UI-Ränder).
       `--c-grau-500` ist dieselbe Primitive, die auch im Hellmodus schon als
       „nur groß/dekorativ, kein Fließtext" dokumentiert ist (§1) — hier
       erreicht sie 4,03:1 auf `--farbe-hintergrund` (grau-900) bzw. 4,88:1
       auf `--farbe-hintergrund-seite` (grau-950), siehe
       `test/zion-css-kontrast.test.js`. Der Hell-Wert (`--c-grau-300`,
       1,45–1,53:1) bleibt bewusst unangetastet — er liegt zwar ebenfalls
       unter 3:1, ist aber Teil des seit Phase 1 abgenommenen Hell-Designs
       (Tabellen-/Karten-Ränder überall); eine Anhebung dort würde das
       Erscheinungsbild sichtbar verändern, siehe Beobachtung in
       `docs/betrieb/phase-e2-deploy.md`. */
    --farbe-rand: var(--c-grau-500);
    --farbe-hintergrund: var(--c-grau-900);
    --farbe-hintergrund-seite: var(--c-grau-950);
    --farbe-hintergrund-zart: var(--c-grau-850);

    --farbe-primaer: var(--c-petrol-300);
    --farbe-primaer-hover: var(--c-petrol-200);
    --farbe-primaer-zart: var(--c-petrol-950);

    --farbe-gefahr: var(--c-rot-300);
    --farbe-gefahr-hover: var(--c-rot-200);
    --farbe-gefahr-zart: var(--c-rot-950);

    --farbe-erfolg-text: var(--c-gruen-300);
    --farbe-erfolg-bg: var(--c-gruen-950);
    --farbe-fehler-text: var(--c-rot-300);
    --farbe-fehler-bg: var(--c-rot-950);
    --farbe-hinweis-text: var(--c-petrol-300);
    --farbe-hinweis-bg: var(--c-petrol-950);
    --farbe-warnung-text: var(--c-orange-300);
    --farbe-warnung-bg: var(--c-orange-950);
    --farbe-umgebung-text: var(--c-orange-950);
    --farbe-umgebung-bg: var(--c-orange-300);

    /* E2-Review H2: siehe Begründung im bare :root — Dunkelmodus-Fassung.
       Bewusst DIESELBEN Primitiven wie `--farbe-hintergrund-zart`/
       `--farbe-text-schwach` (statt eigener neuer Primitiven): eine
       „dezent vom Seitengrund abgehobene Fläche" ist exakt das, was die
       `-zart`-Fläche im Dunkelmodus schon bedeutet — keine helle Platte,
       6,33:1 Text/Grund (Test: `test/zion-css-kontrast.test.js`). */
    --farbe-deaktiviert-bg: var(--c-grau-850);
    --farbe-deaktiviert-text: var(--c-grau-250);

    --farbe-fokus: var(--c-petrol-200);

    --status-unbestaetigt-text: var(--c-grau-350);
    --status-unbestaetigt-bg: var(--c-grau-820);
    --status-eingereicht-text: var(--c-petrol-300);
    --status-eingereicht-bg: var(--c-petrol-950);
    --status-in_pruefung-text: var(--c-bernstein-300);
    --status-in_pruefung-bg: var(--c-bernstein-950);
    --status-aenderung_angefordert-text: var(--c-orange-300);
    --status-aenderung_angefordert-bg: var(--c-orange-950);
    --status-genehmigt-text: var(--c-gruen-300);
    --status-genehmigt-bg: var(--c-gruen-950);
    --status-abgelehnt-text: var(--c-rot-300);
    --status-abgelehnt-bg: var(--c-rot-950);
    --status-verfallen-text: var(--c-taupe-300);
    --status-verfallen-bg: var(--c-taupe-950);
    --status-zurueckgezogen-text: var(--c-violett-300);
    --status-zurueckgezogen-bg: var(--c-violett-950);
  }
}

:root[data-theme='dunkel'] {
  /* E2-Review H3: siehe Begründung bei `:root[data-theme='hell']` oben. */
  color-scheme: dark;
  --farbe-text: var(--c-grau-40);
  --farbe-text-schwach: var(--c-grau-250);
  --farbe-text-auf-primaer: var(--c-grau-950);
  --farbe-rand: var(--c-grau-500);
  --farbe-hintergrund: var(--c-grau-900);
  --farbe-hintergrund-seite: var(--c-grau-950);
  --farbe-hintergrund-zart: var(--c-grau-850);

  --farbe-primaer: var(--c-petrol-300);
  --farbe-primaer-hover: var(--c-petrol-200);
  --farbe-primaer-zart: var(--c-petrol-950);

  --farbe-gefahr: var(--c-rot-300);
  --farbe-gefahr-hover: var(--c-rot-200);
  --farbe-gefahr-zart: var(--c-rot-950);

  --farbe-erfolg-text: var(--c-gruen-300);
  --farbe-erfolg-bg: var(--c-gruen-950);
  --farbe-fehler-text: var(--c-rot-300);
  --farbe-fehler-bg: var(--c-rot-950);
  --farbe-hinweis-text: var(--c-petrol-300);
  --farbe-hinweis-bg: var(--c-petrol-950);
  --farbe-warnung-text: var(--c-orange-300);
  --farbe-warnung-bg: var(--c-orange-950);
  --farbe-umgebung-text: var(--c-orange-950);
  --farbe-umgebung-bg: var(--c-orange-300);

  /* E2-Review H2: siehe Begründung im @media(prefers-color-scheme:dark)-Block
     oben — dieselben Werte, bewusste Dopplung (§1b-Kopfkommentar). */
  --farbe-deaktiviert-bg: var(--c-grau-850);
  --farbe-deaktiviert-text: var(--c-grau-250);

  --farbe-fokus: var(--c-petrol-200);

  --status-unbestaetigt-text: var(--c-grau-350);
  --status-unbestaetigt-bg: var(--c-grau-820);
  --status-eingereicht-text: var(--c-petrol-300);
  --status-eingereicht-bg: var(--c-petrol-950);
  --status-in_pruefung-text: var(--c-bernstein-300);
  --status-in_pruefung-bg: var(--c-bernstein-950);
  --status-aenderung_angefordert-text: var(--c-orange-300);
  --status-aenderung_angefordert-bg: var(--c-orange-950);
  --status-genehmigt-text: var(--c-gruen-300);
  --status-genehmigt-bg: var(--c-gruen-950);
  --status-abgelehnt-text: var(--c-rot-300);
  --status-abgelehnt-bg: var(--c-rot-950);
  --status-verfallen-text: var(--c-taupe-300);
  --status-verfallen-bg: var(--c-taupe-950);
  --status-zurueckgezogen-text: var(--c-violett-300);
  --status-zurueckgezogen-bg: var(--c-violett-950);
}

/* ------------------------------------------------------------------ */
/* 2. Reset / Basis                                                     */
/* ------------------------------------------------------------------ */

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

html {
  font-size: 100%;
}

body {
  margin: 0;
  font-family: var(--schrift-stack);
  font-size: var(--schrift-basis);
  line-height: var(--zeilenhoehe-text);
  color: var(--farbe-text);
  background: var(--farbe-hintergrund-seite);
}

img {
  max-width: 100%;
}

/* Sichtbarer Fokus für ALLE interaktiven Elemente — Tastatur- und
   Screenreader-Nutzung ist bei ehrenamtlichen Vereinsvertretern am Handy
   nicht der Regelfall, aber wo sie vorkommt, darf der Fokus nie
   unsichtbar sein. Bewusst `:focus-visible` mit `:focus`-Fallback für
   ältere Browser. */
a:focus,
button:focus,
input:focus,
select:focus,
textarea:focus,
summary:focus,
[tabindex]:focus {
  outline: 3px solid var(--farbe-fokus);
  outline-offset: 2px;
}
a:focus:not(:focus-visible),
button:focus:not(:focus-visible),
input:focus:not(:focus-visible),
select:focus:not(:focus-visible),
textarea:focus:not(:focus-visible),
summary:focus:not(:focus-visible),
[tabindex]:focus:not(:focus-visible) {
  outline: none;
}
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible {
  outline: 3px solid var(--farbe-fokus);
  outline-offset: 2px;
}

/* ------------------------------------------------------------------ */
/* 3. Layout                                                            */
/* ------------------------------------------------------------------ */

.site-header {
  background: var(--farbe-primaer);
  color: var(--farbe-text-auf-primaer);
  padding: var(--abstand-sm) var(--abstand-md);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--abstand-sm);
}

.site-header h1 {
  margin: 0;
  font-size: var(--schrift-lg);
  color: var(--farbe-text-auf-primaer);
}

.site-header p {
  margin: 0;
  font-size: var(--schrift-sm);
}

.site-header form {
  margin: 0;
}

/* Der Abmelden-Button auf petrolfarbenem Grund braucht einen eigenen,
   hellen Kontrast statt des Standard-Primärbuttons (der wäre auf dieser
   Fläche unsichtbar). */
.site-header button,
.site-header .button {
  background: transparent;
  color: var(--farbe-text-auf-primaer);
  border: 1px solid var(--farbe-text-auf-primaer);
}
.site-header button:hover,
.site-header .button:hover {
  background: rgba(255, 255, 255, 0.15);
}

main {
  max-width: 60rem;
  margin: 0 auto;
  padding: var(--abstand-md);
}

/* Phase S1: Staging-/Entwicklungs-Balken über dem Seitenkopf
   (`partials/umgebung-banner.ejs`, nur außerhalb der Produktion gerendert).
   Schmal, aber voll gefärbt; im Ausdruck ausgeblendet (`@media print`). */
.umgebung-banner {
  background: var(--farbe-umgebung-bg);
  color: var(--farbe-umgebung-text);
  padding: var(--abstand-3xs) var(--abstand-md);
  font-size: var(--schrift-sm);
  text-align: center;
}

.site-footer {
  max-width: 60rem;
  margin: var(--abstand-lg) auto 0;
  padding: var(--abstand-md);
  color: var(--farbe-text-schwach);
  font-size: var(--schrift-sm);
}

/* ------------------------------------------------------------------ */
/* 4. Typografie                                                        */
/* ------------------------------------------------------------------ */

h1,
h2,
h3,
h4 {
  font-family: var(--schrift-stack);
  font-weight: 700;
  line-height: var(--zeilenhoehe-ueberschrift);
  color: var(--farbe-text);
  margin: var(--abstand-lg) 0 var(--abstand-sm);
}
h1:first-child,
h2:first-child,
h3:first-child {
  margin-top: 0;
}

h1 {
  font-size: var(--schrift-xxl);
}
h2 {
  font-size: var(--schrift-xl);
}
h3 {
  font-size: var(--schrift-lg);
}
h4 {
  font-size: var(--schrift-md);
}

p {
  margin: 0 0 var(--abstand-sm);
}

small,
.beschreibung {
  font-size: var(--schrift-sm);
  color: var(--farbe-text-schwach);
}

a {
  color: var(--farbe-primaer);
}
a:hover {
  color: var(--farbe-primaer-hover);
}

code {
  font-family: ui-monospace, 'Cascadia Code', 'Consolas', monospace;
  font-size: 0.9em;
  background: var(--farbe-hintergrund-zart);
  padding: 0.1em 0.35em;
  border-radius: var(--radius-sm);
}

/* ------------------------------------------------------------------ */
/* 5. Button                                                            */
/* ------------------------------------------------------------------ */

.button,
button,
input[type='submit'] {
  display: inline-flex;
  align-items: center;
  gap: var(--abstand-2xs);
  font-family: var(--schrift-stack);
  font-size: var(--schrift-basis);
  font-weight: 600;
  line-height: 1.2;
  padding: 0.6rem 1.1rem;
  border-radius: var(--radius-md);
  border: 1px solid transparent;
  cursor: pointer;
  text-decoration: none;

  /* Standard = primär, siehe Begründung in design-system.md: die meisten
     `<button>` in den bestehenden Views sind die Haupthandlung ihres
     Formulars (Speichern, Freischalten, Anmelden). */
  background: var(--farbe-primaer);
  color: var(--farbe-text-auf-primaer);
}
.button:hover,
button:hover,
input[type='submit']:hover {
  background: var(--farbe-primaer-hover);
}
.button:disabled,
button:disabled,
input[type='submit']:disabled {
  /* E2-Review H2 (vorher `--c-grau-300`/`--c-grau-700`, Hell-Primitive: im
     Dunkelmodus blieb der deaktivierte Knopf eine helle Platte). Eigene
     semantische Tokens `--farbe-deaktiviert-*` (§1), Begründung/Kontrastwerte
     dort. */
  background: var(--farbe-deaktiviert-bg);
  color: var(--farbe-deaktiviert-text);
  cursor: not-allowed;
}

.button--sekundaer {
  background: var(--farbe-hintergrund);
  color: var(--farbe-primaer);
  border-color: var(--farbe-primaer);
}
.button--sekundaer:hover {
  background: var(--farbe-primaer-zart);
  color: var(--farbe-primaer-hover);
}

.button--gefahr {
  background: var(--farbe-gefahr);
  color: var(--farbe-text-auf-primaer);
}
.button--gefahr:hover {
  background: var(--farbe-gefahr-hover);
}

/* T10: ein kleinerer Knopf für Nebenaktionen in dichten Listen (z. B. "Zeile
   leeren" je Waffen-Zeile, `.bf-zeile-grid`) — dieselben Farb-Modifikatoren
   bleiben kombinierbar, nur Schrift/Innenabstand schrumpfen. */
.button--klein {
  padding: 0.3rem 0.7rem;
  font-size: var(--schrift-sm);
}

/* ------------------------------------------------------------------ */
/* 6. Formular                                                          */
/* ------------------------------------------------------------------ */

/*
 * Muster Label/Fehler/aria-describedby (dokumentiertes Beispiel, siehe
 * design-system.md — noch ohne echtes Formular in V1, das entsteht mit
 * dem Antragsformular):
 *
 *   <div class="formular-feld">
 *     <label for="vereinsnummer">Vereinsnummer</label>
 *     <input id="vereinsnummer" name="vereinsnummer" type="text"
 *            aria-describedby="vereinsnummer-fehler" aria-invalid="true">
 *     <p class="formular-fehler" id="vereinsnummer-fehler">
 *       Bitte geben Sie Ihre Vereinsnummer an.
 *     </p>
 *   </div>
 *
 * Wichtig: `aria-describedby` verweist auf die ID des Fehlertexts, damit
 * Screenreader ihn beim Fokussieren des Felds vorlesen. `aria-invalid`
 * markiert das Feld zusätzlich als fehlerhaft.
 */

.formular {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-sm);
  max-width: 32rem;
}

.formular-feld {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-3xs);
}

/* ---------------------------------------------------------------------------
   Unterschriftsfeld der Erstattungs-Abrechnung (Phase R5, Teil C)
   ---------------------------------------------------------------------------

   Der Block ist im HTML `hidden` und wird erst von
   `public/js/ek-unterschrift.js` sichtbar gemacht — ohne JavaScript gibt es
   kein Feld, sondern nur den Weg "Bogen herunterladen, unterschreiben,
   hochladen" (Begruendung in der Vorlage).

   `touch-action: none` ist der eigentliche Punkt dieser Regeln: ohne sie
   scrollt das Telefon die Seite, waehrend jemand mit dem Finger
   unterschreibt, und es entsteht ein Strich quer durch das Feld.

   Die Zeichenflaeche bleibt bewusst WEISS, auch im dunklen Erscheinungsbild:
   die Unterschrift wird als Bild gespeichert und spaeter auf ein weisses
   Blatt gedruckt. Ein heller Strich auf dunklem Grund waere im PDF
   unsichtbar. */
.ek-unterschrift {
  margin: var(--abstand-md) 0;
}

.ek-unterschrift-feld {
  display: block;
  width: 100%;
  max-width: 600px;
  height: 200px;
  background: var(--unterschrift-flaeche);
  border: 2px dashed var(--farbe-rand);
  border-radius: var(--radius-md);
  touch-action: none;
  cursor: crosshair;
}

.ek-unterschrift-leiste {
  display: flex;
  flex-wrap: wrap;
  gap: var(--abstand-sm);
  align-items: center;
  margin-top: var(--abstand-sm);
  max-width: 600px;
}

/*
 * Phase Befürwortung B1e (Kai-Feedback, DRITTER Anlauf beim selben Punkt):
 * die echte Ursache, warum das versteckte Freitext-Feld „Was für eine Waffe
 * ist es?" bei Kai trotz `hidden`-Attribut im Markup (server- UND
 * skriptseitig gesetzt) immer sichtbar blieb — hier, nicht in
 * `oeffentlich-waffe.ejs`/`oeffentlich-waffenbesitz.ejs`/`bf-waffen-zeile.js`.
 * `[hidden] { display: none }` ist eine Regel des User-Agent-Stylesheets;
 * `.formular-feld { display: flex; }` ist eine Autor-Regel MIT GLEICHER
 * Spezifität — und Autor-Regeln gewinnen laut CSS-Kaskade immer über
 * UA-Regeln, unabhängig von Reihenfolge oder Spezifität. Das Attribut stand
 * also im HTML, wirkte aber nie: das Feld war strukturell versteckt und
 * visuell trotzdem da. Diese Regel gewinnt durch eine höhere Spezifität
 * (Klasse + Attribut) und macht `hidden` wieder wirksam — für dieses Feld
 * und jedes künftige `.formular-feld[hidden]`.
 */
.formular-feld[hidden] {
  display: none;
}

/*
 * Phase R5-Feedback Teil A (Kai-Fund 1, 2026-09-18): eine Antwortmöglichkeit
 * einer Radio-/Checkbox-Gruppe stand im Erstattungs-Assistenten (Schritte
 * Veranstaltung/Rolle/Arten/Details) über statt vor ihrem Text. Ursache:
 * `<input>` und `<label>` standen dort als GESCHWISTER in einem
 * `.formular-feld` (oben: `flex-direction: column`) — darin stapelt sich
 * jedes Kind senkrecht, das Kästchen kam vor dem Label-Absatz zu stehen statt
 * daneben.
 *
 * `.formular-wahl` löst das, indem das `<label>` das `<input>` UMSCHLIESST
 * (Muster `.bf-frage label` des Befürwortungs-Assistenten, hier als eigene,
 * wiederverwendbare Klasse): Reihenfolge im Markup ist dann egal, die ganze
 * Zeile bleibt klickbar. Muster:
 *
 *   <label class="formular-wahl">
 *     <input type="radio" id="…" name="…" value="…">
 *     <span>Text der Antwortmöglichkeit</span>
 *   </label>
 *
 * Mit `formular-feld--fehler` kombinierbar (der Modifikator stylt jedes
 * Eingabefeld eines Nachfahren, unabhängig von der Klasse des Wrappers).
 */
.formular-wahl {
  display: flex;
  align-items: center;
  gap: var(--abstand-2xs);
  padding: var(--abstand-3xs) 0;
  cursor: pointer;
}

.formular-wahl input {
  flex: 0 0 auto;
  margin: 0;
}

.formular-feld label,
fieldset label {
  font-weight: 600;
  font-size: var(--schrift-sm);
  color: var(--farbe-text);
}

fieldset label {
  display: block;
  font-weight: 400;
}

fieldset {
  border: 0;
  margin: 0;
  padding: 0;
}

/* Für Screenreader vorhanden, optisch nicht dargestellt — eine sichtbare
   Legende würde in den Tabellen (Rollen je Zeile) nur wiederholen, was die
   Zeile schon zeigt. */
fieldset legend {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

input[type='text'],
input[type='email'],
input[type='password'],
input[type='search'],
input[type='tel'],
input[type='url'],
input[type='number'],
input[type='date'],
select,
textarea {
  font-family: var(--schrift-stack);
  font-size: var(--schrift-basis);
  color: var(--farbe-text);
  background: var(--farbe-hintergrund);
  border: 1px solid var(--farbe-rand);
  border-radius: var(--radius-sm);
  padding: 0.55rem 0.7rem;
  width: 100%;
}

textarea {
  min-height: 6rem;
  resize: vertical;
}

/* E2-Review H3: ohne eigene Regel kommt die Platzhalterfarbe vom
   User-Agent-Stylesheet (meist ein hartes Grau) statt vom Theme — im
   Dunkelmodus kaum lesbar auf dunklem Feld-Hintergrund. `--farbe-text-schwach`
   ist dasselbe Token, das auch Hilfetexte nutzt (Kontrastpaar in
   `test/zion-css-kontrast.test.js`); `opacity: 1` hebt Firefox' Standard
   (Platzhaltertext dort sonst zusätzlich abgeblendet) auf, damit die Farbe
   unverfälscht ankommt. */
::placeholder {
  color: var(--farbe-text-schwach);
  opacity: 1;
}

/* Zielzustand statt Toggle: Checkboxen (z. B. Rollen-/Rechte-Auswahl)
   bleiben klickbare Flächen mit ausreichend Abstand für Handy-Bedienung. */
input[type='checkbox'],
input[type='radio'] {
  width: 1.15rem;
  height: 1.15rem;
  margin-right: var(--abstand-2xs);
  vertical-align: middle;
}

.formular-hilfetext {
  font-size: var(--schrift-sm);
  color: var(--farbe-text-schwach);
  margin: 0;
}

.formular-fehler {
  font-size: var(--schrift-sm);
  color: var(--farbe-fehler-text);
  margin: 0;
  font-weight: 600;
}

.formular-feld--fehler input,
.formular-feld--fehler select,
.formular-feld--fehler textarea {
  border-color: var(--farbe-fehler-text);
}

/* Manuelle Nummernvergabe hinter einem Aufklapper (Phase E2, T3) — dezent,
   dieselbe Auf-/Zu-Optik wie `.historie` (siehe dort): standardmäßig zu,
   serverseitig `open` bei vorhandenem Wert (422-Re-Render darf den Wert nie
   verstecken). */
.formular-aufklapper {
  margin-top: var(--abstand-sm);
}

.formular-aufklapper summary {
  cursor: pointer;
  font-weight: 600;
  color: var(--farbe-primaer);
  list-style: none;
}

.formular-aufklapper summary::-webkit-details-marker {
  display: none;
}

.formular-aufklapper summary::before {
  content: '▸';
  display: inline-block;
  width: 1em;
  font-size: 0.8em;
}

.formular-aufklapper[open] summary::before {
  content: '▾';
}

.formular-aufklapper .formular-feld {
  margin-top: var(--abstand-sm);
}

/* Radiogruppe (Phase E2, T1: Theme-Auswahl System/Hell/Dunkel) — jede
   Option als eigene klickbare Zeile, Muster der Checkbox-Labels oben statt
   nebeneinander gequetschter Knöpfe (bessere Handy-Bedienung). */
.radiogruppe {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-2xs);
  margin-bottom: var(--abstand-sm);
}

.radiogruppe label {
  display: flex;
  align-items: center;
  gap: var(--abstand-3xs);
}

/* ------------------------------------------------------------------ */
/* 7. Tabelle                                                           */
/* ------------------------------------------------------------------ */

.tabelle {
  border-collapse: collapse;
  width: 100%;
  background: var(--farbe-hintergrund);
}

.tabelle caption {
  text-align: left;
  font-weight: 600;
  margin-bottom: var(--abstand-2xs);
  color: var(--farbe-text);
}

.tabelle th,
.tabelle td {
  border: 1px solid var(--farbe-rand);
  padding: 0.55rem 0.7rem;
  text-align: left;
  vertical-align: top;
}

.tabelle th {
  background: var(--farbe-hintergrund-zart);
  font-weight: 600;
}

.tabelle tbody tr:nth-child(even) {
  background: var(--farbe-hintergrund-zart);
}

/* Zwischenzeile in einer gruppierten Tabelle — eine Ebene unterhalb der
   `<th scope="colgroup">`-Überschrift. Gebraucht in der Disziplinen-Pflege:
   Übergruppe als Überschrift, Kreis als eingerückte Zwischenzeile darunter. */
.tabelle-zwischenzeile {
  padding-left: var(--abstand-md);
  font-weight: 600;
  color: var(--farbe-text-schwach);
}

/* Fallback: falls eine Ansicht (noch) kein `.tabelle` trägt, bleibt eine
   nackte `<table>` trotzdem lesbar. */
table {
  border-collapse: collapse;
  width: 100%;
}
th,
td {
  border: 1px solid var(--farbe-rand);
  padding: 0.4rem 0.5rem;
  text-align: left;
  vertical-align: top;
}

/* ------------------------------------------------------------------ */
/* 8. Badge                                                             */
/* ------------------------------------------------------------------ */

.badge {
  display: inline-block;
  padding: 0.15rem 0.55rem;
  margin: 0 0.2rem 0.2rem 0;
  border: 1px solid var(--farbe-rand);
  border-radius: var(--radius-pill);
  font-size: var(--schrift-xs);
  font-weight: 600;
  background: var(--farbe-hintergrund-zart);
  color: var(--farbe-text-schwach);
}

.badge-leer {
  border-style: dashed;
  /* E2-Review H1 (vorher `--c-grau-500`/`--c-grau-700`, Hell-Primitive: im
     Dunkelmodus blieb der Rahmen dunkel-auf-dunkel bzw. der Text zu blass —
     1,67:1). `--farbe-rand` ist exakt das semantische Rand-Token (§1, je
     Theme neu belegt); `--farbe-text-schwach` ist dasselbe grau-700 wie
     vorher im Hellmodus (8,81:1 auf Weiß, 7,85:1 auf grau-100, siehe
     design-system.md §5), im Dunkelmodus jetzt aber grau-250 statt
     unverändert grau-700. Rand bleibt dekorativ (kein Fließtext-Kontrast
     nötig), Text braucht 4,5:1 (AA) — beides über
     `test/zion-css-kontrast.test.js` abgedeckt (die allgemeinen
     `--farbe-text-schwach`-Paare). */
  border-color: var(--farbe-rand);
  background: transparent;
  color: var(--farbe-text-schwach);
  font-weight: 400;
}

/* Statusfarben für den Antrags-Workflow (Spec §5). Der Text im Badge
   bleibt IMMER das entscheidende Merkmal — Farbe ist Verstärkung, nicht
   einziges Unterscheidungsmerkmal (Farbenblindheit). */
.badge--unbestaetigt {
  background: var(--status-unbestaetigt-bg);
  color: var(--status-unbestaetigt-text);
  border-color: transparent;
}
.badge--eingereicht {
  background: var(--status-eingereicht-bg);
  color: var(--status-eingereicht-text);
  border-color: transparent;
}
.badge--in_pruefung {
  background: var(--status-in_pruefung-bg);
  color: var(--status-in_pruefung-text);
  border-color: transparent;
}
.badge--aenderung_angefordert {
  background: var(--status-aenderung_angefordert-bg);
  color: var(--status-aenderung_angefordert-text);
  border-color: transparent;
}
.badge--genehmigt {
  background: var(--status-genehmigt-bg);
  color: var(--status-genehmigt-text);
  border-color: transparent;
}
.badge--abgelehnt {
  background: var(--status-abgelehnt-bg);
  color: var(--status-abgelehnt-text);
  border-color: transparent;
}
.badge--verfallen {
  background: var(--status-verfallen-bg);
  color: var(--status-verfallen-text);
  border-color: transparent;
}
.badge--zurueckgezogen {
  background: var(--status-zurueckgezogen-bg);
  color: var(--status-zurueckgezogen-text);
  border-color: transparent;
}

/* Überfällige Frist im internen Dashboard/der Detailansicht (Task 2,
   Wettkampf-Modul) — reine Anzeige, kein Job dahinter (der Fristablauf-Job
   kommt erst Phase 4). Text bleibt das entscheidende Merkmal, Farbe ist nur
   Verstärkung — dieselbe Regel wie bei den Status-Badges oben. */
.frist-ueberfaellig {
  color: var(--farbe-fehler-text);
  font-weight: 600;
}

/* ------------------------------------------------------------------ */
/* 9. Hinweis                                                           */
/* ------------------------------------------------------------------ */

.hinweis {
  padding: var(--abstand-xs) var(--abstand-sm);
  /* E2-Review N5: vorher `--c-grau-500` (Hell-Primitive, im Dunkelmodus
     unverändert dieselbe helle Farbe) — `--farbe-rand` ist das semantische
     Token für dekorative Ränder, je Theme neu belegt (§1). Diese Basisregel
     greift ohnehin nur, solange keine der Art-Klassen (`.hinweis-erfolg` etc.
     unten) die Randfarbe überschreibt. */
  border-left: 4px solid var(--farbe-rand);
  background: var(--farbe-hintergrund-zart);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  margin: 0 0 var(--abstand-sm);
}

.hinweis-erfolg {
  border-left-color: var(--farbe-erfolg-text);
  background: var(--farbe-erfolg-bg);
  color: var(--farbe-erfolg-text);
}

.hinweis-fehler {
  border-left-color: var(--farbe-fehler-text);
  background: var(--farbe-fehler-bg);
  color: var(--farbe-fehler-text);
}

/* Warn-Art (`art: 'warnung'`, z. B. SEPA-Teilerfolg — Phase M2b). */
.hinweis-warnung {
  border-left-color: var(--farbe-warnung-text);
  background: var(--farbe-warnung-bg);
  color: var(--farbe-warnung-text);
}

/* Neutrale Art (`art: 'hinweis'` im Katalog, z. B. "keine Änderung"). */
.hinweis-hinweis {
  border-left-color: var(--farbe-hinweis-text);
  background: var(--farbe-hinweis-bg);
  color: var(--farbe-hinweis-text);
}

/* ------------------------------------------------------------------ */
/* 10. Karte                                                            */
/* ------------------------------------------------------------------ */

.karte {
  background: var(--farbe-hintergrund);
  border: 1px solid var(--farbe-rand);
  border-radius: var(--radius-lg);
  box-shadow: var(--schatten-karte);
  padding: var(--abstand-md);
  margin: 0 0 var(--abstand-md);
}

.karte > :last-child {
  margin-bottom: 0;
}

/* ------------------------------------------------------------------ */
/* 11. Interne Navigation: feste Seitenleiste (Phase Z2)                */
/* ------------------------------------------------------------------ */

/* Umbau von der horizontalen Kopfleiste (bis Phase Z1) auf eine feste
   Seitenleiste links (Kai-Ruling 2026-09-08: „das Top-Menü ist komplett
   unübersichtlich"). Rechte-Gating und Reihenfolge kommen unverändert aus
   `partials/intern-nav.ejs` — hier nur die Optik. Sichtabnahme durch Kai
   VOR dem Merge, siehe `docs/betrieb/phase-z2-deploy.md`.

   Layout-Entscheidung: `<main>` bekommt zwei Spalten, sobald sie eine
   `.intern-nav` als direktes Kind trägt — Seiten ohne Seitenleiste (Login,
   öffentliche Wettkampfliste, `error.ejs`, `kein-zugriff.ejs`, …) bleiben
   unverändert einspaltig (§3). `:has()` ist in diesem Stylesheet bereits
   Baseline (§15, `.dz-zweig:has(...)`, seit Ende 2023) — hier dieselbe
   Technik, damit weder `main` noch `header`/`footer` wissen müssen, ob die
   aktuelle Seite intern ist. Erst AB der Desktop-Breite: darunter ist die
   Leiste ohnehin eingeklappt (Regeln „Mobil" ganz unten), ein Grid würde dort
   nur unnötig Platz kosten. */
@media (min-width: 56.25rem) {
  main:has(> .intern-nav) {
    max-width: none;
    margin: 0;
    display: grid;
    grid-template-columns: 16rem minmax(0, 1fr);
    align-items: start;
    gap: 0 var(--abstand-lg);
  }

  /* Spannt über ALLE Inhaltszeilen. WICHTIG: `1 / -1` wäre hier FALSCH —
     negative Linien zählen nur im EXPLIZITEN Grid, und dieses Grid hat keine
     grid-template-rows; `-1` hieße dann „Linie 1", die Leiste belegte nur
     Zeile 1, und jedes weitere Inhalts-Geschwister (Zeile 2, 3, …) begänne
     UNTERHALB der hohen Leiste (Live-Fund Kai 2026-09-08: Übersicht mit einem
     Block ok, jede Seite mit mehreren Blöcken kaputt). Deshalb ein fester
     großer Span: überzählige implizite Zeilen bleiben leer und damit 0 hoch —
     mehr als 200 direkte main-Kinder hat keine View. */
  main:has(> .intern-nav) > .intern-nav {
    grid-column: 1;
    grid-row: 1 / span 200;
    /* Bleibt beim Scrollen stehen, scrollt bei Überlänge (viele gleichzeitig
       offene Gruppen) für sich — der Inhalt daneben scrollt unabhängig mit
       der Seite (Vorgabe: Leiste UND Inhalt scrollen unabhängig). */
    position: sticky;
    top: var(--abstand-md);
    max-height: calc(100vh - 2 * var(--abstand-md));
    overflow-y: auto;
  }

  /* ALLE anderen direkten `main`-Kinder sind der Seiteninhalt (jede View
     bringt unterschiedlich viele Geschwister-Elemente mit — `<h2>`,
     `<section class="karte">`, mehrfach in Schleifen erzeugt, …) — generisch
     in die zweite Spalte, ohne dass dieses Partial die Views kennen müsste.
     Weil `.intern-nav` über alle Zeilen spannt (s. o.), bleibt Spalte 1 in
     JEDER Zeile „besetzt": die Inhalts-Geschwister landen dadurch trotzdem
     nacheinander in eigenen Zeilen der Spalte 2 (normale Stapelung) statt
     eine hohe erste Zeile zu erzwingen, die den restlichen Inhalt nach unten
     verschieben würde. `min-width: 0` verhindert zusätzlich, dass eine
     breite Tabelle das Grid-Item über die verfügbare Breite hinauszieht —
     ein bekannter Grid-Stolperstein, der sonst horizontales Scrollen der
     GANZEN Seite auslösen würde (Vorgabe: darf nie passieren; einzelne breite
     Tabellen scrollen weiterhin gezielt über `.tabelle-scroll`, §13). */
  main:has(> .intern-nav) > :not(.intern-nav) {
    grid-column: 2;
    min-width: 0;
  }
}

.intern-nav {
  background: var(--farbe-hintergrund);
  border: 1px solid var(--farbe-rand);
  border-radius: var(--radius-lg);
  display: flex;
  flex-direction: column;
}

.intern-nav-kopf {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--abstand-sm);
  padding: var(--abstand-sm);
  border-bottom: 1px solid var(--farbe-rand);
}

.intern-nav-titel {
  font-weight: 700;
  font-size: var(--schrift-md);
  color: var(--farbe-text);
  text-decoration: none;
}
.intern-nav-titel:hover {
  color: var(--farbe-primaer);
}

.intern-nav-koerper {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  padding: var(--abstand-sm);
  gap: var(--abstand-sm);
}

.intern-nav-koerper > ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--abstand-3xs);
}

/* Fußbereich (Einstellungen/Administration/Abmelden) abgesetzt vom Rest
   (Kai-Ruling) — eigene Liste statt Fortsetzung der oberen, damit
   `margin-top: auto` sie ans untere Ende der Leiste drückt. */
.intern-nav-fuss {
  margin-top: auto;
  padding-top: var(--abstand-sm);
  border-top: 1px solid var(--farbe-rand);
}

.intern-nav a {
  display: block;
  padding: var(--abstand-2xs) var(--abstand-xs);
  font-weight: 600;
  text-decoration: none;
  color: var(--farbe-text-schwach);
  border-radius: var(--radius-sm);
}

.intern-nav a:hover {
  color: var(--farbe-primaer);
  background: var(--farbe-primaer-zart);
}

/* Aktiver Eintrag (Phase Z2, serverseitig ermittelt — `nav-aktiv.js`,
   längster Präfix-Treffer, exakter Treffer gewinnt sofort): Text bleibt
   das entscheidende Merkmal (Fettung), Farbe ist Verstärkung — dieselbe
   Regel wie bei den Status-Badges (§8). Zusätzlich `aria-current="page"`
   im Markup für Screenreader. */
.intern-nav-link--aktiv {
  color: var(--farbe-primaer);
  background: var(--farbe-primaer-zart);
  font-weight: 700;
}

/* Untermenüs („Wettkämpfe", „Standkalender", „Mitglieder", „Rechnungen",
   „Administration"): natives <details>/<summary>, seit Task 6 etabliert —
   in der Seitenleiste jetzt als eingerückter Block INNERHALB der Leiste
   statt als abgesetztes Dropdown (das `position: absolute` ergab nur in der
   früheren horizontalen Kopfleiste Sinn). Die aktive Gruppe trägt das
   `open`-Attribut serverseitig (`nav-aktiv.js`); alle anderen bleiben zu,
   sind aber per Klick/Tastatur weiter aufklappbar (kein JavaScript nötig,
   `<details>` ist von Haus aus tastaturbedienbar: Tab fokussiert
   `<summary>`, Enter/Leertaste klappt auf/zu). */
.nav-untermenue summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--abstand-2xs) var(--abstand-xs);
  font-weight: 600;
  color: var(--farbe-text-schwach);
  border-radius: var(--radius-sm);
  cursor: pointer;
  /* Eigener Pfeil statt des Browser-Dreiecks — auf allen Engines gleich. */
  list-style: none;
}

.nav-untermenue summary::-webkit-details-marker {
  display: none;
}

.nav-untermenue summary::after {
  content: '▸';
  margin-left: var(--abstand-2xs);
  font-size: 0.7em;
}

.nav-untermenue[open] summary::after {
  content: '▾';
}

.nav-untermenue summary:hover,
.nav-untermenue[open] summary {
  color: var(--farbe-primaer);
  background: var(--farbe-primaer-zart);
}

/* Aufgeklappt: eine eingerückte Liste UNTER dem Summary-Link, im normalen
   Fluss der Leiste (kein Dropdown mehr, s. o.). */
.nav-untermenue ul {
  list-style: none;
  margin: var(--abstand-3xs) 0 0;
  padding: 0 0 0 var(--abstand-sm);
  display: flex;
  flex-direction: column;
  gap: var(--abstand-3xs);
}

.nav-untermenue ul a {
  display: block;
}

/* --- Mobiler Auf-/Zuklapp-Knopf (Begründung Kopfkommentar des Partials) - */

.intern-nav-schalter-eingabe {
  /* Wie `.sr-only` (§12) visuell verborgen, aber NICHT `display: none` —
     bleibt fokussierbar (Tab) und über das zugehörige `<label>` für
     Screenreader ansprechbar. Der Fokusring erscheint stattdessen sichtbar
     auf dem `<label>` (Regel unten) — dasselbe Muster wie bei
     benutzerdefiniert gestalteten Checkboxen/Schaltern. `position: absolute`
     nimmt die Checkbox zusätzlich komplett aus dem Layout-Fluss — ihre
     Position im Markup (Partial-Kommentar Punkt 2) hat dadurch keinerlei
     optische Auswirkung, unabhängig davon, wo sie im DOM steht. */
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

.intern-nav-knopf {
  display: none;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: var(--schrift-lg);
  color: var(--farbe-text-schwach);
}

/* Kein `+` (unmittelbarer Nachbar) mehr, seit die Checkbox als eigenes
   Geschwister VOR `.intern-nav-kopf` steht statt darin verschachtelt zu sein
   (Z2-Review-Fund, Begründung Partial-Kommentar Punkt 2): `~` erreicht das
   Label trotzdem, weil beide denselben Elternknoten (`.intern-nav`) teilen —
   `~` verlangt keine unmittelbare Nachbarschaft, nur dieselbe Ebene. */
.intern-nav-schalter-eingabe:focus-visible ~ .intern-nav-kopf .intern-nav-knopf {
  outline: 3px solid var(--farbe-fokus);
  outline-offset: 2px;
}

.intern-nav-knopf:hover {
  background: var(--farbe-primaer-zart);
  color: var(--farbe-primaer);
}

/* --- Theme-Umschalter am Fuß der Leiste (Phase E2, T1) ----------------- */

.intern-nav-theme-form {
  margin: 0;
}

.intern-nav-theme-knopf {
  width: 100%;
  justify-content: flex-start;
  background: transparent;
  color: var(--farbe-text-schwach);
  border-color: transparent;
  font-size: var(--schrift-md);
  line-height: 1;
  padding-top: var(--abstand-2xs);
  padding-bottom: var(--abstand-2xs);
}
.intern-nav-theme-knopf:hover {
  background: var(--farbe-hintergrund-zart);
  color: var(--farbe-text);
}

/* --- Abmelden-Formular am Fuß der Leiste (Kopfkommentar Punkt 3) ------- */

.intern-nav-abmelden-form {
  margin: 0;
}

.intern-nav-abmelden-knopf {
  width: 100%;
  justify-content: flex-start;
  background: transparent;
  color: var(--farbe-gefahr);
  border-color: transparent;
  font-weight: 600;
}
.intern-nav-abmelden-knopf:hover {
  background: var(--farbe-gefahr-zart);
  color: var(--farbe-gefahr-hover);
}

/* --- Mobil: Leiste eingeklappt hinter dem Menü-Knopf ------------------- */

/* Unterhalb der Desktop-Breite (56.25rem, s. o.): der Leisten-„Körper"
   (die Navigationslisten) ist standardmäßig zu — nicht die Leiste als
   Ganzes, Kopfzeile mit Titel und Menü-Knopf bleibt immer sichtbar und
   erreichbar. Bewusst KEIN zweites `<details>` um die ganze Leiste: ein
   geschlossenes `<details>` lässt sich in aktuellen Browsern per Spezifikation
   nicht mehr per CSS erzwungen offen halten (genau dieses Overriding wurde
   nach verbreitetem Missbrauch gesperrt) — für „auf Mobil zu, ab Desktop
   immer offen, ohne JavaScript" bräuchte es also ohnehin zwei verschiedene
   Standardzustände derselben Markup-Instanz, was mit `<details>` technisch
   nicht geht. Die Checkbox löst das: ihr Standardzustand im Markup bleibt
   IMMER „nicht angehakt" — das Ein-/Ausblenden hängt stattdessen komplett an
   dieser Media Query, auf Desktop existiert die Regel schlicht nicht, dort
   ist der Körper unverändert sichtbar, unabhängig vom Checkbox-Zustand.
   Kein JavaScript, keine Inline-Handler (CSP, Projektkonvention).
   Z2-Review-Fund (Hoch, behoben): die Gegenregel nutzte ursprünglich
   `.intern-nav:has(.intern-nav-schalter-eingabe:checked) .intern-nav-koerper`
   — Browser ohne `:has()`-Unterstützung ignorierten sie komplett, `display:
   none` oben blieb dann die einzige greifende Regel und nahm die gesamte
   Navigation vom Bildschirm UND aus der Tab-Reihenfolge. Jetzt ein
   allgemeiner Geschwister-Selektor (`~`, Checkbox als eigenes Kind von
   `.intern-nav` VOR `.intern-nav-koerper`, Begründung im Partial-Kommentar
   Punkt 2) — funktioniert in jedem Browser, kein `:has()` mehr nötig. */
@media (max-width: 56.1875rem) {
  .intern-nav-koerper {
    display: none;
  }

  .intern-nav-schalter-eingabe:checked ~ .intern-nav-koerper {
    display: flex;
  }

  .intern-nav-knopf {
    display: inline-flex;
  }
}

/* ------------------------------------------------------------------ */
/* 12. Hilfsklassen                                                     */
/* ------------------------------------------------------------------ */

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

/* Vor dem Drucken ausgeblendet (§19) — Navigation, Aktionsleisten, Filter
   und Formulare gehören nicht auf den Ausdruck einer Belegungsliste. Am
   Bildschirm unsichtbar, aber ganz normal im Layout (kein `display: none`
   hier): die Klasse wirkt ausschließlich innerhalb von `@media print`. */

/* ------------------------------------------------------------------ */
/* 13. Responsiv — einspaltig unterhalb von 40rem (640px)               */
/* ------------------------------------------------------------------ */

@media (max-width: 40rem) {
  main,
  .site-footer {
    padding: var(--abstand-sm);
  }

  .site-header {
    padding: var(--abstand-sm);
  }

  /* Formularbreite füllt die verfügbare Spalte statt der festen
     Desktop-Breite. */
  .formular {
    max-width: 100%;
  }

  /* Tabellen mit vielen Spalten (Benutzer-/Rollenverwaltung) laufen auf
     dem Handy horizontal statt Inhalte abzuschneiden — Alternative wäre
     ein Kartenlayout pro Zeile, das aber `<th scope="col">`/`<td>`-
     Struktur der bestehenden Tests verändern würde. */
  .tabelle-scroll {
    overflow-x: auto;
  }

  .button,
  button,
  input[type='submit'] {
    width: 100%;
    justify-content: center;
  }

  .site-header button,
  .site-header .button {
    width: auto;
  }
}

/* ------------------------------------------------------------------ */
/* 14. Statuszeile, Datenliste, Timeline (Antrags-Statusseite)          */
/* ------------------------------------------------------------------ */

/* Nachgestellte Sektion (nach dem Responsiv-Block aus §13), damit die
   bestehende Nummerierung stabil bleibt; die zugehörigen Media-Query-
   Regeln stehen deshalb am Ende dieses Abschnitts statt in §13. */

.status-zeile {
  display: flex;
  align-items: center;
  gap: var(--abstand-2xs);
  margin: 0 0 var(--abstand-sm);
}

.status-zeile-label {
  color: var(--farbe-text-schwach);
  font-size: var(--schrift-sm);
}

/* Eckdaten als Beschreibungsliste: zweispaltiges Grid (Label/Wert) statt
   Tabelle — es sind Schlüssel-Wert-Paare eines Objekts, keine Datenreihen. */
.datenliste {
  display: grid;
  grid-template-columns: minmax(8rem, 12rem) 1fr;
  gap: var(--abstand-3xs) var(--abstand-sm);
  margin: 0 0 var(--abstand-md);
}

.datenliste dt {
  color: var(--farbe-text-schwach);
  font-size: var(--schrift-sm);
  font-weight: 600;
}

.datenliste dd {
  margin: 0;
}

/* Verlauf eines Antrags. `<ol>` bleibt die Semantik (Reihenfolge zählt),
   die Nummern werden nur optisch durch Punkte auf der Linie ersetzt. */
.timeline {
  list-style: none;
  margin: 0 0 var(--abstand-md);
  padding: 0 0 0 var(--abstand-md);
  border-left: 2px solid var(--farbe-rand);
}

.timeline > li {
  position: relative;
  padding-bottom: var(--abstand-sm);
}

.timeline > li:last-child {
  padding-bottom: 0;
}

/* Rein dekorativer Punkt, zentriert auf der 2px-Linie. */
.timeline > li::before {
  content: '';
  position: absolute;
  left: calc(-1 * var(--abstand-md) - 5px);
  top: 0.45rem;
  width: 8px;
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--farbe-primaer);
}

.timeline p {
  margin: 0;
}

.timeline > li > p + p {
  margin-top: var(--abstand-3xs);
}

.timeline-datum {
  color: var(--farbe-text-schwach);
  font-size: var(--schrift-xs);
}

.timeline-label {
  font-weight: 600;
}

/* Freitext aus einem <textarea> behält seine Zeilenumbrüche, ohne dass die
   View HTML einfügen müsste (`<br>` aus Nutzereingabe wäre eine
   Escaping-Falle). */
.text-mehrzeilig {
  white-space: pre-line;
}

@media (max-width: 40rem) {
  /* Auf dem Handy stehen Label und Wert untereinander — zwei Spalten
     lassen für den Wert sonst zu wenig Platz. */
  .datenliste {
    grid-template-columns: 1fr;
    gap: 0;
  }

  .datenliste dt {
    margin-top: var(--abstand-2xs);
  }
}

/* ------------------------------------------------------------------ */
/* 15. Disziplinen-Baum (Antrags-/Korrekturformular, dreistufig)         */
/* ------------------------------------------------------------------ */

/* Übergruppe → Nummernkreis → Kennziffer, ausschließlich mit CSS
   aufklappbar (Phase 2b Task 3; ersetzt das flache Gruppen-Styling aus
   Phase 1b): `input:checked ~ …` für „Näher spezifizieren", natives
   <details>/<summary> für die beiden Ebenen darunter. Kein JavaScript,
   keine Inline-Styles (CSP ohne 'unsafe-inline', Projektkonvention) — keine
   Progressive-Enhancement-Falle, kein `aria-expanded` nötig (das wäre nur
   bei einer JS-Variante Pflicht).

   Markup-Voraussetzung des Geschwister-Selektors: Checkbox, `<label for>`
   und Aufklapp-Block liegen als Geschwister im `<li>`
   (`partials/disziplinen-auswahl.ejs`), keine Label-Verschachtelung. */
.dz-auswahl {
  /* Einzug je Ebene = Checkbox-Breite + ihr rechter Abstand (§6), damit
     eingerückter Inhalt genau unter dem Label der Ebene darüber beginnt. */
  --dz-einzug: 1.65rem;
  border: 1px solid var(--farbe-rand);
  border-radius: var(--radius-md);
  padding: var(--abstand-2xs) var(--abstand-xs) var(--abstand-xs);
}

/* Der einzige sichtbare `<legend>` der Oberfläche: §6 versteckt Legenden
   generell (in den Rollen-Tabellen wiederholen sie nur die Zeile) — dieser
   Baum ist dagegen ein eigener, gerahmter Block und braucht eine sichtbare
   Überschrift. */
.dz-auswahl > legend {
  position: static;
  width: auto;
  height: auto;
  overflow: visible;
  clip-path: none;
  padding: 0 var(--abstand-3xs);
  font-weight: 600;
  font-size: var(--schrift-sm);
  color: var(--farbe-text);
}

.dz-baum,
.dz-kreise,
.dz-blaetter {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Labels bleiben in der Zeile ihrer Checkbox: §6 setzt `fieldset label` auf
   `display: block` (richtig für die einspaltigen Formularfelder, hier wäre
   es ein Umbruch nach jedem Kästchen). */
.dz-auswahl label {
  display: inline;
  font-weight: 400;
}

/* Jede Ebene ist eine explizite Flex-Zeile: Checkbox fix vorn, Label füllt
   den Rest (hängender Einzug — umbrechender Text steht unter Text, nie unter
   dem Kästchen). Der Aufklapp-Block (.dz-feiner bzw. <details>) bricht per
   flex-basis:100% in die eigene Zeile darunter (Kai-Feedback 2026-09-03). */
.dz-zweig,
.dz-kreis,
.dz-blatt {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
}

.dz-zweig > input,
.dz-kreis > input,
.dz-blatt > input {
  flex: none;
  /* Optisch mittig zur ersten Textzeile (1.15rem Kästchen, ~1.5 Zeilenhöhe). */
  margin-top: 0.2rem;
}

.dz-zweig > label,
.dz-kreis > label,
.dz-blatt > label {
  flex: 1 1 0;
  min-width: 0;
}

.dz-zweig {
  padding: var(--abstand-2xs) 0;
}

/* Trennlinien nur zwischen den Übergruppen — die 16 Zeilen der
   Standardansicht bleiben so als Liste lesbar, ohne Rahmen je Zeile. */
.dz-baum > .dz-zweig + .dz-zweig {
  border-top: 1px solid var(--farbe-rand);
}

.dz-auswahl .dz-ug-label {
  font-weight: 600;
}

.dz-feiner {
  display: none;
  /* Eigene Zeile unterhalb von Checkbox+Label (Flex-Zeile s. o.); der
     Standard-flex-shrink gleicht den Einzug gegen die 100%-Basis aus. */
  flex-basis: 100%;
  margin: var(--abstand-3xs) 0 0 var(--dz-einzug);
}

.dz-ug-checkbox:checked ~ .dz-feiner {
  display: block;
}

/* Verstecktes-aber-gezähltes-Häkchen unmöglich machen: entfernt ein Verein
   das Übergruppen-Häkchen, NACHDEM er darunter Kreise/Kennziffern
   angekreuzt hat, bleiben diese Checkboxen im Formular-Body und zählen
   serverseitig weiter (implizite Zweig-Übernahme ist gewolltes Ruling,
   siehe Spec) — sie dürfen dann nicht unsichtbar sein. `:has()` hält den
   Feinbereich deshalb zusätzlich offen, solange darunter etwas angekreuzt
   ist. Baseline seit Ende 2023; in älteren Browsern ohne `:has()`-Support
   greift nur die bisherige Regel oben, was unkritisch ist, weil dort ein
   Häkchen ohnehin nur bei angekreuzter Übergruppe gesetzt werden kann. */
.dz-zweig:has(.dz-kreis-checkbox:checked) .dz-feiner,
.dz-zweig:has(.dz-kz-checkbox:checked) .dz-feiner {
  display: block;
}

/* Summary als klickbare Zeile mit eigenem Marker (Muster wie das
   Nav-Untermenü in §11): auf allen Engines dieselbe Optik, und die Zeile
   ist groß genug für den Daumen. */
/* Bewusst klein und in der Link-Farbe, normale Stärke (Kai-Feedback
   2026-09-03): der Aufklapper ist eine Nebenaktion und soll wie ein Link
   wirken, nicht wie eine Zwischenüberschrift. */
.dz-details > summary {
  display: inline-block;
  padding: var(--abstand-3xs) 0;
  font-size: var(--schrift-xs);
  font-weight: 400;
  color: var(--farbe-primaer);
  cursor: pointer;
  list-style: none;
}

.dz-details > summary::-webkit-details-marker {
  display: none;
}

.dz-details > summary::before {
  content: '▸';
  display: inline-block;
  width: 1rem;
  font-size: 0.8em;
}

.dz-details[open] > summary::before {
  content: '▾';
}

.dz-details > summary:hover {
  color: var(--farbe-primaer-hover);
  text-decoration: underline;
}

.dz-kreis {
  padding: var(--abstand-3xs) 0;
}

.dz-kreis > .dz-details {
  display: block;
  /* Eigene Zeile in der Kreis-Flex-Zeile (wie .dz-feiner). */
  flex-basis: 100%;
  margin-left: var(--dz-einzug);
}

.dz-blaetter {
  margin-left: var(--dz-einzug);
}

.dz-blatt {
  padding: 0.15rem 0;
}

/* „Ihre Auswahl" nach einem Re-Render: dieselbe zarte Fläche wie ein
   Hinweis, aber ohne Ampelfarbe — es ist eine Bestätigung dessen, was der
   Server gelesen hat, keine Meldung. */
.dz-zusammenfassung {
  margin-top: var(--abstand-xs);
  padding: var(--abstand-2xs) var(--abstand-xs);
  background: var(--farbe-hintergrund-zart);
  border-radius: var(--radius-sm);
}

.dz-zusammenfassung-titel {
  margin: 0 0 var(--abstand-3xs);
  font-weight: 600;
  font-size: var(--schrift-sm);
}

.dz-zusammenfassung ul {
  margin: 0 0 var(--abstand-3xs);
  padding-left: var(--abstand-sm);
}

.dz-auswahl-hinweis {
  margin-top: var(--abstand-xs);
}

/* Handy: der Einzug je Ebene fällt kleiner aus, sonst bleibt für die
   Blatt-Labels („1001 – Präz. Pistole bis 9 mm") kaum Breite. */
@media (max-width: 40rem) {
  .dz-auswahl {
    --dz-einzug: 1rem;
    padding: var(--abstand-2xs);
  }
}

/* ------------------------------------------------------------------ */
/* 16. Öffentliche Wettkampfliste (GET /wettkaempfe, Phase 3, Task 1)    */
/* ------------------------------------------------------------------ */

/* Kopf der eigenständigen Seite (`partials/oeffentliche-kopf.ejs`): nutzt
   `.site-header` (§3) für dieselbe Optik wie der Rest von Zion, aber mit
   Verbandsname statt „GSVBW Zion" — der Untertitel „Genehmigte Wettkämpfe"
   steht in einem eigenen `<p>` darunter, deshalb kein `justify-content:
   space-between` wie im internen Header (kein zweites Element daneben). */
.oeffentliche-liste-kopf {
  justify-content: flex-start;
}
.oeffentliche-liste-kopf p {
  color: var(--farbe-text-auf-primaer);
  opacity: 0.9;
}

/* Durchgängig Karten statt Tabelle (Entscheidung Task 1, Detailplan erlaubt
   beides): eine Zeile pro Wettkampf liest sich mit acht möglichen Feldern
   (Kennung, Zeitraum, Verein, Ort, Disziplinen, Meldeschluss) als Tabelle
   auf dem Handy nur noch waagerecht scrollend; als Karte bleibt sie auf
   jeder Breite ohne Scrollen lesbar — und passt besser zu einer Seite, die
   von gsvbw.de aus verlinkt wird (Einbettung per iFrame ist durch frame-ancestors bewusst ausgeschlossen, siehe docs/betrieb/einbindung-gsvbw-de.md). */
.wettkampf-liste {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-sm);
  margin: 0;
  padding: 0;
  list-style: none;
}

.wettkampf-karte {
  border: 1px solid var(--farbe-rand);
  border-radius: var(--radius-md);
  padding: var(--abstand-sm);
}

.wettkampf-karte-kopf {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--abstand-2xs);
  margin-bottom: var(--abstand-3xs);
}

.wettkampf-karte-zeitraum {
  font-weight: 600;
  color: var(--farbe-text);
}

.wettkampf-karte h2 {
  margin: 0 0 var(--abstand-xs);
  font-size: var(--schrift-lg);
}

/* Beschreibungsliste als Zwei-Spalten-Raster ab Tablet-Breite (Label fix
   schmal, Wert füllt den Rest) — auf dem Handy fällt sie einspaltig zurück
   (Regel unten in diesem Abschnitt, nicht in §13: dieselbe Begründung wie
   bei §14, die Media-Query gehört zu den Regeln, die sie einschränkt). */
.wettkampf-karte-details {
  display: grid;
  grid-template-columns: 8rem 1fr;
  gap: var(--abstand-3xs) var(--abstand-xs);
  margin: 0;
}
.wettkampf-karte-details div {
  display: contents;
}
.wettkampf-karte-details dt {
  font-weight: 600;
  color: var(--farbe-text-schwach);
}
.wettkampf-karte-details dd {
  margin: 0;
}

/* ------------------------------------------------------------------ */
/* 17. Paginierung (Audit-Log-Ansicht, Phase 4, Task 3)                 */
/* ------------------------------------------------------------------ */

/* Zurück/Weiter-Buttons und Seitenangabe in einer Zeile — dasselbe
   Flex/Gap-Muster wie `.status-zeile` (§14) und `.wettkampf-karte-kopf`
   (§16), nur mit `justify-content: center`, weil hier kein Label vorne
   steht, das den Rest nach rechts drückt. */
.paginierung {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--abstand-sm);
  margin: var(--abstand-sm) 0 0;
}

/* Filterzeile der Audit-Ansicht: responsives Spaltenraster. Als Klasse statt
   style-Attribut — die CSP (default-src 'self', kein unsafe-inline) verwirft
   Inline-Stile kommentarlos (Final-Review Phase 4). */
.filter-raster {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12.5rem, 1fr));
  gap: var(--abstand-sm);
}

@media (max-width: 40rem) {
  .wettkampf-karte-details {
    grid-template-columns: 1fr;
  }
  .wettkampf-karte-details dt {
    margin-top: var(--abstand-2xs);
  }
}

/* ------------------------------------------------------------------ */
/* 18. Standkalender: Monatsraster, Tagesraster, Buchungsaktionen       */
/*     (Phase K1, Task 3)                                              */
/* ------------------------------------------------------------------ */

/* Nur bestehende Tokens (§1) — keine neuen Farben, Abstände oder Radien.
   Beide Raster sind echte `<table>`n (der Monat IST ein Raster aus Wochen
   und Wochentagen, der Tag eines aus Ressourcen und Halbtagen); die Regeln
   hier geben ihnen Zellenoptik, ohne die Tabellensemantik anzutasten. */

/* Aktionszeile über/unter einer Ansicht — dasselbe Flex/Gap-Muster wie
   `.paginierung` (§17), nur linksbündig. */
.kalender-aktionen {
  display: flex;
  flex-wrap: wrap;
  gap: var(--abstand-2xs);
  margin: var(--abstand-sm) 0;
}

/* Vormonat ← Monatsname → Folgemonat. Der Name steht als `h3` mittig; die
   beiden Buttons drücken ihn über `flex: 1` auseinander. */
.kalender-navigation {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--abstand-2xs);
  margin: var(--abstand-md) 0 var(--abstand-sm);
}

.kalender-navigation h3 {
  margin: 0;
  text-align: center;
  flex: 1 1 auto;
}

/* --- Monatsraster ---------------------------------------------------- */

.kalender-raster {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
}

.kalender-raster caption {
  caption-side: bottom;
  text-align: left;
  font-size: var(--schrift-sm);
  color: var(--farbe-text-schwach);
  padding-top: var(--abstand-2xs);
}

.kalender-raster th[scope="col"] {
  text-align: center;
  font-size: var(--schrift-sm);
  color: var(--farbe-text-schwach);
  padding: var(--abstand-3xs);
  border-bottom: 1px solid var(--farbe-rand);
}

.kalender-raster td {
  border: 1px solid var(--farbe-rand);
  padding: 0;
  vertical-align: top;
  height: 4.5rem;
}

.kalender-leer {
  background: var(--farbe-hintergrund-zart);
  border-color: transparent;
}

/* Die ganze Zelle ist der Link — größere Trefferfläche als eine verlinkte
   Tagesziffer, und der Fokusring (§2) umfasst sie sichtbar. */
.kalender-zelle a {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-3xs);
  height: 100%;
  padding: var(--abstand-3xs);
  text-decoration: none;
  color: var(--farbe-text);
}

.kalender-zelle a:hover,
.kalender-zelle a:focus-visible {
  background: var(--farbe-primaer-zart);
}

.kalender-tag {
  font-weight: 600;
}

.kalender-auslastung {
  font-size: var(--schrift-xs);
  color: var(--farbe-text-schwach);
}

/* Heute: Rahmen statt Fläche — eine gefärbte Fläche würde mit dem
   Hover-Zustand konkurrieren. */
.kalender-zelle--heute {
  outline: 2px solid var(--farbe-primaer);
  outline-offset: -2px;
}

.kalender-zelle--vergangen a {
  color: var(--farbe-text-schwach);
}

/* Auslastungsbalken. Rein dekorativ (`aria-hidden` in der View) — die Zahl
   darüber sagt dasselbe. Elf Stufenklassen statt eines style-Attributs: die
   CSP (`default-src 'self'`, kein `unsafe-inline`) verwirft Inline-Stile
   kommentarlos, und eine berechnete Breite ist ohnehin nur Dekoration. */
.kalender-balken {
  display: block;
  height: 0.25rem;
  margin-top: auto;
  border-radius: var(--radius-pill);
  background: var(--farbe-primaer);
}

.kalender-balken--0 { width: 0; }
.kalender-balken--10 { width: 10%; }
.kalender-balken--20 { width: 20%; }
.kalender-balken--30 { width: 30%; }
.kalender-balken--40 { width: 40%; }
.kalender-balken--50 { width: 50%; }
.kalender-balken--60 { width: 60%; }
.kalender-balken--70 { width: 70%; }
.kalender-balken--80 { width: 80%; }
.kalender-balken--90 { width: 90%; }

/* Voll belegt: die Gefahrenfarbe sagt „hier ist nichts mehr frei". */
.kalender-balken--100 {
  width: 100%;
  background: var(--farbe-gefahr);
}

/* --- Tagesraster ----------------------------------------------------- */

.tages-raster {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--schrift-sm);
}

.tages-raster caption {
  caption-side: bottom;
  text-align: left;
  font-size: var(--schrift-sm);
  color: var(--farbe-text-schwach);
  padding-top: var(--abstand-2xs);
}

.tages-raster th,
.tages-raster td {
  border: 1px solid var(--farbe-rand);
  padding: var(--abstand-2xs);
  text-align: left;
  vertical-align: top;
}

.tages-raster th[scope="col"] {
  background: var(--farbe-hintergrund-zart);
}

.tages-zelle--frei {
  background: var(--farbe-hintergrund);
}

.tages-zelle--belegt {
  background: var(--farbe-primaer-zart);
}

.tages-belegung {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--abstand-3xs);
  margin: 0;
}

/* Kleingedrucktes an einer Buchung (Kategorie, Kommentar, Equipment). */
.sk-meta {
  font-size: var(--schrift-xs);
  color: var(--farbe-text-schwach);
  margin: var(--abstand-3xs) 0 0;
}

/* --- Buchungsaktionen und Listen ------------------------------------- */

/* Aufklappbarer Aktionsblock — dieselbe Summary-Optik wie das Nav-Untermenü
   (§11) und der Disziplinen-Baum (§15): eigener Marker, klickbare Zeile. */
.buchung-aktionen {
  margin-top: var(--abstand-2xs);
}

.buchung-aktionen > summary {
  cursor: pointer;
  font-size: var(--schrift-xs);
  color: var(--farbe-primaer);
  list-style: none;
}

.buchung-aktionen > summary::-webkit-details-marker {
  display: none;
}

.buchung-aktionen > summary::before {
  content: "\25B8\00A0";
}

.buchung-aktionen[open] > summary::before {
  content: "\25BE\00A0";
}

.buchung-aktionen .formular {
  max-width: 100%;
  margin: var(--abstand-2xs) 0;
}

.buchung-aktionen form + form {
  margin-top: var(--abstand-3xs);
}

/* Eingeklappte Vergangenheit in „Meine Buchungen". */
.vergangene-buchungen > summary {
  cursor: pointer;
  font-size: var(--schrift-lg);
  font-weight: 600;
  margin: var(--abstand-md) 0 var(--abstand-2xs);
}

/* Ergebnisliste der Mehrfachbuchung: kein Aufzählungszeichen, eine Zeile je
   Slot-Gruppe. */
.sk-ergebnis-liste {
  list-style: none;
  padding: 0;
  margin: 0;
}

.sk-ergebnis-liste li + li {
  margin-top: var(--abstand-3xs);
  border-top: 1px solid var(--farbe-rand);
  padding-top: var(--abstand-3xs);
}

/* Sichtbare Legende: §6 versteckt `fieldset legend` (in den Rollentabellen
   wäre sie eine Dopplung) — hier ist sie die einzige Beschriftung der
   Checkbox-Gruppe und muss deshalb zurück in den sichtbaren Fluss. */
.sk-fieldset legend {
  position: static;
  width: auto;
  height: auto;
  overflow: visible;
  clip-path: none;
  font-weight: 600;
  font-size: var(--schrift-sm);
  padding: 0 0 var(--abstand-3xs);
}

/* --- Mobil ------------------------------------------------------------ */

@media (max-width: 40rem) {
  /* Der Monat bleibt ein Raster (sieben Spalten sind auf dem Handy schmal,
     aber lesbar — darin stehen nur Ziffer und Auslastung); das Tagesraster
     wird dagegen zur Liste: vier Spalten mit Formularen darin sind auf
     360 px unbenutzbar. `display: block` auf Zeilen und Zellen löst das
     Raster optisch auf; die Tabellensemantik selbst bleibt im Markup. */
  .kalender-raster td {
    height: 3.5rem;
  }

  .kalender-auslastung {
    font-size: 0.6875rem;
  }

  .tages-raster,
  .tages-raster tbody,
  .tages-raster tr,
  .tages-raster td,
  .tages-raster th[scope="row"] {
    display: block;
    width: auto;
  }

  /* Die Spaltenköpfe verlieren im Listen-Layout ihren Sinn — die Zellen
     tragen ihre Beschriftung selbst (Abteilung, Badge „Ganztags"). Sie
     werden deshalb nur optisch ausgelagert (wie `.sr-only`, §12), nicht
     entfernt. */
  .tages-raster thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
  }

  .tages-raster tr {
    border: 1px solid var(--farbe-rand);
    border-radius: var(--radius-sm);
    margin-bottom: var(--abstand-2xs);
    padding: var(--abstand-2xs);
  }

  .tages-raster td,
  .tages-raster th[scope="row"] {
    border: 0;
    padding: var(--abstand-3xs) 0;
  }

  .tages-zelle--frei,
  .tages-zelle--belegt {
    background: transparent;
  }

  .tages-zelle--belegt {
    border-left: 3px solid var(--farbe-primaer);
    padding-left: var(--abstand-2xs);
  }
}

/* ------------------------------------------------------------------ */
/* 19. Druckansicht: Standbericht + Berichte (Phase K2, Task 2)         */
/* ------------------------------------------------------------------ */

/* Gilt absichtlich für **jeden** Ausdruck, nicht nur für die beiden Ansichten
   dieses Tasks: Kopfzeile und interne Navigation gehören auf keinen
   Ausdruck, das ist keine Standkalender-Besonderheit. Die feingranulare
   Auswahl (Filterformular, Aktionsknöpfe, Freigabe-/Rückmelde-Abschnitte)
   passiert dagegen gezielt über `.no-print` in den jeweiligen Views — ein
   globales „kein `<form>` im Druck" würde z. B. auch das Melde-Formular
   selbst treffen, auf Seiten, die (noch) nicht für den Ausdruck gedacht
   sind.
   Übrig bleibt auf beiden Seiten: Titel (`h2`)/Zeitraum (`h3`) als
   Druckkopf und die Belegungstabelle(n) — „Titel + Zeitraum sichtbar"
   (Phase-K2-Vorgabe) braucht damit keine eigene Regel, es wird schlicht
   nichts davon ausgeblendet. */
@media print {
  /* Phase E2, T1 (Kai-Ruling F1a: „PDFs unberührt", auf den Ausdruck
     übertragen): ein Ausdruck bleibt IMMER hell, unabhängig vom
     Dunkelmodus des Bildschirms — dunkle Flächen verschwenden Toner/Tinte
     und der Bericht soll auf Papier so aussehen wie bisher (§19 Berichte).

     ACHTUNG Spezifität: ein bloßes `:root` (Spezifität 0-1-0) würde gegen
     `:root[data-theme="dunkel"]` und `:root:not([data-theme="hell"])`
     (je 0-2-0) VERLIEREN, egal wie spät im Stylesheet es steht — höhere
     Spezifität gewinnt immer vor Quellreihenfolge, `@media` ändert daran
     nichts. Die Selektorliste unten trifft deshalb dieselben drei Fälle
     mit GLEICHER Spezifität wie die Dunkel-Blöcke oben (0-2-0 bzw. hier
     0-1-0 für den harmlosen dritten Fall) — bei Gleichstand entscheidet
     die Position im Stylesheet, und dieser Block steht später. */
  :root,
  :root[data-theme='dunkel'],
  :root:not([data-theme='hell']) {
    --farbe-text: var(--c-grau-900);
    --farbe-text-schwach: var(--c-grau-700);
    --farbe-text-auf-primaer: var(--c-weiss);
    --farbe-rand: var(--c-grau-300);
    --farbe-hintergrund: var(--c-weiss);
    --farbe-hintergrund-seite: var(--c-grau-50);
    --farbe-hintergrund-zart: var(--c-grau-100);

    --farbe-primaer: var(--c-petrol-700);
    --farbe-primaer-hover: var(--c-petrol-900);
    --farbe-primaer-zart: var(--c-petrol-100);

    --farbe-gefahr: var(--c-rot-700);
    --farbe-gefahr-hover: var(--c-rot-800);
    --farbe-gefahr-zart: var(--c-rot-100);

    --farbe-erfolg-text: var(--c-gruen-700);
    --farbe-erfolg-bg: var(--c-gruen-100);
    --farbe-fehler-text: var(--c-rot-700);
    --farbe-fehler-bg: var(--c-rot-100);
    --farbe-hinweis-text: var(--c-petrol-700);
    --farbe-hinweis-bg: var(--c-petrol-100);
    --farbe-warnung-text: var(--c-orange-700);
    --farbe-warnung-bg: var(--c-orange-100);
    --farbe-umgebung-text: var(--c-weiss);
    --farbe-umgebung-bg: var(--c-orange-700);

    /* E2-Review M2: der Print-Block erzwingt ALLE semantischen Tokens auf
       ihre Hell-Werte (Kopfkommentar dieses Abschnitts) — die beiden neuen
       H2-Tokens fehlten hier, ein deaktivierter Knopf im Ausdruck hätte sonst
       (je nach Bildschirm-Theme zum Druckzeitpunkt) die DUNKLE Fläche
       behalten. */
    --farbe-deaktiviert-bg: var(--c-grau-300);
    --farbe-deaktiviert-text: var(--c-grau-700);

    --farbe-fokus: var(--c-petrol-900);

    --status-unbestaetigt-text: var(--c-grau-700);
    --status-unbestaetigt-bg: var(--c-grau-100);
    --status-eingereicht-text: var(--c-petrol-700);
    --status-eingereicht-bg: var(--c-petrol-100);
    --status-in_pruefung-text: var(--c-bernstein-700);
    --status-in_pruefung-bg: var(--c-bernstein-100);
    --status-aenderung_angefordert-text: var(--c-orange-700);
    --status-aenderung_angefordert-bg: var(--c-orange-100);
    --status-genehmigt-text: var(--c-gruen-700);
    --status-genehmigt-bg: var(--c-gruen-100);
    --status-abgelehnt-text: var(--c-rot-700);
    --status-abgelehnt-bg: var(--c-rot-100);
    --status-verfallen-text: var(--c-taupe-700);
    --status-verfallen-bg: var(--c-taupe-100);
    --status-zurueckgezogen-text: var(--c-violett-700);
    --status-zurueckgezogen-bg: var(--c-violett-100);

    color-scheme: light;
  }

  .site-header,
  .intern-nav,
  .site-footer,
  .umgebung-banner,
  .no-print {
    display: none !important;
  }

  /* Phase Z2: `.intern-nav` verschwindet zwar (Regel oben), bleibt aber im
     DOM — `main:has(> .intern-nav)` (§11) trifft also weiterhin zu und würde
     ohne diese Regel eine leere 16rem-Spalte reservieren (eine
     `display: none`-Zeile entfernt das Element aus dem Grid, NICHT aber die
     vom Container selbst definierten Spalten). Für den Ausdruck deshalb
     zurück auf die normale einspaltige Form aus §3. */
  main:has(> .intern-nav) {
    display: block;
    max-width: 60rem;
    margin: 0 auto;
  }

  /* Die mobile Scroll-Box (§13) wird am Bildschirm gebraucht, auf Papier
     nicht — eine abgeschnittene Tabelle im Ausdruck wäre schlechter als eine,
     die über die Seitenbreite hinausragt und vom Browser selbst umbricht. */
  .tabelle-scroll {
    overflow: visible;
  }

  .tabelle {
    width: 100%;
  }

  /* Seitenumbruch-freundlich: eine Buchungszeile reißt nicht mitten durch
     (Phase-K2-Vorgabe). `break-inside` statt des älteren, aber synonymen
     `page-break-inside` — beide werden von den unterstützten Browsern
     verstanden, `break-inside` ist die aktuelle Fassung der Spezifikation. */
  .tabelle tbody tr {
    break-inside: avoid;
  }

  /* Der Standbericht gliedert sich in ein `<section>` je Tag — dieselbe
     Umbruch-Rücksicht wie bei einzelnen Zeilen, für den Fall, dass ein
     einzelner Tag knapp auf eine Seite passt. */
  .standbericht-tag {
    break-inside: avoid;
  }
}

/* ------------------------------------------------------------------ */
/* 20. Mitgliederverwaltung: Detail-Blöcke, Änderungshistorie           */
/*     (Phase M1, Task 3)                                              */
/* ------------------------------------------------------------------ */

/* Nur bestehende Tokens (§1) — keine neuen Farben, Abstände oder Radien. */

/* Mitgliedschafts- und Qualifikations-Karten stehen VERSCHACHTELT in der
   jeweiligen Block-`<section class="karte">` (Muster
   `wettkampf/views/intern-detail.ejs`s „Versionen"-Abschnitt: `.karte` in
   `.karte`). Ein zarter Rand statt Schatten hebt sie von der äußeren Karte
   ab, ohne einen zweiten Schatten aufzubauen (zwei `box-shadow`-Ebenen
   übereinander wirken auf hellem Grund schnell schmutzig). */
.mitgliedschaft-karte,
.qualifikation-karte {
  box-shadow: none;
  border-color: var(--farbe-rand);
}

/* Kern-Partial `partials/historie.ejs` — natives <details>/<summary>
   (Task-3-Vorgabe: standardmäßig zu), dieselbe Optik wie das
   Administrations-Untermenü (`.nav-untermenue`, §11), aber blockweise statt
   als Dropdown. */
.historie {
  margin-top: var(--abstand-sm);
}

.historie summary {
  cursor: pointer;
  font-weight: 600;
  color: var(--farbe-primaer);
  list-style: none;
}

.historie summary::-webkit-details-marker {
  display: none;
}

.historie summary::before {
  content: '▸';
  display: inline-block;
  width: 1em;
  font-size: 0.8em;
}

.historie[open] summary::before {
  content: '▾';
}

/* Ein Block je Speichervorgang (Phase E2, T4, Kai-Ruling): Kopfzeile
   „Akteur, TT.MM.JJJJ HH:MM Uhr" + Tabelle Feld/Alt/Neu — Linie-mit-Punkten-
   Trennung zwischen den Einträgen wie `.timeline` (§14). */
.historie-eintrag {
  padding: var(--abstand-sm) 0 var(--abstand-sm) var(--abstand-md);
  margin-top: var(--abstand-sm);
  border-left: 2px solid var(--farbe-rand);
  position: relative;
}

.historie-eintrag::before {
  content: '';
  position: absolute;
  left: -5px;
  top: calc(var(--abstand-sm) + 0.5rem);
  width: 8px;
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--farbe-text-schwach);
}

.historie-eintrag:first-child {
  margin-top: var(--abstand-sm);
}

.historie-kopf {
  margin: 0 0 var(--abstand-2xs);
  font-size: var(--schrift-sm);
  color: var(--farbe-text-schwach);
}

.historie-kopf strong {
  color: var(--farbe-text);
}

/* Eigener Rahmen statt des vollen Breitengreifs von `.tabelle`: die
   Historie steht oft in einem schmalen Karten-Bereich, lange Alt-/Neu-Werte
   (z. B. Bemerkungsfelder) dürfen umbrechen statt die Karte zu sprengen. */
.historie-tabelle-wrapper {
  overflow-x: auto;
}

.historie-tabelle {
  font-size: var(--schrift-sm);
}

.historie-tabelle th:first-child,
.historie-tabelle td:first-child {
  white-space: nowrap;
  font-weight: 600;
}

.historie-tabelle td {
  overflow-wrap: anywhere;
  max-width: 22rem;
}

/* ------------------------------------------------------------------ */
/* 21. Rechnungswesen: Logo-Vorschau (Admin-Config)                     */
/*     (Phase M2, Task 2)                                              */
/* ------------------------------------------------------------------ */

/* Nur bestehende Tokens (§1). Offene-Posten-Übersicht, Empfänger-Detail,
   Zahllauf-Bericht und Rechnungsliste/-Detail brauchen keine neuen Klassen
   — sie bestehen ausschließlich aus `.karte`, `.tabelle`, `.badge`,
   `.datenliste`, `.formular*` und `.paginierung` (§7/§8/§10/§14/§6/§17). */
.rechnungslogo-vorschau {
  display: block;
  max-width: 12rem;
  max-height: 6rem;
  margin-bottom: var(--abstand-sm);
  border: 1px solid var(--farbe-rand);
  border-radius: var(--radius-sm);
  background: var(--c-weiss);
  padding: var(--abstand-2xs);
}

/* ------------------------------------------------------------------ */
/* 22. Feld-genaue Validierung                                          */
/*     (Phase M2b, Task 1)                                             */
/* ------------------------------------------------------------------ */

/*
 * Zwei Bausteine, die immer zusammen auftreten (Muster in §6 oben, jetzt mit
 * echtem Formular dahinter):
 *
 *   <input id="plz" name="plz" class="feld-fehlerhaft" data-pruefung="plz"
 *          aria-invalid="true" aria-describedby="plz-fehler">
 *   <p class="formular-fehler" id="plz-fehler" data-feld-pruefung-hinweis>…</p>
 *
 * `feld-fehlerhaft` sitzt am Eingabefeld SELBST (nicht am umgebenden
 * `.formular-feld` wie die ältere Modifikator-Klasse `--fehler`): die
 * Live-Prüfung `public/js/feld-pruefung.js` setzt und entfernt sie beim
 * Tippen, und ein einzelnes Element umzuschalten ist dort die kürzeste,
 * eindeutigste Operation. Beide Schreibweisen führen zum selben Bild.
 *
 * Der rote Rahmen ist NICHT das einzige Signal (Barrierefreiheit — Farbe
 * allein trägt keine Information): die Hinweiszeile darunter nennt den Fehler
 * im Klartext, `aria-invalid`/`aria-describedby` verbinden beides für
 * Screenreader.
 */
.feld-fehlerhaft,
input.feld-fehlerhaft,
select.feld-fehlerhaft,
textarea.feld-fehlerhaft {
  border-color: var(--farbe-fehler-text);
  border-width: 2px;
  /* Der zweite Pixel Rahmen darf das Feld nicht verspringen lassen. */
  padding: calc(0.55rem - 1px) calc(0.7rem - 1px);
}

.feld-fehlerhaft:focus,
input.feld-fehlerhaft:focus,
select.feld-fehlerhaft:focus,
textarea.feld-fehlerhaft:focus {
  outline-color: var(--farbe-fehler-text);
}

/* Die Hinweiszeile selbst kommt aus §6 (`.formular-fehler`) — hier nur der
   Abstand, damit sie am Feld klebt und nicht am nächsten Hilfetext. */
.formular-fehler[data-feld-pruefung-hinweis] {
  margin-top: var(--abstand-3xs);
}

/* ------------------------------------------------------------------ */
/* 23. Qualifikationen: Gruppen-Schalter und Ja/Nein-Zeilen             */
/*     (Phase M3, Task 2)                                              */
/* ------------------------------------------------------------------ */

/* Ein kompaktes Formular ohne die volle `.formular`-Spaltenbreite (§6) —
   für Zeilen, die mehrere kleine Felder nebeneinander zeigen sollen
   (Qualifikations-Zeile: Nummer/Erteilt am/Prüfer + Schaltfläche). */
.formular--inline {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--abstand-sm);
  max-width: none;
}

.formular--inline .formular-feld {
  flex: 1 1 9rem;
  min-width: 8rem;
  gap: var(--abstand-3xs);
}

/* Die Übergruppen-Schalter der Personen-Detailseite („Standard ✓ | IPSC ○ |
   Western ○") — je Gruppe ein einzeiliges Formular mit genau einer
   Schaltfläche, siehe `modules/mitglieder/views/person-detail.ejs`. */
.gruppen-schalter-zeile {
  display: flex;
  flex-wrap: wrap;
  gap: var(--abstand-xs);
  margin: 0 0 var(--abstand-sm);
}

.gruppen-schalter-form {
  margin: 0;
}

.gruppen-schalter {
  display: inline-flex;
  align-items: center;
  gap: var(--abstand-3xs);
  padding: 0.4rem 0.9rem;
  border-radius: var(--radius-pill);
  border: 1px solid var(--farbe-rand);
  font-weight: 600;
  font-size: var(--schrift-sm);
  cursor: pointer;
  background: var(--farbe-hintergrund-zart);
  color: var(--farbe-text-schwach);
}

/* Freigeschaltet — Text bleibt das entscheidende Merkmal (das „✓"), Farbe
   ist nur Verstärkung (dieselbe Regel wie bei den Status-Badges, §8). */
.gruppen-schalter--an {
  background: var(--farbe-erfolg-bg);
  color: var(--farbe-erfolg-text);
  border-color: transparent;
}

.gruppen-schalter--aus {
  background: transparent;
  border-style: dashed;
}

/* Eine Übergruppen-Karte je freigeschalteter Gruppe; darin je Art eine
   Ja/Nein-Zeile mit den drei Feldern und den beiden Aktionen. */
.qualifikations-gruppe-karte {
  margin-top: var(--abstand-sm);
}

.qualifikation-art-zeile {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--abstand-sm);
  padding: var(--abstand-sm) 0;
  border-bottom: 1px solid var(--farbe-rand);
}

.qualifikation-art-zeile:last-child {
  border-bottom: 0;
  padding-bottom: 0;
}

.qualifikation-art-kopf {
  display: flex;
  align-items: center;
  gap: var(--abstand-2xs);
  flex: 0 0 auto;
  min-width: 10rem;
}

.qualifikation-art-name {
  font-weight: 600;
}

/* Phase M3b: EIN Formular über alle Art-Zeilen einer Gruppe plus einem
   Speichern-Knopf — bewusst NICHT `.formular` (dessen `max-width: 32rem`
   würde die mehrspaltigen Zeilen quetschen) und auch nicht `.formular--inline`
   (das dreht das ÄUSSERE Element auf eine Zeile — hier soll nur jede
   `.qualifikation-art-zeile` für sich eine Reihe sein, das Formular drumherum
   stapelt sie nur senkrecht). */
.formular-qualifikationen-gruppe {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-sm);
  max-width: none;
}

/* Checkbox + Name als eine klickbare Fläche (die Checkbox selbst IST das
   Ja/Nein — kein separater Badge-Text mehr, Kai-Ruling „kein Ja setzen"). */
.qualifikation-art-kopf label {
  display: flex;
  align-items: center;
  gap: var(--abstand-3xs);
  cursor: pointer;
}

/* ------------------------------------------------------------------ */
/* 24. Befürwortungs-Assistent (öffentlich, Phase Befürwortung B1)     */
/* ------------------------------------------------------------------ */

/* Nur bestehende Tokens (§1) — keine neuen Farben, Abstände oder Radien.
   Der Assistent ist eine öffentliche Strecke ohne Anmeldung und benutzt
   ansonsten dieselben Bausteine wie die übrigen Formulare (`.karte`,
   `.formular*`, `.hinweis*`, `.tabelle`, `.datenliste`). */

/* Schritt-Anzeige über der Überschrift — bewusst Text und kein Balken: sie
   soll auch dann verständlich sein, wenn Schritte entfallen (der
   Kontingent-Schritt gilt nur für kontingentrelevante Waffenarten, die
   Gesamtzahl ist deshalb nicht konstant). */
.bf-fortschritt {
  margin: 0 0 var(--abstand-2xs);
  color: var(--farbe-text-schwach);
  font-size: var(--schrift-sm);
  font-weight: 600;
  letter-spacing: 0.02em;
}

/* Die Navigationszeile: „Weiter“ steht im MARKUP vorn (damit ein
   Enter-Druck im Textfeld weitergeht und nicht zurück) und optisch rechts —
   `row-reverse` dreht nur die Darstellung. Dieselbe Flex/Gap-Sprache wie
   `.formular-actions`, das die Zeile ohnehin schon trägt. */
.bf-schritt-nav {
  display: flex;
  flex-direction: row-reverse;
  justify-content: flex-start;
  gap: var(--abstand-xs);
  flex-wrap: wrap;
}

/* Eine Frage mit sichtbarer Legende: §6 versteckt `fieldset legend`
   generell (in den Rollentabellen wäre sie eine Dopplung) — hier ist sie die
   Frage selbst und muss in den sichtbaren Fluss zurück. Muster
   `.sk-fieldset legend` (§18). */
.bf-frage {
  margin: 0 0 var(--abstand-sm);
}

.bf-frage > legend {
  position: static;
  width: auto;
  height: auto;
  overflow: visible;
  clip-path: none;
  font-weight: 600;
  font-size: var(--schrift-sm);
  padding: 0 0 var(--abstand-3xs);
}

/* Eine Antwortmöglichkeit mit erklärendem Satz: Radio links, Text daneben —
   die ganze Zeile ist klickbar (das `<label>` umschließt beides). */
.bf-frage label {
  display: flex;
  align-items: flex-start;
  gap: var(--abstand-2xs);
  padding: var(--abstand-3xs) 0;
  cursor: pointer;
}

.bf-frage label input {
  margin-top: 0.2em;
  flex: 0 0 auto;
}

/* Eine Zeile der Anlagen A/B und C — als abgesetzter Block, damit sichtbar
   bleibt, welche Felder zusammengehören (auf dem Handy stehen sie
   untereinander und wären sonst nicht mehr unterscheidbar). */
.bf-zeile {
  border: 1px solid var(--farbe-rand);
  border-radius: var(--radius-sm);
  padding: var(--abstand-xs) var(--abstand-sm);
  background: var(--farbe-hintergrund-zart);
}

/* T10 (Kai 2026-09-15, "kompakter"): EINE Waffe = EINE kompakte Zeile statt
   gestapelter Einzelfelder — Ziel war "4 Waffen passen auf einen
   Bildschirm" (Schritt Waffenbesitz, `views/oeffentlich-waffenbesitz.ejs`).
   `.bf-zeile-kompakt` verkleinert nur den Außenabstand der Karte, die
   Feldanordnung übernimmt `.bf-zeile-grid`. */
.bf-zeile-kompakt {
  margin: 0 0 var(--abstand-2xs);
  padding: var(--abstand-2xs) var(--abstand-sm);
}

/* Grid: auf breiten Bildschirmen mehrere Felder NEBENEINANDER
   (`auto-fit`/`minmax` statt einer festen Spaltenzahl — passt sich der
   Kartenbreite an), auf schmalen automatisch EINE Spalte (kein eigenes
   Mobil-Markup, `.formular` selbst bleibt einspaltig). */
.bf-zeile-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: var(--abstand-xs) var(--abstand-sm);
  align-items: start;
}

/* Die Waffenart-Radios sind die einzige GRUPPE ohne einzelnes `<input>`, an
   das ein `<label>` binden könnte — dieser Span trägt dieselbe Optik wie
   `.formular-feld label` (§6), ohne ein `for` zu behaupten, das ins Leere
   zeigt. */
.formular-feld-label {
  font-weight: 600;
  font-size: var(--schrift-sm);
  color: var(--farbe-text);
}

/* Die Waffenart braucht mehr Breite als die übrigen Felder (mehrere Radios
   nebeneinander) — deshalb über zwei Grid-Spalten, sobald welche da sind. */
.bf-zeile-art {
  grid-column: span 2;
  min-width: 16rem;
}

.bf-zeile-radios {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 var(--abstand-sm);
}

.bf-zeile-radios-gruppe {
  font-size: var(--schrift-sm);
  color: var(--farbe-text-schwach);
  flex-basis: 100%;
}

/* Freitext („Sonstiges") und Wettkampf-Einsatz brauchen die volle Zeilenbreite
   — ein einzeiliges Textfeld daneben wäre auf Desktop-Breite zu schmal, um
   lesbar zu bleiben. */
.bf-zeile-span-alle {
  grid-column: 1 / -1;
}

.bf-zeile-checkbox {
  justify-content: flex-end;
}

.bf-zeile-loeschen {
  margin: var(--abstand-2xs) 0 0;
  text-align: right;
}

/* Die Liste der hochgeladenen Anlagen: Name, Art und der Entfernen-Knopf in
   einer Zeile. Muster `.status-zeile` (§14). */
.bf-dateien {
  list-style: none;
  margin: 0 0 var(--abstand-sm);
  padding: 0;
}

.bf-dateien li {
  display: flex;
  align-items: center;
  gap: var(--abstand-xs);
  flex-wrap: wrap;
  padding: var(--abstand-2xs) 0;
  border-bottom: 1px solid var(--farbe-rand);
}

.bf-dateien li:last-child {
  border-bottom: 0;
}

.bf-datei-name {
  font-weight: 600;
  overflow-wrap: anywhere;
}

.bf-datei-art {
  color: var(--farbe-text-schwach);
  font-size: var(--schrift-sm);
  margin-right: auto;
}

/* T10: der Unterlagen-Listenpunkt wird "grün", sobald der Server eine Datei
   seiner Kategorie sieht (`eintrag.erfuellt`) — Muster `.hinweis-erfolg`
   (§14), aber ohne eigenen Kasten, weil es nur EIN Wort der Liste faerbt. */
.bf-unterlage-erfuellt {
  color: var(--farbe-erfolg-text);
}

/* Aufklappbare Kennziffern-Liste — dieselbe Formsprache wie die übrigen
   `<details>`-Blöcke der Oberfläche (§15/§20). */
.bf-liste > summary {
  cursor: pointer;
  font-weight: 600;
  padding: var(--abstand-2xs) 0;
}

/* Die Postanschrift auf der Bestätigungsseite: `<address>` kursiv ist die
   Browser-Vorgabe und liest sich hier falsch — es ist eine Adresse zum
   Abschreiben, kein Zitat. */
.bf-anschrift {
  font-style: normal;
  margin: var(--abstand-2xs) 0;
  padding-left: var(--abstand-sm);
  border-left: 3px solid var(--farbe-rand);
}

/* ------------------------------------------------------------------ */
/* 25. Befürwortungs-Assistent — Umbau nach dem Live-Test (B1b)        */
/* ------------------------------------------------------------------ */

/* Additiv zu §24, weiterhin ausschließlich bestehende Tokens (§1) — keine
   neuen Farben, Abstände oder Radien. Vier Dinge kommen dazu: das
   Korrektur-Panel, das Sticky-Panel der Anlage C, die kompakte
   Aktivitäten-Zeile und der Zähler. */

/* Die Nachforderung des Verbandes (Kai-Feedback B1b, Punkt 8). `sticky` und
   nicht `fixed`: das Panel soll im Textfluss der Karte bleiben (kein
   Überdecken, keine Kollision mit der Kopfzeile) und nur beim Scrollen oben
   kleben. Der Rand links greift die Formsprache von `.bf-anschrift` auf. */
.bf-korrektur-panel {
  position: sticky;
  top: 0;
  z-index: 1;
  margin: 0 0 var(--abstand-sm);
  padding: var(--abstand-xs) var(--abstand-sm);
  background: var(--farbe-warnung-bg);
  color: var(--farbe-warnung-text);
  border-left: 4px solid var(--farbe-warnung-text);
  border-radius: var(--radius-sm);
}

.bf-korrektur-panel > h3 {
  margin: 0 0 var(--abstand-3xs);
  font-size: var(--schrift-sm);
  color: inherit;
}

/* Der nachgeforderte Text ist eine Anweisung eines Menschen an einen
   Menschen — er darf Zeilenumbrüche behalten, und ein langes Wort (eine
   Nummer, eine URL) darf die Karte nicht sprengen. */
.bf-korrektur-text {
  margin: 0 0 var(--abstand-2xs);
  white-space: pre-line;
  overflow-wrap: anywhere;
}

/* Das Hinweis-Panel der Anlage C (Punkt 4f): Zähler, Monats-Warnung und der
   Pflicht-Hinweis auf die Gegenzeichnung — es fliegt beim Scrollen mit, damit
   der Zähler beim Ausfüllen der zwölften Zeile noch sichtbar ist. */
.bf-sticky {
  position: sticky;
  top: 0;
  z-index: 1;
  margin: 0 0 var(--abstand-sm);
  padding: var(--abstand-xs) var(--abstand-sm);
  background: var(--farbe-hintergrund-zart);
  border: 1px solid var(--farbe-rand);
  border-radius: var(--radius-sm);
}

.bf-sticky > p {
  margin: 0 0 var(--abstand-3xs);
}

.bf-sticky > p:last-child {
  margin-bottom: 0;
}

/* Der Live-Zähler. Die beiden Zustände tragen die Statusfarben aus §1 — das
   Browser-Skript setzt genau diese zwei Klassen (`bf-aktivitaeten.js`), der
   Server rendert sie identisch. */
.bf-zaehler {
  font-weight: 600;
  font-size: var(--schrift-md);
}

.bf-zaehler--erfuellt {
  color: var(--farbe-erfolg-text);
}

.bf-zaehler--offen {
  color: var(--farbe-warnung-text);
}

.bf-warnung {
  color: var(--farbe-warnung-text);
  font-size: var(--schrift-sm);
}

.bf-sticky-pflicht {
  color: var(--farbe-text-schwach);
  font-size: var(--schrift-sm);
}

/* Die KOMPAKTE Aktivitäten-Zeile (Punkt 4a): Datum + Schießstätte + die
   beiden Häkchen in EINER Reihe. `flex-wrap` statt Media-Query — auf einem
   Telefon bricht die Reihe von selbst um, und der Umbruchpunkt hängt an der
   Feldbreite, nicht an einer geratenen Bildschirmgröße. */
.bf-akt-zeile {
  margin: 0 0 var(--abstand-2xs);
  padding: var(--abstand-2xs) 0 0;
  border-top: 1px solid var(--farbe-rand);
}

.bf-akt-zeile--fehler {
  border-left: 3px solid var(--farbe-fehler-text);
  padding-left: var(--abstand-2xs);
}

.bf-akt-felder {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--abstand-2xs) var(--abstand-xs);
}

.bf-akt-felder > .formular-feld {
  margin: 0;
  flex: 1 1 10rem;
  min-width: 8rem;
}

/* Die beiden Häkchen nebeneinander, nicht untereinander — sie sind zusammen
   eine Angabe („womit hast du geschossen"). */
.bf-akt-haken {
  display: flex;
  flex-wrap: wrap;
  gap: var(--abstand-xs);
  flex: 0 1 auto;
}

.bf-akt-haken label {
  display: flex;
  align-items: center;
  gap: var(--abstand-3xs);
  cursor: pointer;
  margin: 0;
}

/* ------------------------------------------------------------------ */
/* 26. Filter-Auto-Submit app-weit + Anlage-C-Ampel-Fix (B1b, Task 4)  */
/* ------------------------------------------------------------------ */

/* `public/js/filter-auto.js` setzt diese Klasse beim Laden auf jedes
   Formular mit `data-filter-auto` — NIE direkt eine Regel auf das
   Attribut selbst, siehe Kopfkommentar der Datei: ohne aktives JavaScript
   bleibt der „Anzeigen"-Knopf der einzige Weg zum Abschicken und muss
   deshalb voll betont bleiben. Der Knopf bleibt in jedem Fall klickbar,
   nur optisch tritt er zurück (dieselben Werte wie `.button--sekundaer`,
   §5) — mit aktivem Auto-Submit ist er nur noch der No-JS-Fallback. */
.filter-auto-aktiv button[type='submit'] {
  background: var(--farbe-hintergrund);
  color: var(--farbe-primaer);
  border-color: var(--farbe-primaer);
}
.filter-auto-aktiv button[type='submit']:hover {
  background: var(--farbe-primaer-zart);
  color: var(--farbe-primaer-hover);
}

/* Anlage-C-Regel-Ampel im internen Vorgangs-Detail (Kai-Ruling 2, Task 4:
   Meldung „Regel NICHT erfüllt" unlesbar, Text übereinander). Das Markup
   (`modules/befuerwortung/views/intern-vorgang-detail.ejs`, T3-Territorium
   — hier nicht angefasst) rendert die Ampel als `<span class="hinweis
   ...">` statt als Block-Element. `.hinweis` (§9) setzt vertikales
   Padding; bei einem INLINE-Element (Span) rechnet der Browser das nicht in
   die Zeilenhöhe ein, sondern legt es nur optisch über/unter die
   Textzeile — bricht der lange "Regel NICHT erfüllt"-Satz mehrzeilig um,
   überlagert Padding und Hintergrund die eigene erste/letzte Zeile bis zur
   Unlesbarkeit. `inline-block` behebt das: die Box bekommt eine Höhe, die
   ihr Padding einschließt, ohne den Text aus dem Lesefluss zu nehmen oder
   die volle Breite zu beanspruchen (anders als `display: block`, das hier
   den Zeilenumbruch vor dem Span erzwingen würde).
   Additiv und bewusst auf `span.hinweis` beschränkt: ein
   `<p class="hinweis">`/`<div class="hinweis">` (alle übrigen Stellen der
   Anwendung, z. B. `rechnungen-liste.ejs`, `standbericht-melden.ejs`)
   bleibt unverändert blockweise über die volle Breite. Baut T3 das Markup
   künftig auf ein Block-Element um, greift diese Regel dort einfach nicht
   mehr — Block-Elemente haben den beschriebenen Bug nicht, der Fix
   funktioniert also mit dem heutigen UND einem künftigen Markup. */
span.hinweis {
  display: inline-block;
}

/* ------------------------------------------------------------------ */
/* 27. Formular-Wortlaute pflegen (Phase Formulare D3, D3-Review N5)   */
/* ------------------------------------------------------------------ */

/* `views/formulare.ejs` reiht 23 Bausteine in drei Karten — je Baustein
   Beschreibung, Platzhalter-Liste, Textfeld, Knopfreihe, Vorschau,
   Standard-Text und Historie. Ohne diese Regeln standen die Bausteine
   ohne Trenner direkt aneinander (bei 13 Einträgen in einer Karte war
   nicht zu sehen, wo einer endet), die drei Knöpfe klebten zusammen,
   und — der eigentliche Fund — Zeilenumbrüche im Text waren in Vorschau
   und Standard-Vergleich UNSICHTBAR, obwohl sie im PDF wirken. */

.formulartext-eintrag {
  padding: var(--abstand-md) 0;
  border-top: 1px solid var(--farbe-rand);
}

/* Der erste Baustein einer Karte steht direkt unter der Gruppen-
   Überschrift — dort wäre der Trenner eine doppelte Linie. */
.formulartext-eintrag:first-of-type {
  border-top: 0;
  padding-top: var(--abstand-2xs);
}

.formulartext-eintrag > h4 {
  margin: 0 0 var(--abstand-2xs);
}

/* Die drei Knöpfe (Vorschau / Speichern / Zurücksetzen) in einer Reihe,
   mit Abstand und umbruchfähig — auf dem Telefon stapeln sie sich. */
.formular-knopfreihe {
  display: flex;
  flex-wrap: wrap;
  gap: var(--abstand-2xs);
  margin-top: var(--abstand-2xs);
}

/* Vorschau-Block und einklappbarer Standard-Text: dieselbe zarte Fläche,
   die in dieser Datei sonst „gelesen, nicht bearbeitet" bedeutet. */
.formulartext-vorschau {
  margin: var(--abstand-2xs) 0;
  padding: var(--abstand-2xs) var(--abstand-xs);
  background: var(--farbe-hintergrund-zart);
  border-left: 3px solid var(--farbe-primaer);
  border-radius: var(--radius-sm);
}

.formulartext-standard {
  margin-top: var(--abstand-2xs);
  font-size: var(--schrift-sm);
  color: var(--farbe-text-schwach);
}

.formulartext-standard > summary {
  cursor: pointer;
}

/* `white-space: pre-wrap` ist hier FACHLICH nötig, nicht kosmetisch: ein
   Zeilenumbruch im gepflegten Text bricht auch im PDF um
   (`core/pdf/zeichensatz.js#textUmbrechen` trennt an `\n`). Glättete die
   Vorschau ihn wie HTML zu einem Leerzeichen, zeigte sie ein anderes
   Ergebnis als das Blatt — genau das, was eine Vorschau nicht darf. Der
   `blockquote`-Reset nimmt dem Zitat-Element seinen Browser-Einzug; die
   Fläche selbst rahmt schon. */
.formulartext-vorschau blockquote,
.formulartext-standard blockquote {
  white-space: pre-wrap;
  margin: 0;
  padding: 0;
  border: 0;
  font-style: normal;
}

/* =====================================================================
   § Prozess-Leiste der Befuerwortungs-Detailseite (Phase E1, Kai-Ruling
   F3a). Jeder Arbeitsschritt eine Box: erledigte zeigen eine Zeile und
   sind AUFKLAPPBAR (Kais Leitsatz — eine Korrektur muss moeglich
   bleiben), offen ist nur der aktuelle Schritt, spaetere stehen grau.

   OHNE JAVASCRIPT: <details>/<summary> koennen das von Haus aus, und
   `open` setzt der SERVER an genau der Box, die
   `core/befuerwortung/prozess.js` `aktuell` nennt (CSP ohne
   unsafe-inline — es gaebe hier kein Skript, das nachrechnen koennte).

   KEINE display-Regel auf einem Element, das ein hidden-Attribut tragen
   kann: der globale [hidden]-Riegel am Dateiende bleibt die letzte Regel
   und traegt !important (B1e-Lehre), aber eine eigene display-Regel
   gleicher Spezifitaet ohne !important waere trotzdem die naechste
   Fehlerquelle. Die Zustaende faerben, sie blenden nicht aus.
   ===================================================================== */

.bf-prozess {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-2xs);
}

.bf-schritt {
  border: 1px solid var(--farbe-rand);
  border-left: 4px solid var(--farbe-rand);
  border-radius: var(--radius-md);
  background: var(--farbe-hintergrund);
}

.bf-schritt > summary {
  cursor: pointer;
  padding: var(--abstand-xs) var(--abstand-sm);
  /* `list-style` bleibt: das Dreieck ist der einzige Hinweis darauf, dass
     sich die Box oeffnen laesst — genau die Auskunft, die Kais
     „er sollte es koennen" braucht. */
}

.bf-schritt-kopf {
  display: inline flow-root;
  vertical-align: top;
  max-width: calc(100% - 1.5rem);
}

.bf-schritt-titel {
  font-weight: 600;
}

.bf-schritt-marke {
  margin-right: var(--abstand-3xs);
  font-weight: 700;
}

.bf-schritt-zusammenfassung {
  display: block;
  font-size: var(--schrift-sm);
  color: var(--farbe-text-schwach);
}

.bf-schritt-inhalt {
  padding: 0 var(--abstand-sm) var(--abstand-sm);
  border-top: 1px solid var(--farbe-rand);
}

/* Die vier Zustaende aus `prozess.js#SCHRITT_ZUSTAENDE`. Farbe UND ein
   Wort in der Box (`.bf-schritt-marke`) — nie Farbe allein: sie ist fuer
   einen farbfehlsichtigen Leser keine Auskunft. */
.bf-schritt--erledigt {
  border-left-color: var(--farbe-erfolg-text);
}

.bf-schritt--aktuell {
  border-left-color: var(--farbe-primaer);
  box-shadow: 0 0 0 1px var(--farbe-primaer-zart);
}

.bf-schritt--offen {
  border-left-color: var(--farbe-rand);
  background: var(--farbe-hintergrund-seite);
}

.bf-schritt--entfaellt {
  border-left-color: var(--farbe-rand);
  background: var(--farbe-hintergrund-seite);
}

.bf-schritt--offen .bf-schritt-titel,
.bf-schritt--entfaellt .bf-schritt-titel {
  color: var(--farbe-text-schwach);
}

/* Der Gebuehren-Grund haengt am Radio „gebuehrenfrei" und wird
   SERVERSEITIG per hidden-Attribut geschaltet (B1e-Lehre: nie nur im
   Markup, nie per Kaskade) — hier steht nur die Feld-Regel, die der
   globale Riegel ohnehin schlaegt. */
.bf-gebuehr-liste {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-3xs);
  margin-bottom: var(--abstand-2xs);
}

.bf-gebuehr-hinweis {
  color: var(--farbe-text-schwach);
  font-size: var(--schrift-sm);
}

/* =====================================================================
   § Arbeitsliste: Gruppierung nach "was ist als Naechstes zu tun" (Phase
   F0, Kai-Ruling Vorschlag 2). Zwei Bereiche ("Ich bin dran"/"Ich warte")
   ueber mehreren Gruppen — `.bf-arbeitsliste-bereich` trennt nur optisch,
   die eigentliche Gruppierung steht bereits fertig sortiert in
   `gruppenAnzeige` (server-berechnet, `core/befuerwortung/arbeitsliste.js`).

   Die Ampel "Mitgliederdaten" ist ein `.badge` mit eigenem Modifier — sie
   leiht sich die BESTEHENDEN Erfolgs-/Warnungs-/Text-Tokens (kein neues
   Farbschema, Waechter-Vorgabe): Text ("✓"/"n Abweichungen"/"–") bleibt das
   entscheidende Merkmal, Farbe ist nur Verstaerkung (Muster Status-Badges,
   §8 oben). ===================================================================== */

.bf-arbeitsliste-bereich {
  margin-top: var(--abstand-lg);
  padding-top: var(--abstand-sm);
  border-top: 1px solid var(--farbe-rand);
}

.bf-ampel--ok {
  background: var(--farbe-erfolg-bg);
  color: var(--farbe-erfolg-text);
  border-color: transparent;
}

.bf-ampel--abweichung {
  background: var(--farbe-warnung-bg);
  color: var(--farbe-warnung-text);
  border-color: transparent;
}

.bf-ampel--unbekannt {
  color: var(--farbe-text-schwach);
}

/* =====================================================================
   Sporthandbuch-Regeln (Phase F2, T10): Liste und Detail unter
   /intern/regelwerk. Bewusst OHNE neue Farb-Token — die drei Staende und die
   zwei Marken greifen auf die bestehenden semantischen Paare zurueck
   (erfolg/warnung/fehler, je Theme schon belegt und im Kontrast-Test
   abgedeckt). Die Farbe ist auch hier nur Verstaerkung: jeder Badge traegt
   seinen Text ausgeschrieben. */
.regelwerk-zaehler {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1.5rem;
}

.regelwerk-stand--geprueft {
  background: var(--farbe-erfolg-bg);
  color: var(--farbe-erfolg-text);
  border-color: transparent;
}

.regelwerk-stand--automatisch {
  background: var(--farbe-warnung-bg);
  color: var(--farbe-warnung-text);
  border-color: transparent;
}

.regelwerk-stand--deaktiviert {
  border-style: dashed;
  background: transparent;
  color: var(--farbe-text-schwach);
}

.regelwerk-marke--sperre {
  background: var(--farbe-fehler-bg);
  color: var(--farbe-fehler-text);
  border-color: transparent;
}

.regelwerk-marke--unsicher {
  background: var(--farbe-warnung-bg);
  color: var(--farbe-warnung-text);
  border-color: transparent;
}

.regelwerk-unsicher li {
  margin-bottom: 0.5rem;
}

/* =====================================================================
   Sporthandbuch-Befund (Phase F2, T9): die Warnbox im oeffentlichen
   Assistenten und der Ampel-Block im internen Pruefen-Schritt. Beide
   benutzen DIESELBEN Klassen — der Befund ist derselbe, nur das Publikum
   ist ein anderes.

   Wieder ohne neue Farb-Token: gruen/gelb/rot greifen auf die bestehenden
   semantischen Paare (erfolg/hinweis/fehler) zurueck, die je Theme belegt
   und im Kontrast-Test abgedeckt sind. Die Farbe ist NUR Verstaerkung —
   jeder Punkt traegt seinen Text ausgeschrieben, und die Ampel nennt ihren
   Stand im Klartext ("Hinweise aus dem Sporthandbuch"). */
.shb-box {
  border: 1px solid var(--farbe-rand);
  border-left-width: 4px;
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  background: var(--farbe-hintergrund-zart);
  padding: var(--abstand-sm);
  margin: 0 0 var(--abstand-sm);
}

.shb-box > h3 {
  margin-top: 0;
}

.shb-box--gruen {
  border-left-color: var(--farbe-erfolg-text);
}

.shb-box--gelb {
  border-left-color: var(--farbe-hinweis-text);
}

.shb-box--rot {
  border-left-color: var(--farbe-fehler-text);
}

.shb-punkte {
  list-style: none;
  padding: 0;
  margin: 0;
}

.shb-punkt {
  border-left: 3px solid var(--farbe-rand);
  padding-left: var(--abstand-sm);
  margin-bottom: var(--abstand-sm);
}

.shb-punkt--hinweis {
  border-left-color: var(--farbe-hinweis-text);
}

.shb-punkt--warnung {
  border-left-color: var(--farbe-warnung-text);
}

.shb-punkt--sperre {
  border-left-color: var(--farbe-fehler-text);
}

.shb-punkt-text {
  margin: 0 0 0.25rem;
}

.shb-ampel-text {
  margin: 0 0 var(--abstand-xs);
}

.shb-zitat {
  margin: 0 0 0.25rem;
  padding-left: var(--abstand-sm);
  border-left: 2px solid var(--farbe-rand);
  color: var(--farbe-text-schwach);
}

/* =====================================================================
   Rechte-Gruppen in Admin -> Rollen (Phase R5, Teil D, Kai-Wunsch
   2026-09-18: "in einer zweiten Zeile nebeneinander in Gruppen, die man
   erweitern kann"). Eine Kachel je Bereich (`rbac.js#rechteGruppiert`),
   nebeneinander im Grid; jede Kachel ist ein natives `<details>` — dieselbe
   Auf-/Zu-Optik wie `.formular-aufklapper` oben, aber mit eigenem Rahmen
   statt reinem Einzug, weil mehrere Kacheln nebeneinander stehen. Serverseitig
   `open`, wenn die Rolle in diesem Bereich mindestens ein Recht trägt — kein
   JS nötig (`views/admin/rollen.ejs`). */
.rechte-kacheln {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: var(--abstand-sm);
  margin: var(--abstand-sm) 0;
}

.rechte-kachel {
  border: 1px solid var(--farbe-rand);
  border-radius: var(--radius-md);
  padding: var(--abstand-xs) var(--abstand-sm);
}

.rechte-kachel summary {
  cursor: pointer;
  font-weight: 600;
  list-style: none;
}

.rechte-kachel summary::-webkit-details-marker {
  display: none;
}

.rechte-kachel summary::before {
  content: '▸';
  display: inline-block;
  width: 1em;
  font-size: 0.8em;
}

.rechte-kachel[open] summary::before {
  content: '▾';
}

/* Die Gruppen-Checkbox "Alle Rechte dieses Bereichs" — optisch von den
   einzelnen Rechten abgesetzt, damit sie nicht wie ein weiteres Einzelrecht
   aussieht. */
.rechte-kachel-alle {
  display: block;
  margin: var(--abstand-xs) 0 var(--abstand-2xs);
  padding-bottom: var(--abstand-2xs);
  border-bottom: 1px solid var(--farbe-rand);
  font-style: italic;
  color: var(--farbe-text-schwach);
}

.rechte-kachel-liste {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-3xs);
  margin-top: var(--abstand-2xs);
}

/* Kompakte Rechte-Zusammenfassung in der Rollen-Übersicht ("Formulare 2 ·
   Wettkämpfe 3 · Mitglieder 1" statt der vollen Aufzählung; die Namen der
   gesetzten Rechte stehen im title-Tooltip). */
.rechte-zusammenfassung {
  color: var(--farbe-text-schwach);
  font-size: var(--schrift-sm);
  margin: var(--abstand-3xs) 0 0;
}

/* =====================================================================
   Globaler hidden-Riegel (Phase B1e, Fehlerklassen-Schluss). Das
   User-Agent-[hidden]{display:none} verliert per Kaskade gegen JEDE eigene
   display-Regel gleicher Spezifitaet (Author-Origin schlaegt UA-Origin,
   Reihenfolge egal) — so war der Waffen-Freitext trotz korrektem
   hidden-Attribut drei Runden lang sichtbar. Diese Regel macht das
   Attribut verbindlich, fuer alle heutigen und kuenftigen Elemente.
   !important ist gerechtfertigt: hidden ist eine semantische
   Zustandsaussage, keine Gestaltungsfrage. */
[hidden] {
  display: none !important;
}
