/* Sports Statistics — stylesheet */

/* ---------- Schriften ----------
   Barlow und Barlow Condensed (SIL OFL 1.1, Lizenz in `fonts/OFL.txt`) liegen
   als WOFF2 **im Projekt**. Kein Aufruf zu fonts.googleapis.com und keiner zu
   fonts.gstatic.com: ein Seitenaufruf soll keine Adresse an einen Dritten
   schicken, und eine Schrift, die von außen kommt, fehlt genau dann, wenn das
   Netz in der Halle schlecht ist.

   Zwei Schnitte je Gewicht, latin und latin-ext, getrennt über
   `unicode-range`: wer nur Deutsch und Englisch liest, lädt latin-ext nie.
   `font-display: swap` zeigt den Text sofort in der Fallback-Schrift und
   tauscht sie aus, sobald die Datei da ist — eine Tabelle, die man nicht
   lesen kann, ist am Spielfeldrand schlimmer als eine, die einmal umbricht.

   Die Dateien sind mit `assets/fonts/` **relativ** verlinkt, also relativ zu
   dieser Datei und nicht zum Seitenpfad; deshalb steht hier `fonts/…`. */
@font-face {
    font-family: 'Barlow';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/barlow-400-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
    font-family: 'Barlow';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/barlow-400-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: 'Barlow';
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url('fonts/barlow-500-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
    font-family: 'Barlow';
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url('fonts/barlow-500-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: 'Barlow';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/barlow-600-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
    font-family: 'Barlow';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/barlow-600-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: 'Barlow';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/barlow-700-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
    font-family: 'Barlow';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/barlow-700-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: 'Barlow Condensed';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/barlow-condensed-700-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
    font-family: 'Barlow Condensed';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/barlow-condensed-700-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: 'Barlow Condensed';
    font-style: normal;
    font-weight: 800;
    font-display: swap;
    src: url('fonts/barlow-condensed-800-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
    font-family: 'Barlow Condensed';
    font-style: normal;
    font-weight: 800;
    font-display: swap;
    src: url('fonts/barlow-condensed-800-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
    /* **Zwei Schriftstapel, zwei Namen.** Sie stehen hier und nicht an den
       dreißig Stellen, die sie lesen, damit der Fallback an *einer* Stelle
       gepflegt wird: `system-ui, sans-serif` greift, solange die WOFF2 noch
       lädt (`font-display: swap`) und auch dann, wenn sie gar nicht kommt.

       `--font-cond` nennt zuerst Barlow Condensed, dann **Barlow** und erst
       danach die Systemschrift. Der Mittelweg ist Absicht: fehlt nur der
       schmale Schnitt, soll eine Überschrift nicht in eine völlig andere
       Schrift springen, sondern in die breitere Schwester derselben. */
    --font-sans: 'Barlow', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
    --font-cond: 'Barlow Condensed', 'Barlow', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;

    /* **Harte Kanten** (Loop 91, auf Wunsch des Auftraggebers: „stop using
       those boxes with the rounded corners"). Vorher stand hier `10px` — und
       daneben, über die Datei verteilt, noch `4px`, `6px`, `7px`, `8px` und
       `999px`: sechs Eckenmaße für eine Oberfläche. Jetzt gibt es **eine**
       Zahl, und jede Ecke im Projekt liest sie (33 Stellen). Wer die runde
       Form je zurückwill, ändert diese Zeile und sonst nichts. */
    --radius:    0;
}

/* ====================================================================
   PALETTEN
   ====================================================================

   Dreizehn Namen, und **nur hier** stehen Hex-Werte. Eine Regel weiter unten,
   die eine eigene Farbe mitbringt, ist der Weg, auf dem eine Palette halb
   umgesetzt wird — genau so sind vor Loop 91 sechs Eckenmaße entstanden.

   | Name            | Rolle                                               |
   |-----------------|-----------------------------------------------------|
   | `--bg`          | Seitengrund                                         |
   | `--surface`     | Karte, Kopf des Spielbogens                         |
   | `--text`        | Schrift                                             |
   | `--text-muted`  | Beiwerk: Datum, Label, Hinweis                      |
   | `--line`        | 1-px-Linien (Karten, Zeilen). 2-px-Linien: `--text` |
   | `--accent`      | Akzent **als Schrift**                              |
   | `--accent-fill` | Akzent **als Fläche** (primärer Knopf)              |
   | `--on-accent`   | Schrift auf `--accent-fill`                         |
   | `--win`         | Sieg, als Fläche                                    |
   | `--on-win`      | Schrift auf `--win`                                 |
   | `--loss`        | Niederlage, als Fläche                              |
   | `--on-loss`     | Schrift auf `--loss`                                |
   | `--warn-bg`     | Grund des Prüfstreifens                             |

   `--accent` und `--accent-fill` sind in Hallenboden derselbe Wert, in
   anderen Paletten müssen sie das nicht sein; deshalb zwei Namen. Eine Farbe
   kann nicht gleichzeitig gute Schrift auf hellem Grund und gute Fläche unter
   weißer Schrift sein — das war schon vor diesem Umbau der Grund für die
   `-ink`-Namen (N-09, Loop 25).

   Die zwei **abgeleiteten** Werte stehen nicht als Hex da, sondern werden aus
   den Tokens gemischt. Das ist der Punkt: sie folgen jeder Palette von selbst,
   und niemand muss fünf Zebra-Töne pflegen.

     --zebra  4 % Schrift auf der Fläche — in hellen Paletten dunkelt das ab,
              in dunklen hellt es auf, beides ohne Fallunterscheidung.
     --zero   55 % Schrift auf der Fläche. Die abgeschwächte Null im
              Spielbogen. Mindestens 3:1, weil sie bei 24 px groß ist und
              trotzdem gelesen werden muss — eine Null, die man für eine leere
              Zelle hält, ist schlimmer als keine.

   Hell oder dunkel folgt **immer** dem Gerät (`prefers-color-scheme`), auch
   wenn ein Konto eine Palette gewählt hat. Deshalb trägt jede Palette zwei
   Blöcke und nicht einen.

   `:root` ist der Fallback und zeigt Hallenboden: ohne Anmeldung (Landing,
   Login, Registrierung, Passwort-Reset) und auf den Wartungsseiten in
   `views/` steht kein `data-palette` am `<html>`, und dort muss trotzdem eine
   vollständige Palette gelten. Der Selektor nennt beides zusammen, damit die
   Standardpalette nicht zweimal gepflegt wird. */

:root,
[data-palette="hallenboden"] {
    --l-bg:          #F6F5F1;
    --l-surface:     #FFFFFF;
    --l-text:        #16181D;
    --l-text-muted:  #595C63;
    --l-line:        #D6D3CB;
    --l-accent:      #B3380A;
    --l-accent-fill: #B3380A;
    --l-on-accent:   #FFFFFF;
    --l-win:         #1E3A6E;
    --l-on-win:      #FFFFFF;
    --l-loss:        #B3380A;
    --l-on-loss:     #FFFFFF;
    --l-warn-bg:     #FBEDE6;

    /* ---- Die Brücke zu den alten Namen ist weg (Loop 157) ----
       Von Loop 108 bis 156 zeigten hier sechs Altnamen auf die neuen Tokens:
       `--border`→`--line`, `--muted`→`--text-muted`, `--surface-2`→`--zebra`
       und `--accent-2`/`--accent-ink`/`--loss-ink`→`--accent`. Der Auftrag
       wollte sie erst gelöscht sehen, „wenn sie nicht mehr gelesen werden" —
       und das ist jetzt der Fall: 97 Leser tragen den neuen Namen.

       Dass dabei keine Farbe gewandert ist, ist nicht geprüft, sondern
       **bewiesen**: wendet man die Brücke auf die alte Datei an, entsteht
       zeichengleich die neue. Eine Ersetzung von `var(--alt)` durch den Wert,
       der dahinter stand, kann den berechneten Wert nicht ändern.

       `--loss-ink` zeigte auf `--accent` und **nicht** auf `--loss`, und das
       war Absicht: seine zwei Leser (`.status-postponed`,
       `[data-sheet-status].is-error`) setzen Schrift, `--loss` ist eine
       Fläche. In Kreide hell ist `--loss` weiß — auf weißem Grund unlesbar. */

    --d-bg:          #111317;
    --d-surface:     #1A1D23;
    --d-text:        #F1EFEA;
    --d-text-muted:  #A3A6AD;
    --d-line:        #33373F;
    --d-accent:      #FF8A5B;
    --d-accent-fill: #FF8A5B;
    --d-on-accent:   #111317;
    --d-win:         #8FB0E8;
    --d-on-win:      #111317;
    --d-loss:        #B8461A;
    --d-on-loss:     #FFFFFF;
    --d-warn-bg:     #2A1A12;

    /* `--zebra` und `--zero` stehen nur einmal oben: sie sind aus `--text`
       und `--surface` gemischt, und die beiden sind hier schon getauscht.
       Eine zweite Kopie wäre eine zweite Stelle, an der sie auseinander-
       laufen können.

       Hier stand bis Loop 157 die Begründung für `--loss-ink`, das auf
       `--accent` zeigte und nicht auf `--loss`. Der Name ist weg, die
       Rechnung gilt weiter und ist der Grund, warum die beiden Leser
       (`.status-postponed`, `[data-sheet-status].is-error`) jetzt
       `var(--accent)` tragen: im Dunklen ist `--loss` (#B8461A) eine
       *Fläche* für weiße Schrift — als Schrift auf dem dunklen Grund
       erreicht es 2,1:1 und ist unlesbar. `--accent` (#FF8A5B) ist
       dieselbe Farbfamilie, nur aufgehellt, und schafft 8,0:1. Oben im
       Hellen fällt das zusammen, weil Hallenboden dort `--loss` und
       `--accent` gleichsetzt. */
}

/* ---------------------------------------------------------------- Linienblau
   Die zweite Palette (Loop 140). Nur die **dreizehn** Farben stehen hier.

   `--zebra` und `--zero` fehlen mit Absicht: sie stehen einmal oben im
   `:root`-Block und sind aus `--text` und `--surface` **gemischt**. `:root` und `[data-palette="…"]` treffen dasselbe `<html>`, haben
   dieselbe Spezifität, und weil dieser Block später steht, gewinnen seine dreizehn
   Werte — die abgeleiteten Tokens rechnen dann von selbst mit den neuen Farben. Eine
   zweite Kopie wäre eine zweite Stelle, an der sie auseinanderlaufen.

   Alle Kontraste nachgerechnet, in beiden Modi, gegen dieselben Schwellen, die
   `tests/run.php` für jede Palette prüft. Die knappsten Werte, und damit die, die
   bei einer Änderung zuerst kippen:

     • `--accent` auf `--warn-bg`    5,95:1  (Schrift, verlangt 4,5)
     • `--accent` auf `--bg`         6,00:1  (Schrift)
     • `--zero`  auf `--zebra`       3,58:1  (Schrift, verlangt 3,0)
     • `--on-loss` auf `--loss`      6,44:1  (dunkler Text auf Orange)

   Der Auftrag nennt als Mindestkontrast 6,0 hell und 6,7 dunkel — das sind die
   Akzentwerte auf `--bg`, und sie stimmen auf die Stelle (6,00 und 7,76 auf `--bg`,
   6,61 und 7,06 auf `--surface`).

   **Sieg und Niederlage sind hier dieselbe Farbe wie der Akzent bzw. Orange** —
   `--win` ist `--accent`, und das ist so bestellt. Unterschieden werden sie über die
   Helligkeit (Abstand 0,232 hell, 0,147 dunkel; verlangt 0,05) und über die Form
   (gefüllt gegen umrandet), nicht über Rot gegen Grün. */
[data-palette="linienblau"] {
    --l-bg:          #F2F4F7;
    --l-surface:     #FFFFFF;
    --l-text:        #0F1B2D;
    --l-text-muted:  #4E5A6A;
    --l-line:        #D2D8E1;
    --l-accent:      #0B57C4;
    --l-accent-fill: #0B57C4;
    --l-on-accent:   #FFFFFF;
    --l-win:         #0B57C4;
    --l-on-win:      #FFFFFF;
    --l-loss:        #E08A1E;
    --l-on-loss:     #0F1B2D;
    --l-warn-bg:     #FDF1E4;

    --d-bg:          #0B1220;
    --d-surface:     #131C2E;
    --d-text:        #E8EDF5;
    --d-text-muted:  #9AA6B8;
    --d-line:        #26324A;
    --d-accent:      #6EA8FF;
    --d-accent-fill: #6EA8FF;
    --d-on-accent:   #0B1220;
    --d-win:         #6EA8FF;
    --d-on-win:      #0B1220;
    --d-loss:        #FFB067;
    --d-on-loss:     #0B1220;
    --d-warn-bg:     #2A1E10;
}

/* -------------------------------------------------------------------- Petrol
   Die dritte Palette (Loop 141). Wie bei Linienblau stehen hier **nur** die
   dreizehn Farben; `--zebra` und `--zero` kommen aus dem `:root`-Block und
   rechnen mit den neuen Werten weiter.

   Petrol ist die erste Palette, in der Sieg und Niederlage **verschiedene
   Farbfamilien** sind: Petrol gegen Violett. Das ist kein Rot/Grün-Paar (darauf
   besteht der Auftrag), und der Helligkeitsabstand trägt die Unterscheidung
   zusätzlich — nachgerechnet, nicht angenommen.

   Die knappsten Werte in dieser Palette, **gemessen** (die erste Fassung dieses
   Absatzes nannte geschätzte Zahlen, drei davon falsch — nachgerechnet steht jetzt,
   was gilt):

     • `--accent` auf `--warn-bg`  hell   5,43:1  (Schrift, verlangt 4,5)
     • `--accent` auf `--bg`       hell   5,80:1  (Schrift)
     • `--zero`  auf `--zebra`     hell   3,49:1  (Schrift, verlangt 3,0)
     • `--on-loss` auf `--loss`    hell   6,43:1  (dunkler Text auf hellem Violett)
     • `--text-muted` auf `--zebra` dunkel 6,19:1 (der niedrigste Wert im Dunklen)
     • `--on-loss` auf `--loss`    dunkel 6,86:1  (Weiß auf dem dunklen Violett)

   Helligkeitsabstand von Sieg und Niederlage: 0,245 hell, 0,408 dark (verlangt 0,05)
   — und das ist hier wichtiger als in den anderen Paletten, weil Petrol und Violett
   als erste beide *farbig* sind. */
[data-palette="petrol"] {
    --l-bg:          #F3F5F2;
    --l-surface:     #FFFFFF;
    --l-text:        #132220;
    --l-text-muted:  #4F5E5B;
    --l-line:        #D3DBD7;
    --l-accent:      #0E6B63;
    --l-accent-fill: #0E6B63;
    --l-on-accent:   #FFFFFF;
    --l-win:         #0E6B63;
    --l-on-win:      #FFFFFF;
    --l-loss:        #C58BE6;
    --l-on-loss:     #132220;
    --l-warn-bg:     #F3EAF8;

    --d-bg:          #0D1614;
    --d-surface:     #14201E;
    --d-text:        #E7EFEC;
    --d-text-muted:  #98A9A4;
    --d-line:        #263633;
    --d-accent:      #4FD1C0;
    --d-accent-fill: #4FD1C0;
    --d-on-accent:   #0D1614;
    --d-win:         #4FD1C0;
    --d-on-win:      #0D1614;
    --d-loss:        #7B3FA0;
    --d-on-loss:     #FFFFFF;
    --d-warn-bg:     #241A2B;
}

/* --------------------------------------------------------------- Vereinsrot
   Die vierte Palette (Loop 142). Nur die dreizehn Farben; `--zebra` und `--zero`
   kommen aus dem `:root`-Block.

   Alle Werte **von `tests/run.php` gemessen**, nicht geschätzt (die Lehre aus Loop
   141: eine Zahl in einem Kommentar ist eine Zusage). Die knappsten:

     • `--zero` auf `--zebra`      hell 3,72:1  (verlangt 3,0 — knappster Wert)
     • `--accent` auf `--warn-bg`  hell 5,85:1  (knappstes Schriftpaar, verlangt 4,5)
     • `--accent` auf `--bg`       hell 6,26:1 · dunkel 7,48:1
     • `--on-loss` auf `--loss`    hell 6,85:1 · dunkel 7,48:1

   **Der Helligkeitsabstand von Sieg und Niederlage ist hier der zweitknappste aller
   vier Paletten: 0,079 hell** (verlangt 0,05; Hallenboden liegt mit 0,080 noch
   dichter dran, Petrol mit 0,408 am weitesten). Dunkel sind es 0,133. Wer an
   `--win` oder `--loss` dreht, dreht hier an dem Paar, das als erstes reißt — und
   dann unterscheiden sich Sieg und Niederlage nur noch im Farbton, also genau in dem
   Merkmal, das eine Rot-Grün-Blindheit nicht hat.

   Dass `--accent` und `--loss` **dieselbe** Farbe sind (hell wie dunkel), ist
   bestellt: der Verein ist rot, und eine Niederlage ist keine zweite Aussage über die
   Farbe des Vereins. Unterschieden wird sie vom Sieg durch Blau. */
[data-palette="vereinsrot"] {
    --l-bg:          #F7F4F2;
    --l-surface:     #FFFFFF;
    --l-text:        #1A1414;
    --l-text-muted:  #5E5655;
    --l-line:        #DDD5D2;
    --l-accent:      #B01E2A;
    --l-accent-fill: #B01E2A;
    --l-on-accent:   #FFFFFF;
    --l-win:         #1C2B4A;
    --l-on-win:      #FFFFFF;
    --l-loss:        #B01E2A;
    --l-on-loss:     #FFFFFF;
    --l-warn-bg:     #FBE9EA;

    --d-bg:          #141112;
    --d-surface:     #1E191A;
    --d-text:        #F3EDEC;
    --d-text-muted:  #AEA4A3;
    --d-line:        #3A3132;
    --d-accent:      #FF7A84;
    --d-accent-fill: #FF7A84;
    --d-on-accent:   #141112;
    --d-win:         #A9BCE6;
    --d-on-win:      #141112;
    --d-loss:        #FF7A84;
    --d-on-loss:     #141112;
    --d-warn-bg:     #2E1618;
}

/* -------------------------------------------------------------------- Kreide
   Die fünfte und letzte Palette (Loop 143) — und der Sonderfall der Reihe.

   Schwarz, Weiß und ein Gelb. Die Kontraste sind die höchsten des Projekts (Text
   21:1), aber **`--loss` ist hier die Farbe des Grundes**: hell `#FFFFFF` auf
   `#FFFFFF`, dunkel `#000000` auf `#000000`. Als *Fläche* ist die Niederlage damit
   unsichtbar, und keine Kontrastrechnung dieses Projekts merkt das — die Regeln
   prüfen **Schrift auf Fläche** (`--on-loss` auf `--loss` sind 21:1, tadellos) und
   nicht **Fläche gegen Seite**.

   Das ist kein Versehen des Auftrags, sondern sein Entwurf: Sieg ist gefüllt,
   Niederlage ist leer. Damit „leer" nicht „nicht da" heißt, bekommen die Leser von
   `--loss` in dieser Palette einen 2-px-Rahmen in `--text` — der Auftrag nennt das
   Abzeichen, es sind aber **sieben** Stellen (siehe `[data-palette="kreide"]`-Regeln
   weiter unten).

   Von `tests/run.php` gemessen: `--text` 21,00:1 auf beiden Gründen, `--text-muted`
   10,96:1 hell und 11,07:1 dunkel (genau die Zahlen, die der Auftrag für Kreide
   nennt), `--zero` 4,74:1, `--accent` 21,00:1 hell und 14,42:1 dunkel. */
[data-palette="kreide"] {
    --l-bg:          #FFFFFF;
    --l-surface:     #FFFFFF;
    --l-text:        #000000;
    --l-text-muted:  #3D3D3D;
    --l-line:        #000000;
    --l-accent:      #000000;
    --l-accent-fill: #000000;
    --l-on-accent:   #FFE14D;
    --l-win:         #000000;
    --l-on-win:      #FFFFFF;
    --l-loss:        #FFFFFF;
    --l-on-loss:     #000000;
    --l-warn-bg:     #FFE14D;

    --d-bg:          #000000;
    --d-surface:     #0A0A0A;
    --d-text:        #FFFFFF;
    --d-text-muted:  #C2C2C2;
    --d-line:        #FFFFFF;
    --d-accent:      #FFE14D;
    --d-accent-fill: #FFE14D;
    --d-on-accent:   #000000;
    --d-win:         #FFE14D;
    --d-on-win:      #000000;
    --d-loss:        #000000;
    --d-on-loss:     #FFFFFF;
    --d-warn-bg:     #2B2500;
}

/* ---- Vereinsfarben (2026-10-10): die sechste Palette, und die einzige ohne eigene Farbe.
   Was hier steht, ist nur ihr **Grund aus Schwarz und Weiß** — genau das, was
   `clubPaletteTokens('#000000')` in `domain.php` rechnet, und `tests/run.php` hält
   beide Stellen gleich. Hat ein Verein Farben hinterlegt, legt `index.php` denselben
   Selektor als `<style>` **hinter** dieses Stylesheet: gleiche Spezifität, der
   spätere gewinnt. Man sieht diesen Block also nur, wenn jemand „Vereinsfarben"
   gewählt hat und der Verein (noch) keine hat. */
[data-palette="verein"] {
    --l-bg:          #F6F6F6;
    --l-surface:     #FFFFFF;
    --l-text:        #0D0D0D;
    --l-text-muted:  #595959;
    --l-line:        #D4D4D4;
    --l-accent:      #000000;
    --l-accent-fill: #000000;
    --l-on-accent:   #FFFFFF;
    --l-win:         #000000;
    --l-on-win:      #FFFFFF;
    --l-loss:        #595959;
    --l-on-loss:     #FFFFFF;
    --l-warn-bg:     #D6D6D6;

    --d-bg:          #101010;
    --d-surface:     #1A1A1A;
    --d-text:        #F2F2F2;
    --d-text-muted:  #A6A6A6;
    --d-line:        #363636;
    --d-accent:      #8A8A8A;
    --d-accent-fill: #8A8A8A;
    --d-on-accent:   #000000;
    --d-win:         #8A8A8A;
    --d-on-win:      #000000;
    --d-loss:        #A6A6A6;
    --d-on-loss:     #000000;
    --d-warn-bg:     #141414;
}
/* Linienblau dunkel (Loop 140). **Ohne diesen Block wäre die Palette halb**:
   `[data-palette="linienblau"]` erbt im Dunklen sonst die Hallenboden-Werte aus
   dem Block darüber — ein blaues Hell und ein orangefarbenes Dunkel. Deshalb
   gehören hell und dunkel in denselben Loop.

   Die Kopfzeile braucht hier **keine** Sonderbehandlung, und das ist nachgerechnet
   statt angenommen: im Dunklen ist `--topbar-bg` die Fläche `--surface` und
   `--topbar-accent` der unveränderte Akzent — #6EA8FF auf #131C2E sind 7,06:1.
   (Meine erste Rechnung nahm die *helle* Formel „Akzent 50 % zu `--bg`" auch für
   das Dunkle und meldete 2,57:1 — einen Befund, den es nicht gibt. Die beiden
   Modi leiten verschieden ab, siehe `.topbar`.) */
/* Petrol dunkel (Loop 141). Hier trägt `--loss` zum ersten Mal **weiße** Schrift
   auf einer dunklen Fläche (#FFFFFF auf #7B3FA0 = 6,86:1) — in den anderen
   Paletten ist `--on-loss` dunkel. Das war der Wert, den ich vor dem Schreiben am
   meisten im Verdacht hatte; er hält mit Abstand. */
/* Vereinsrot dunkel (Loop 142). `--accent` und `--loss` sind auch hier derselbe
   Wert (#FF7A84), der Sieg ist das gedämpfte Blau #A9BCE6 — ihr Helligkeitsabstand
   beträgt 0,133, gemessen von `tests/run.php`. Vor dem Schreiben war das der Wert,
   den ich für den wahrscheinlichsten Befund hielt; er hält. */
/* Kreide dunkel (Loop 143). Hier kehrt sich der Sonderfall um: `--loss` ist
   `#000000` und damit die Farbe von `--bg`, und `--surface` liegt mit `#0A0A0A`
   nur eine Stufe darüber. Die Rahmenregeln unten gelten deshalb in **beiden**
   Modi; sie lesen `--text`, und das ist hier Weiß. */
/* ================================================================= Zwischenschicht
   **Hell oder dunkel ist seit Loop 290 wählbar** (ENTSCHEIDUNGEN.md Frage 11,
   Möglichkeit b, Entscheidung des Betreibers vom 2026-10-02 — ausdrücklich „mit
   Zwischenschicht").

   Vorher stand jede Palette zweimal da: die hellen dreizehn Werte in
   `[data-palette="…"]`, die dunklen in `@media (prefers-color-scheme: dark)`. Eine
   Mediensperre ist für eine **Benutzereinstellung nicht erreichbar** — sie fragt das
   Gerät, nicht das Konto. Die Kostenschätzung aus Loop 145 nannte zwei Wege: ~65
   Deklarationen doppelt oder dreizehn Zwischentokens je Palette. Der Betreiber hat den
   zweiten gewählt, und das ist der hier.

   Jede Palette schreibt ihre Werte jetzt **einmal** als Paar: `--l-*` für hell,
   `--d-*` für dunkel, beide in **einem** Block ohne Mediensperre. Die dreizehn
   wirklichen Tokens entstehen hier, in drei kleinen Blöcken:

     1. `:root, [data-mode="light"]`  — hell, und der Rückfall ohne Attribut
     2. `[data-mode="dark"]`          — dunkel, auf Wunsch, unabhängig vom Gerät
     3. die Mediensperre              — dunkel, **wenn das Gerät es sagt und niemand
                                        ausdrücklich hell gewählt hat**

   Was hier dreifach steht, ist eine **Namenszuordnung** (`--bg: var(--d-bg)`), kein
   Farbwert. Eine Farbe ändert sich weiter an genau einer Stelle; das war die Bedingung.

   Die Reihenfolge der drei Blöcke ist Teil der Rechnung: 1 und 2 haben dieselbe
   Spezifität (0,1,0), also gewinnt der spätere — deshalb steht „dunkel auf Wunsch"
   hinter „hell". Block 3 ist mit `:not()` spezifischer (0,2,0) und muss das sein: er
   überstimmt den hellen Rückfall, lässt aber eine **ausdrückliche** helle Wahl stehen.

   `--zebra` und `--zero` stehen weiter nur einmal (unten): sie mischen aus den
   **finalen** `--text`/`--surface` und rechnen damit in jedem Modus von selbst mit. */

/* **Die drei Blöcke treffen auch jedes Element mit eigenem `data-palette`** — die
   Zeilen der Palettenwahl in *Mein Konto* und die Farbtupfer im Fuß. Ein
   `var(--l-bg)` wird an dem Element aufgelöst, an dem die Deklaration steht, und
   vererbt wird der **fertige** Wert: stünde die Zuordnung nur am `<html>`, setzte
   eine Zeile zwar ihre eigenen `--l-*`, läse aber weiter das `--bg` der aktiven
   Palette — fünf gleiche Reihen, und bei jedem Klick alle in der neuen Farbe.
   Die Spezifität bleibt gestaffelt: hell (0,1,0), dunkel auf Wunsch (0,2,0 für
   das innere Element), Mediensperre (0,3,0). */
:root,
[data-mode="light"],
[data-palette] {
    --bg: var(--l-bg);
    --surface: var(--l-surface);
    --text: var(--l-text);
    --text-muted: var(--l-text-muted);
    --line: var(--l-line);
    --accent: var(--l-accent);
    --accent-fill: var(--l-accent-fill);
    --on-accent: var(--l-on-accent);
    --win: var(--l-win);
    --on-win: var(--l-on-win);
    --loss: var(--l-loss);
    --on-loss: var(--l-on-loss);
    --warn-bg: var(--l-warn-bg);
}

[data-mode="dark"],
[data-mode="dark"] [data-palette] {
    --bg: var(--d-bg);
    --surface: var(--d-surface);
    --text: var(--d-text);
    --text-muted: var(--d-text-muted);
    --line: var(--d-line);
    --accent: var(--d-accent);
    --accent-fill: var(--d-accent-fill);
    --on-accent: var(--d-on-accent);
    --win: var(--d-win);
    --on-win: var(--d-on-win);
    --loss: var(--d-loss);
    --on-loss: var(--d-on-loss);
    --warn-bg: var(--d-warn-bg);
}

@media (prefers-color-scheme: dark) {
    :root:not([data-mode="light"]),
    :root:not([data-mode="light"]) [data-palette] {
        --bg: var(--d-bg);
        --surface: var(--d-surface);
        --text: var(--d-text);
        --text-muted: var(--d-text-muted);
        --line: var(--d-line);
        --accent: var(--d-accent);
        --accent-fill: var(--d-accent-fill);
        --on-accent: var(--d-on-accent);
        --win: var(--d-win);
        --on-win: var(--d-on-win);
        --loss: var(--d-loss);
        --on-loss: var(--d-on-loss);
        --warn-bg: var(--d-warn-bg);
    }
}

/* Die zwei abgeleiteten Tokens, einmal für alle Paletten und beide Modi. */
:root {

    --zebra: color-mix(in srgb, var(--text)  4%, var(--surface));
    --zero:  color-mix(in srgb, var(--text) 55%, var(--surface));
}


* { box-sizing: border-box; }

/* Sprungmarke (N-09). Sie liegt außerhalb des Bildes, bis sie den Fokus
   bekommt — sichtbar für die Tastatur, unsichtbar für alle anderen. Nicht
   `display: none`: was nicht angezeigt wird, bekommt auch keinen Fokus. */
.skip-link {
    position: absolute; left: -9999px; top: 0; z-index: 100;
    background: var(--surface); color: var(--text);
    border: 1px solid var(--line); border-radius: var(--radius);
    padding: 12px 18px; font-weight: 600;
}
.skip-link:focus { left: 0; text-decoration: none; }
/* Der Sprung setzt den Fokus auf <main tabindex="-1">; ein Rahmen um die
   halbe Seite hilft dabei niemandem. */
main:focus { outline: none; }

body {
    margin: 0;
    background: var(--bg);
    color: var(--text);
    font: 400 15px/1.55 var(--font-sans);
    /* **Tabellenziffern für die ganze Seite.** Die Vorgabe lautet „bei allen
       Zahlen"; das ist eine Eigenschaft, die vererbt wird, also gehört sie
       einmal hierhin und nicht in jede Regel, die irgendwann eine Zahl zeigt.
       Vorher stand sie an vierzehn Stellen — und fehlte überall sonst, zum
       Beispiel in der Spalte mit den Rückennummern.

       Der Gewinn ist nicht Schönheit: in einer Spalte aus proportionalen
       Ziffern sitzt die 1 schmaler als die 8, dadurch wandert das Komma von
       Zeile zu Zeile und man vergleicht zwei Zahlen nicht mehr mit dem Auge,
       sondern muss sie lesen. Genau das soll ein Spielbogen ersparen. */
    font-variant-numeric: tabular-nums;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

.wrap { max-width: 1100px; margin: 0 auto; padding: 0 18px; }

/* ---------- top bar ----------

   **Die Leiste ist in beiden Modi dunkel** (Auftrag: hell → Grund `--text`,
   dunkel → `--surface`). Damit ist sie die einzige Fläche des Projekts, auf der
   die Palette *umgekehrt* gilt: die Schriftfarbe der Seite wäre hier
   unsichtbar. Deshalb hat sie fünf eigene, **abgeleitete** Tokens. Sie stehen
   hier und nicht in den Palettenblöcken, weil sie nichts Neues festlegen —
   sie rechnen aus den dreizehn Tokens aus, was auf dunklem Grund daraus wird,
   und das gilt für jede Palette gleichermaßen.

   Der Grund für `--topbar-accent` ist eine Rechnung, die der Auftrag so nicht
   vorsieht: der Akzent von Hallenboden hell (`#B3380A`) erreicht auf der
   dunklen Leiste nur **2,95:1**. Der 3-px-Unterstrich des aktiven Punktes ist
   reine Grafik — er trägt keinen Text, der ihn erklärt — und müsste 3:1
   schaffen. Er hätte also gefehlt, und zwar unsichtbar: die Seite wäre grün
   durchgelaufen und der aktive Navigationspunkt nicht zu erkennen.

   Deshalb wird der Akzent für die Leiste zum Seitengrund hin aufgehellt, zu
   **50 %**. Nachgerechnet über alle fünf hellen Paletten des Auftrags:
   Hallenboden 7,24 · Linienblau 6,92 · Petrol 6,84 · Vereinsrot 6,73 ·
   Kreide 5,32 — alle über 4,5:1, obwohl 3:1 genügen würde. Der Spielraum ist
   Absicht, weil vier dieser Paletten noch nicht gebaut sind. Kreide ist dabei
   der aufschlussreiche Fall: dort sind `--accent` und `--text` beide `#000000`,
   die Mischung mit dem weißen Grund macht daraus Grau, und auch das trägt.

   Im Dunklen ist die Leiste `--surface` — also derselbe Grund, gegen den die
   Kontrastprüfung die Palette schon rechnet. Dort zeigen alle fünf Tokens
   einfach auf ihr Gegenstück und mischen nichts. */
.topbar {
    --topbar-bg:     var(--text);
    --topbar-ink:    var(--bg);
    --topbar-muted:  color-mix(in srgb, var(--bg) 72%, var(--text));
    --topbar-accent: color-mix(in srgb, var(--accent) 50%, var(--bg));
    --topbar-line:   color-mix(in srgb, var(--bg) 25%, var(--text));

    background: var(--topbar-bg);
    color: var(--topbar-ink);
    border-bottom: 1px solid var(--topbar-line);
    position: sticky; top: 0; z-index: 10;
}
/* **Dieselbe Zwischenschicht auch für die Kopfzeile** (Loop 290). Ohne sie wäre eine
   ausdrücklich dunkle Seite oben hell abgeleitet: dunkle Fläche, aber die Leiste rechnet
   weiter mit der hellen Formel (`--topbar-bg: var(--text)`), und der Akzent darauf fällt
   auf 2,57:1 — genau die Fehlrechnung, die in Loop 140 schon einmal als Befund gemeldet
   wurde, der es nicht gab. Verdoppelt wird hier **keine Farbe**, nur die Zuordnung von
   fünf Namen; die Werte stehen in der Palette. */
[data-mode="dark"] .topbar {
    --topbar-bg:     var(--surface);
    --topbar-ink:    var(--text);
    --topbar-muted:  var(--text-muted);
    --topbar-accent: var(--accent);
    --topbar-line:   var(--line);
}
@media (prefers-color-scheme: dark) {
    :root:not([data-mode="light"]) .topbar {
        --topbar-bg:     var(--surface);
        --topbar-ink:    var(--text);
        --topbar-muted:  var(--text-muted);
        --topbar-accent: var(--accent);
        --topbar-line:   var(--line);
    }
}

/* **Die Leiste der Vereinspalette: Primärfarbe auf Weiß** (Betreiber, 2026-10-10).
   Zwei Anläufe am selben Tag stehen hinter dieser Regel: zuerst war die Leiste
   schwarz wie bei allen Paletten, dann die Primärfarbe als Fläche — und darauf
   „beißt sich" die zweite Vereinsfarbe als Strich. Jetzt ist sie die Karte
   (`--surface`, hell also Weiß), und alles, was auf ihr Farbe trägt, trägt die
   Primärfarbe: Schriftzug, aktiver Punkt, Strich, Abzeichen. Die zweite Farbe
   kommt auf der Leiste nicht vor. Die ruhenden Punkte sind `--text-muted`.

   Alle fünf Werte sind **fertige** Tokens, also schon hell oder dunkel — deshalb
   genügt ein Block für beide Modi. Dass die Paare tragen, rechnet die
   Palettenprüfung ohnehin: `--accent` und `--text-muted` auf `--surface`.

   `:root` steht im Selektor, damit er (0,3,0) wiegt und **hinter** der Mediensperre
   darüber gewinnt; `[data-palette="verein"] .topbar` allein unterläge ihr im Dunklen.
   Die Vorschau in *Mein Konto* schreibt `data-palette` am `<html>` um und nimmt die
   Leiste damit von selbst mit. */
:root[data-palette="verein"] .topbar {
    --topbar-bg:     var(--surface);
    --topbar-ink:    var(--accent);
    --topbar-muted:  var(--text-muted);
    --topbar-accent: var(--accent);
    --topbar-line:   var(--line);
}

/* 64 px auf dem Desktop, 56 px mobil (Auftrag). `min-height` und nicht
   `height`: die Leiste darf umbrechen — sie trägt bis zu zwölf Tabulatorstopps
   —, und eine feste Höhe würde den Inhalt dann abschneiden statt die Leiste
   wachsen zu lassen. Die Zahl ist also eine Zusage nach unten, keine Grenze
   nach oben. */
.topbar-inner {
    display: flex; align-items: center; gap: 20px; flex-wrap: wrap;
    min-height: 64px; padding-block: 8px;
}

/* Die Marke: Zeichen und Wort in Barlow Condensed 800, in Versalien wie jeder
   Titel. `.brand strong` färbt nur den zweiten Wortteil — auf der dunklen
   Leiste in `--topbar-accent`, demselben aufgehellten Akzent wie der
   Unterstrich. */
.brand {
    display: inline-flex; align-items: center; gap: 8px;
    color: var(--topbar-ink); font-family: var(--font-cond); font-weight: 800;
    font-size: 22px; line-height: 1; letter-spacing: .02em; text-transform: uppercase;
}
.brand:hover { text-decoration: none; }
.brand strong { color: var(--topbar-accent); font-weight: 800; }
/* Das Zeichen (Loop 109). Vorher stand hier das Emoji 📊 — eine Grafik, die
   jedes Gerät anders zeichnet, die in Schwarzweiß druckt, was sie will, und die
   Vorlesegeräte als „Balkendiagramm" vorlesen, obwohl daneben schon der Name
   steht. Jetzt drei eckige Balken als Inline-SVG, `aria-hidden`, Höhe an der
   Schrift (`em`), damit es mit ihr skaliert. Seit Loop 736 („My Team Stats“)
   steigen sie an, und die Raute darüber trägt den Akzent; die Form steht in
   `SS_BRAND_MARK` (pwa.php), nicht hier. */
.brand-mark { flex: 0 0 auto; width: 1em; height: 1em; display: block; }
.brand-mark .bar { fill: currentColor; }
.brand-mark .mark-accent { fill: var(--topbar-accent); }

/* Die Navigation. Der aktive Punkt bekommt einen 3-px-Unterstrich in
   `--topbar-accent` statt einer Füllung (Auftrag). `box-shadow: inset` und
   nicht `border-bottom`: ein Rahmen würde den Punkt um 3 px höher machen als
   seine Nachbarn, und dann springt die Zeile beim Wechsel. */
.nav { display: flex; gap: 4px; flex: 1; flex-wrap: wrap; }
.nav a {
    display: inline-flex; align-items: center; min-height: 40px;
    padding: 6px 12px; border-radius: var(--radius); color: var(--topbar-muted);
}
.nav a:hover { color: var(--topbar-ink); text-decoration: none; }
.nav a.active {
    color: var(--topbar-ink); font-weight: 600;
    box-shadow: inset 0 -3px 0 0 var(--topbar-accent);
}
.season-switch select, .filterbar select, select, input, button { font: inherit; }
.season-badge { color: var(--text-muted); font-variant-numeric: tabular-nums; }

/* Die beiden Umschalter der Leiste: native, eckige `<select>` mit 40 px Höhe
   (Auftrag). Die Beschriftung ist ein echtes `<label class="sr-only">` und
   kein `aria-label` — ein `<label>` vergrößert zusätzlich die Trefferfläche
   und wird auch von Übersetzungswerkzeugen gefunden. Sichtbar ist es nicht,
   weil die Leiste eng ist und der gewählte Wert selbst schon sagt, worum es
   geht.

   `color-scheme: dark` auf dem Feld: das Feld steht auf der dunklen Leiste,
   während die Seite hell sein kann. Ohne die Zeile zeichnet der Browser seinen
   Pfeil und seine Aufklappliste in den Farben des *hellen* Modus — dunkler
   Pfeil auf dunklem Grund. */
.topbar select {
    min-height: 40px; padding: 0 10px; border-radius: var(--radius);
    border: 1px solid var(--topbar-line);
    background: var(--topbar-bg); color: var(--topbar-ink);
    color-scheme: dark;
}

/* Alles, was in der Leiste steht, liest ihre Tokens und nicht die der Seite.
   Diese Regeln sind **Überschreibungen**: dieselben Bausteine stehen auch im
   Seiteninhalt und bleiben dort, wie sie sind. Ohne sie wären in der hellen
   Palette graue Links (`--text-muted`, gegen den *hellen* Grund gerechnet) auf
   einer fast schwarzen Leiste gelandet — 1,4:1, also nichts. */
.topbar .user-menu a { color: var(--topbar-muted); }
.topbar .user-menu a:hover { background: transparent; color: var(--topbar-ink); }
.topbar .user-menu a.active {
    background: transparent; color: var(--topbar-ink);
    box-shadow: inset 0 -3px 0 0 var(--topbar-accent);
}
/* Das Zählerabzeichen auf der Leiste. Es nimmt `--topbar-accent` und nicht
   `--accent-fill`, aus demselben Grund wie der Unterstrich: `#B3380A` erreicht
   auf der dunklen Leiste nur 2,95:1, der Fleck wäre also kaum zu finden. Die
   Zahl darin stünde mit 6,02:1 lesbar da — aber nur, wenn man die Plakette
   erst einmal sieht. Mit dem aufgehellten Akzent sind es 7,24:1 nach außen
   **und** nach innen. Außerhalb der Leiste bleibt `--accent-fill`. */
.topbar .badge-pending, .topbar .badge-alarm {
    background: var(--topbar-accent); color: var(--topbar-bg);
}
.topbar .btn-quiet { color: var(--topbar-muted); border-color: var(--topbar-line); }
.topbar .btn-quiet:hover { color: var(--topbar-ink); border-color: var(--topbar-accent); }
.topbar .sport-single { color: var(--topbar-muted); background: transparent; border: 1px solid var(--topbar-line); }

/* Die Sprachwahl: zwei schlichte Links, keine Auswahlliste. Sie muss auf der
   Anmeldeseite funktionieren — ohne Sitzung, ohne JavaScript —, und zwei Links
   sind weniger Aufwand als ein Formular. Die aktive Sprache ist **hinterlegt**
   und nicht nur gefärbt: Farbe allein wäre kein Merkmal (N-09). */
/* ---- Der Palettenumschalter für Gäste (Loop 292, Frage 12) ----
   Dieselben Maße und dieselbe Lage wie der Sprachumschalter: beide stehen in der
   Kopfzeile, also lesen beide `--topbar-*` und **nie** eine Seitenfarbe (Loop 109 — ein
   `--text-muted` landet dort bei 1,4:1). Der Unterschied ist der Inhalt: Sprache ist ein
   Kürzel („DE"), eine Palette ist eine Farbe. Deshalb ein Punkt statt Text — und der
   Name steht als `.sr-only` daneben, damit Sprachausgabe, Tastatur und Suchfunktion ihn
   finden (dieselbe Lösung wie bei der abgeschwächten Null im Spielbogen, Loop 131).

   **Der Punkt liest `var(--accent-fill)` innerhalb von `[data-palette="…"]`** am Link
   selbst: so zeigt jeder Punkt die Farbe *seiner* Palette, nicht die der gerade
   aktiven — genau die Überlegung, die die Palettenwahl auf der Kontoseite trägt. Eine
   Kante in `--topbar-line`, weil Kreide hell einen fast weißen Akzent hat und der Punkt
   auf der dunklen Leiste sonst verschwindet. */
.palette-switch { display: flex; align-items: center; gap: 4px; }
.palette-switch a, .palette-switch .active {
    display: inline-flex; align-items: center; justify-content: center;
    width: 28px; height: 28px; border-radius: var(--radius);
}
.palette-switch .active { background: var(--topbar-accent); }
.palette-dot {
    width: 16px; height: 16px; border-radius: var(--radius);
    background: var(--accent-fill);
    border: 1px solid var(--topbar-line);
}

.lang-switch { display: flex; align-items: center; gap: 2px; }
.lang-switch a, .lang-switch .active {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 40px; min-height: 40px; padding: 0 8px;
    border-radius: var(--radius); font-size: 13px; font-weight: 600;
    letter-spacing: .04em;
}
.lang-switch a { color: var(--topbar-muted); }
.lang-switch a:hover { color: var(--topbar-ink); text-decoration: none; }
.lang-switch .active { background: var(--topbar-accent); color: var(--topbar-bg); }

/* Der Mannschaftsname neben der Marke. Auf dem Telefon ist er die eine Angabe,
   die beim Blättern durch einen Spielbogen oben stehen bleibt — die Leiste
   klebt. Lange Vereinsnamen werden gekürzt und nicht umgebrochen: eine
   zweizeilige Leiste frisst den halben Bogen. Der volle Name steht im `title`
   und, weil ein `title` auf einem Telefon niemandem hilft, ohnehin in der
   Überschrift der Seite. */
.topbar-team {
    font-family: var(--font-cond); font-weight: 700; text-transform: uppercase;
    font-size: 17px; letter-spacing: .02em; color: var(--topbar-ink);
    max-width: 28ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ---- Der Menüknopf (Loop 110) ----

   Der Schalter selbst ist ein Kontrollkästchen; `:checked` ist reines CSS und
   verhält sich in jedem Browser gleich. Warum nicht `<details>`: siehe die
   Begründung an der Stelle in `index.php`.

   `.menu-toggle` erbt `.sr-only` aus dem Markup — es liegt also außerhalb des
   Bildes, **behält aber den Fokus**. Deshalb gehört der Ring an das `<label>`,
   das an seiner Stelle sichtbar ist; sonst würde die Tastatur einen Fokus
   setzen, den niemand sieht. */
.menu-button { display: none; }
.menu-toggle:focus-visible + .menu-button {
    outline: 3px solid var(--topbar-accent); outline-offset: 2px;
}

/* Die Gruppe, die auf dem Telefon hinter dem Knopf liegt. Auf dem Desktop ist
   sie einfach eine Reihe — kein `display: none` und kein Knopf, also auch
   keine Möglichkeit, dass die Navigation dort verschwindet. */
.topbar-menu { display: flex; align-items: center; gap: 20px; flex: 1; flex-wrap: wrap; }

@media (max-width: 640px) {
    /* Mobil 56 px statt 64 px (Auftrag) und ein engerer Abstand: auf einem
       Telefon ist die Leiste so hoch wie ein Daumen breit, und jedes Pixel
       darüber fehlt dem Spielbogen. */
    .topbar-inner { min-height: 56px; gap: 10px; padding-block: 6px; }
    .brand { font-size: 19px; }
    /* Der Wortteil der Marke tritt zurück — **nur wenn** ein Mannschaftsname
       da ist, der den Platz braucht. Das Zeichen bleibt immer stehen, die
       Marke verschwindet also nicht. Kann ein Browser `:has()` nicht, bleibt
       das Wort stehen und es wird eine Spur enger: die schlechtere von zwei
       funktionierenden Varianten, nicht ein Fehler. */
    .topbar-inner:has(.topbar-team) .brand span { display: none; }
    .topbar-team { font-size: 15px; max-width: none; flex: 1 1 auto; min-width: 0; }

    .menu-button {
        display: inline-flex; align-items: center; justify-content: center;
        flex: 0 0 auto; width: 44px; height: 44px; margin-left: auto;
        border: 1px solid var(--topbar-line); border-radius: var(--radius);
        color: var(--topbar-ink); background: transparent; cursor: pointer;
    }
    .menu-button svg { fill: currentColor; display: block; }
    .menu-toggle:checked + .menu-button {
        background: var(--topbar-accent); color: var(--topbar-bg);
        border-color: var(--topbar-accent);
    }

    /* Zu: die Gruppe ist weg. Auf: sie liegt als eigener Block unter der
       Leiste. Zwei Klassen und eine Pseudoklasse schlagen die Grundregel
       `.topbar-menu` mit ihrer einen Klasse — die Reihenfolge entscheidet hier
       nicht allein. */
    .menu-toggle:not(:checked) ~ .topbar-menu { display: none; }
    .topbar-menu {
        flex-basis: 100%; flex-direction: column; align-items: stretch; gap: 4px;
        padding: 6px 0 10px; border-top: 1px solid var(--topbar-line);
    }
    .topbar-menu .nav { flex-direction: column; }
    .topbar-menu .nav a, .topbar-menu .user-menu a { min-height: 44px; }
    .topbar-menu .user-menu { flex-wrap: wrap; margin-left: 0; width: 100%; }
    /* Der aktive Punkt steht hier übereinander, nicht nebeneinander: ein
       Unterstrich unter einer vollen Zeile liest sich wie ein Trennstrich.
       Also an die Seite damit. */
    .topbar-menu .nav a.active, .topbar-menu .user-menu a.active {
        box-shadow: inset 3px 0 0 0 var(--topbar-accent);
    }

    /* Der Saisonumschalter bleibt sichtbar (Auftrag: Marke, Mannschaft,
       Saison) und bekommt die ganze zweite Zeile, sobald es neben Marke und
       Mannschaftsname zu eng wird. */
    .season-switch { flex: 1 1 100%; }
    .season-switch select { width: 100%; }
}

/* Sportart — der vierte Punkt der Kopfzeile. Mit genau einer Sportart gibt es
   nichts zu wählen; dann steht der Name da, damit die Kopfzeile trotzdem sagt,
   worüber die Seite spricht. */
.sport-switch { display: flex; align-items: center; gap: 6px; }
.sport-single { color: var(--text-muted); padding: 6px 10px; border-radius: var(--radius);
                background: var(--zebra); font-size: 13px; }

/* Die Platzhalterseite einer vorgemerkten Sportart. Sie sieht bewusst nicht aus
   wie eine Auswertung — keine Kacheln, keine Tabelle, kein Diagramm: hier steht
   nichts Gerechnetes, und das soll man sehen, bevor man liest. */
.sport-placeholder { max-width: 62ch; }
.sport-lead { font-size: 17px; color: var(--text-muted); margin: 0 0 18px; }
.sport-todo { margin: 0 0 18px; padding-left: 20px; }
.sport-todo li { margin: 0 0 8px; }
.sport-actions { display: flex; flex-wrap: wrap; gap: 12px; margin: 18px 0 0; }
.sport-actions .btn { min-height: 44px; display: inline-flex; align-items: center; }
@media (max-width: 600px) {
    .sport-actions .btn { flex: 1 1 100%; justify-content: center; }
}

/* ---------- headings ----------
   Mit den harten Kanten (Loop 91) trägt die Form weniger: eine abgerundete
   Karte hebt sich schon durch ihren Umriss ab, ein Rechteck nicht. Die
   Gliederung muss dann aus der Schrift kommen — deshalb hier ein Schritt mehr
   Abstand zwischen h1 und h2 und ein engerer Zeilenfall in der Überschrift
   selbst. Mehr wird an der Typografie nicht gedreht: alles Weitere wäre eine
   Geschmacksfrage, die ohne Blick auf den Bildschirm niemand entscheiden kann. */
/* Seitentitel und Modulüberschriften: Barlow Condensed in Versalien. Der
   schmale Schnitt ist hier kein Geschmack, sondern der Grund, warum man
   überhaupt eine zweite Schrift mitlädt — „SAISONWERTE FELDSPIELERINNEN" ist
   in Versalien aus einer normal breiten Schrift auf einem Telefon zu lang und
   bricht um; schmal passt sie in eine Zeile.

   Zwei Dinge gehören zu Versalien dazu, sonst sehen sie gedrängt aus:
   positives `letter-spacing` (Großbuchstaben brauchen mehr Luft als
   Kleinbuchstaben, für die die Schrift ausgelegt ist) und ein engerer
   Zeilenfall, weil keine Unterlängen mehr in den Zwischenraum ragen.
   Das vorherige `-.01em` war das Gegenteil und für Kleinbuchstaben gedacht. */
h1 {
    font-family: var(--font-cond); font-weight: 800; text-transform: uppercase;
    font-size: 34px; line-height: 1.1; margin: 28px 0 16px; letter-spacing: .015em;
}
h2 {
    font-family: var(--font-cond); font-weight: 700; text-transform: uppercase;
    font-size: 21px; line-height: 1.15; margin: 0 0 14px; letter-spacing: .02em;
    display: flex; align-items: baseline; gap: 10px;
}
/* Der Zusatz hinter der Überschrift (Saison, Anzahl, Datum) ist **kein**
   Titel, sondern ein Nebensatz: zurück in Barlow, zurück in Gemischtschrift.
   Ohne diese Rücknahme erbt er Schrift und Versalien vom Elternteil und die
   Überschrift hat zwei Titel. Dasselbe gilt für den „mehr"-Link rechts. */
h1 small, h2 small {
    font-family: var(--font-sans); font-size: 14px; font-weight: 400;
    text-transform: none; letter-spacing: 0;
    color: var(--text-muted); margin-left: 8px;
}
h2 .more {
    font-family: var(--font-sans); margin-left: auto; font-size: 13px;
    font-weight: 400; text-transform: none; letter-spacing: 0;
}
.crumb { margin: 20px 0 0; }

/* ---------- cards & layout ---------- */
/* Karte: `--surface`, **1 px** in `--line`, 20 px Innenabstand, kein Schatten.
   Die Linienstärke ist Teil des Systems und nicht Geschmack: 1 px in `--line`
   trennt *innerhalb* einer Ebene (Karten, Tabellenzeilen), 2 px in `--text`
   trennt *Ebenen* (Tabellenkopf, Summenzeile, Kennzahlenleiste, Knopf). Wer
   das mischt, nimmt der Seite ihre Gliederung — mit harten Ecken und ohne
   Schatten ist die Linienstärke das einzige, was noch Hierarchie trägt. */
.card { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 20px; margin-bottom: 20px; }
.cols { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; align-items: start; }
@media (max-width: 820px) { .cols { grid-template-columns: 1fr; } }

/* ---------- Kennzahlenleiste ----------

   **Eine** Leiste mit 2 px Rahmen in `--text`, die Felder durch 1 px in
   `--line` getrennt — nicht mehr vier einzelne Kästchen mit Abstand dazwischen.
   Vier Kästchen sind vier Dinge; eine Leiste mit Trennstrichen ist ein Ding mit
   vier Zahlen, und genau das ist gemeint.

   Die Trennstriche entstehen aus `gap: 1px` über einem Grund in `--line`: die
   Lücken *sind* die Linien. Der naheliegende Weg — `border-left` an jedem Feld
   außer dem ersten — bricht genau dann, wenn die Leiste umbricht: `:first-child`
   ist nur das erste Feld **der Leiste**, nicht das erste jeder Zeile, und auf
   einem Telefon mit zwei Spalten steht dann mitten in der zweiten Reihe ein
   Strich zu viel. So hier gibt es das Problem nicht, in keiner Richtung. */
.stat-grid {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 1px; background: var(--line);
    border: 2px solid var(--text); margin-bottom: 20px;
}
/* **Genau zwei Spalten** (Loop 136, Auftrag „2×2-Raster"). Die Grundregel darüber
   verteilt so viele Kacheln, wie Platz haben (`auto-fit`, mindestens 150 px) — das
   ist richtig für die Mannschaftsseite mit sechs Werten und falsch für vier
   bestellte Plätze, die je nach Fensterbreite als 4×1, 2×2 oder 3+1 erschienen.
   Ein Raster, dessen Form vom Zufall der Fensterbreite abhängt, ist kein Raster.

   Der 2-px-Rahmen und die 1-px-Fugen kommen unverändert aus `.stat-grid`; dieser
   Zusatz ändert **nur** die Spaltenzahl. Auf dem Telefon bleiben zwei Spalten:
   bei 360 px sind das 180 px je Kachel, und eine 52-px-Zahl passt hinein. */
.stat-grid-2x2 { grid-template-columns: repeat(2, 1fr); }
.stat { background: var(--surface); border: 0; border-radius: var(--radius); padding: 16px 14px; text-align: center; }
/* Kennzahl. Barlow Condensed 800 mit **52 px** (Auftrag) — die Zahl ist das,
   was man aus zwei Metern Entfernung lesen soll. Dass der schmale Schnitt hier
   trägt, ist der Grund, warum er überhaupt mitgeladen wird: „27:24" in 52 px
   normal breit passt in kein Feld von 150 px. */
.stat-value {
    display: block; font-family: var(--font-cond); font-weight: 800;
    font-size: 52px; line-height: 1; letter-spacing: .01em;
    color: var(--accent);
}
/* Auf dem Telefon einen Schritt kleiner. Der Auftrag nennt 52 px, und das gilt
   dort, wo die Leiste breit ist; in einem Feld von 150 px sprengt ein
   siebenstelliger Wert wie „487:412" bei 52 px die Spalte, und eine
   abgeschnittene Kennzahl ist keine Kennzahl. Die Rangfolge bleibt: es ist
   weiter die größte Zahl der Seite. */
@media (max-width: 640px) {
    .stat-value { font-size: 38px; }
}
.stat-label { display: block; font-size: 12px; color: var(--text-muted); text-transform: uppercase; letter-spacing: .04em; margin-top: 4px; }

/* Die dritte Zeile einer Kennzahl: „laut Bogen 27:24" (Loop 124).
   Sie steht **nur** da, wenn die Bogensumme vom eingetragenen Ergebnis
   abweicht — eine Angabe, die immer dasteht, liest nach dem dritten Mal
   niemand mehr, und diese soll genau dann auffallen, wenn ein Bogen unfertig
   ist. Im Akzent und nicht im Leisen: sie ist eine Rückfrage, kein Beiwerk.
   Dieselbe Entscheidung wie beim Prüfstreifen, nur eine Zeile groß. */
.stat-note {
    display: block; margin-top: 4px;
    font-size: 12px; font-weight: 600; color: var(--accent);
    font-variant-numeric: tabular-nums;
}

/* ---------- tables ----------
   Kopf in 12 px Versalien mit **2 px** Unterkante in `--text` (Auftrag): der
   Kopf ist eine andere Ebene als die Zeilen, und das trägt nach dem Wegfall von
   Schatten und runden Ecken allein die Linienstärke. Die Zeilen untereinander
   bleiben bei 1 px in `--line`. */
/* `--th-ell` ist die Breite, auf die ein Spaltenkopf gekürzt wird. Sie steht
   **hier** in der einen `.table`-Regel und nicht unten bei ihrer Erklärung: ein
   Selektor, zwei Blöcke, ist viermal der Fehler gewesen (`.table-scroll` Loop
   116, `.scope-switch` Loop 119, beinahe `.team-meta` Loop 127) — die Prüfung
   liest den **ersten** Block und urteilt dann über den falschen. Gefunden hat es
   in Loop 131 eine Zusicherung, die ins Leere griff, obwohl das Stylesheet
   richtig war. Warum gekürzt wird, steht weiter unten bei `.th-ell`. */
.table { width: 100%; border-collapse: collapse; font-variant-numeric: tabular-nums; --th-ell: 11ch; }
.table th { text-align: left; font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--text-muted); padding: 8px 10px; border-bottom: 2px solid var(--text); }
/* Sortierbare Spaltenköpfe (F-19). Der Kopf bleibt ein Kopf: gleiche Größe,
   gleiche Farbe, nur anklickbar. Die aktive Spalte ist an der Textfarbe und am
   Pfeil zu erkennen — Farbe allein wäre für Rot-Grün-Blindheit kein Merkmal.
   44 px Höhe, weil auch diese Liste auf dem Telefon bedient wird. */
.sort-link { display: inline-flex; align-items: center; gap: 4px; min-height: 44px;
             color: inherit; text-decoration: none; }
.sort-link:hover { color: var(--text); text-decoration: underline; }
.sort-link.active { color: var(--text); font-weight: 700; }

/* Der stapelbare Spaltenkopf der Kadertabelle (F-34). Dieselbe Regel wie oben
   und derselbe Grund: der Kopf bleibt ein Kopf. Ein `<button>` bringt Rahmen,
   Fläche, eigene Schrift und eine eigene Ausrichtung mit — alles vier wird
   hier zurückgenommen, sonst sitzt in jeder Spalte eine graue Schaltfläche.

   `text-align: inherit` ist der Teil, den man leicht übersieht: ohne ihn
   stünde die Beschriftung der elf Zahlenspalten plötzlich links, weil ein
   Knopf seinen Inhalt zentriert — und die Kopfzeile stünde nicht mehr über
   ihren Zahlen. Den Knopf baut erst `app.js`; ohne JavaScript greift keine
   dieser Regeln, weil es keinen Knopf gibt. */
.sort-btn { display: inline-flex; align-items: center; gap: 4px; min-height: 44px;
            width: 100%; margin: 0; padding: 0; border: 0; background: none;
            font: inherit; letter-spacing: inherit; text-transform: inherit;
            color: inherit; text-align: inherit; cursor: pointer; }
/* Rechtsbündige Spalte: der Knopf füllt die Zelle, also muss sein Inhalt an
   das rechte Ende — sonst klebt „Tore ▼" am linken Rand über den Zahlen. */
.num .sort-btn { justify-content: flex-end; }
.sort-btn:hover { color: var(--text); }
.sort-btn.active { color: var(--text); font-weight: 700; }
.sort-link .th-ell, .sort-btn .th-ell { min-width: 0; flex: 0 1 auto; }
/* Pfeil und Stufennummer. Nicht `aria-hidden` zuliebe klein gehalten, sondern
   weil sie neben die Überschrift passen müssen, ohne die Spalte zu verbreitern
   — die Breite war der ganze Punkt von F-33. */
.sort-mark { flex: 0 0 auto; font-size: 11px; font-weight: 700; color: var(--accent);
             font-variant-numeric: tabular-nums; }
/* Der Satz, der die drei Klicks erklärt. `app.js` setzt ihn hinter die
   Tabelle; ohne Skript steht er nicht da, weil es dann nichts zu erklären
   gibt. */
.sort-hint { margin: 8px 0 0; }
/* Direktvergleich (F-16): der bessere Wert. Fett **und** eine Hinterlegung —
   Fettdruck allein verschwindet in einer Spalte aus lauter Zahlen, Farbe
   allein wäre für Farbenblindheit kein Merkmal (N-09). */
.compare-table .better { font-weight: 700; color: var(--accent);
                         background: color-mix(in srgb, var(--accent) 10%, transparent); }
.compare-table td:first-child { color: var(--text-muted); }

/* ---------- Die gespiegelten Balken (F-16, Loop 148) ----------

   Sie stehen **zwischen** den beiden Zahlenspalten und wachsen von der Mitte nach
   außen: links die erste Mannschaft, rechts die zweite. Zwei Balken, die an derselben
   Kante beginnen, vergleicht das Auge ohne eine Achse zu lesen — das ist der Sinn von
   „gespiegelt".

   Die Länge steht im SVG und nicht in einem `style`-Attribut: eine Balkenlänge ist eine
   Angabe, und im Markup dieses Projekts gibt es kein `style=` (A-26). Genau dafür sind
   die Diagramme hier Inline-SVG.

   **Gefüllt gegen umrandet** für „besser gegen nicht besser" — vierte Anwendung
   derselben Unterscheidung (Plaketten, gestapelte Balken, Chips, Verlaufspunkte), weil
   zwei Helligkeiten eines Akzents gegeneinander nur 2,1:1 erreichen (Loop 125). Bei
   Gleichstand oder fehlender Zahl ist **keiner** gefüllt; eine Füllung wäre dann eine
   Behauptung. */
.mirror { width: 120px; max-width: 100%; height: 14px; display: block; }
.mirror-cell { width: 120px; padding: 2px 6px; vertical-align: middle; }
.mirror-head { width: 120px; }
/* Die Mittellinie ist der Bezug, an dem beide Balken beginnen — ohne sie schweben zwei
   Rechtecke im Nichts. 1 px in `--line`, wie jede andere ordnende Linie. */
.mirror-axis { stroke: var(--line); stroke-width: 1; vector-effect: non-scaling-stroke; }
.mirror-bar { fill: var(--surface); stroke: var(--accent); stroke-width: 1.5;
              vector-effect: non-scaling-stroke; }
.mirror-bar.is-better { fill: var(--accent); stroke: var(--accent); }
/* Auf dem Telefon ist die Spalte das erste, was weichen darf: zwei Zahlen und ihre
   Beschriftung sind die Angabe, der Balken ist ihre Wiederholung. */
@media (max-width: 640px) {
    .mirror-cell, .mirror-head { display: none; }
}
/* Ein verlassener Kader (F-15) bleibt lesbar, tritt aber zurück: er sagt etwas
   über gestern, und die Zeile darüber sagt etwas über heute. */
.squad-former { opacity: .7; }
.squad-former .access-state { color: var(--text-muted); }
/* Eine Zeile zum Kopieren (Cron, Befehle). Sie darf umbrechen — auf einem
   Telefon ist ein waagerechter Rollbalken in einer Karte schlimmer als drei
   Zeilen. */
.code-line { background: var(--zebra); border: 1px solid var(--line); border-radius: var(--radius);
             padding: 10px 12px; font-size: 13px; white-space: pre-wrap; word-break: break-all;
             overflow-wrap: anywhere; margin: 8px 0 0; }
.table th[aria-sort="ascending"], .table th[aria-sort="descending"] { background: var(--zebra); }
.table td { padding: 9px 10px; border-bottom: 1px solid var(--line); }
.table tbody tr:last-child td { border-bottom: 0; }
/* **Dezentes Zebra** (Auftrag). Bisher hob sich eine Zeile nur beim Überfahren
   mit der Maus hervor — auf einem Telefon also nie, und genau dort wird die
   Kadertabelle mit elf Zahlenspalten gelesen. `--zebra` ist 4 % Schrift auf der
   Fläche; mehr wäre ein Muster und keine Hilfe.
   Das Zebra steht **vor** der Hover-Regel, damit die Maus weiter gewinnt. */
.table tbody tr:nth-child(even) { background: var(--zebra); }
.table tbody tr:hover { background: var(--zebra); }
.table.compact td, .table.compact th { padding: 6px 8px; font-size: 14px; }
.num, th.num { text-align: right; }
/* The squad table carries one column per category — on a phone that is wider
   than the screen, and shrinking the font would make it unreadable. It scrolls
   sideways instead, with the wrapper providing the only horizontal scrollbar.
   `max-width: 100%` statt einer freien Breite: ohne sie wächst der Behälter
   mit der Tabelle und schiebt die Karte auf — der Rollbalken gehört hierher,
   nicht an die Seite (Loop 101). */
.table-scroll { max-width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; margin: 0 -18px; padding: 0 18px; scroll-behavior: smooth; }
.table-scroll .table { min-width: 100%; white-space: nowrap; }

/* ---------- Spaltenköpfe kürzen (Loop 101) ----------
   Eine Kaderzeile trägt bis zu elf Kategorien, und „7 Meter rausgeholt" ist
   als Überschrift viermal so breit wie die zweistellige Zahl darunter. Genau
   diese Köpfe waren der Grund, dass die Tabelle auch auf einem großen Schirm
   breiter wurde als die Karte.

   Gekürzt wird deshalb in CSS, nicht im Text: im Markup steht weiterhin das
   ganze Wort, `title` gibt es beim Überfahren zurück, und Vorlesen, Suchen im
   Browser, Drucken und die CSV-Ausgabe bekommen es ungekürzt. Eine zweite,
   handgeschriebene Kurzform je Kategorie wäre die zweite Wahrheit darüber,
   wie eine Kategorie heißt (A-08) — und sie müsste in beiden Sprachen
   gepflegt werden.

   Die Breite steht in `--th-ell` an der Tabelle, damit die Telefonfassung sie
   überschreiben kann, ohne die Regel zu wiederholen; `.table-wide` bekommt
   dieselbe Regel für die Datenzellen, wo ein Gegnername sonst dasselbe tut
   wie die lange Überschrift. Die Festlegung selbst steht oben in der einen
   `.table`-Regel — zwei Blöcke für einen Selektor waren hier bis Loop 131 ein
   Fallstrick für jede Prüfung, die den ersten liest. */
.th-ell { display: block; max-width: var(--th-ell);
          overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Im sortierbaren Kopf steckt der Text in einem Flex-Element neben dem Pfeil
   und braucht dort `min-width: 0`, sonst besteht er auf seiner vollen Breite
   und die Kürzung greift nie. Die Regel dazu steht oben bei `.sort-link` und
   `.sort-btn` — dort, wo das Flex-Element entsteht. */

/* Auf dem Telefon bleibt der Rollbalken die Antwort — elf Zahlenspalten
   passen dort auch gekürzt nicht nebeneinander. Er muss aber weniger weit
   tragen: kürzere Köpfe und schmalere Zellen sparen zusammen rund ein
   Drittel der Breite, und `title` ist ohne Mauszeiger ohnehin kein Ersatz für
   das ganze Wort — wer es lesen will, öffnet die Spielerseite. */
@media (max-width: 640px) {
    .table { --th-ell: 7ch; }
    .table.compact td, .table.compact th { padding: 6px 6px; }
}

/* Auf Papier gibt es keinen Mauszeiger und damit kein `title`: dort wird
   nichts gekürzt, sondern umgebrochen. Das Blatt ist breiter als ein Telefon
   und kostet keinen Rollbalken — der einzige Grund für die Kürzung fällt
   weg. (`print.php` bringt für den Spielbogen sein eigenes Stylesheet mit;
   diese Regel gilt dem Ausdruck einer gewöhnlichen Seite.) */
@media print {
    .th-ell { max-width: none; overflow: visible; white-space: normal; }
}
/* Summenzeile: 2 px oben **und unten** in `--text` (Auftrag). Sie ist keine
   Zeile der Tabelle, sondern deren Abschluss — dieselbe Linienstärke wie der
   Kopf, weil sie dieselbe Rolle am anderen Ende hat. */
.sum-row td { border-top: 2px solid var(--text); border-bottom: 2px solid var(--text); background: var(--zebra); }
/* Zwischenüberschrift in einer Tabelle (Loop 100): die Spieleliste der
   Mannschaftsseite trennt „steht an" von „gespielt". Links ausgerichtet und
   gedämpft — sie ordnet die Zeilen, sie ist keine weitere Zeile. */
.group-row th { text-align: left; font-size: 12px; font-weight: 700; color: var(--text-muted);
                text-transform: uppercase; letter-spacing: .04em;
                background: var(--zebra); border-top: 2px solid var(--line); }
tr.inactive td { opacity: .55; }
.right, th.right { text-align: right; }
.rank { color: var(--text-muted); width: 34px; }
.strong { font-weight: 700; }
tr.leader td { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.muted { color: var(--text-muted); }
.small { font-size: 12px; }

/* ---------- teams ---------- */
.team-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 14px; margin-bottom: 22px; }
.team-card { display: flex; flex-direction: column; gap: 3px; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 16px; color: var(--text); }
.team-card:hover { border-color: var(--accent); text-decoration: none; }
.team-badge { font-size: 26px; }
.team-name { font-weight: 600; }
.team-meta { font-size: 12px; color: var(--accent); margin-top: 6px; }

/* ---------- Beitritt und Zugang (A-12) ----------
   The club card is no longer a single link: it carries a join/leave button as
   well, so the link is a child and the card is the box around it. */
.team-card > a { display: flex; flex-direction: column; gap: 3px; color: var(--text); }
.team-card > a:hover { text-decoration: none; }
.team-card.is-member { border-left: 3px solid var(--accent); }
.team-card form { margin-top: 10px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* ---------- Vereinslogo (Loop 90) ----------
   Vier Größen, eine Form. Das Bild wird über `object-fit: cover` mittig
   beschnitten statt verzerrt: ein gestauchtes Wappen sieht nach Fehler aus,
   ein angeschnittenes nach Absicht.

   `.is-empty` ist der Verein ohne Wappen — dieselbe Fläche, aber mit den
   Anfangsbuchstaben darin. Ohne diesen Zweig stünde in jeder Liste an der
   Stelle des Bildes ein Loch, und die Kacheln wären unterschiedlich hoch. */
.club-logo { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
             background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
             object-fit: cover; overflow: hidden; }
.club-logo.is-empty { font-weight: 600; color: var(--text-muted); letter-spacing: .5px; }

.club-logo-sm { width: 22px;  height: 22px;  font-size: 10px; }
.club-logo-md { width: 34px;  height: 34px;  font-size: 13px; }
.club-logo-lg { width: 52px;  height: 52px;  font-size: 19px; }
.club-logo-xl { width: 108px; height: 108px; font-size: 38px; }

/* Die Angabenzeile unter dem Kopf einer **Detailseite** (Loop 127 für die
   Mannschaft, Loop 135 auch für die Spielerin): Verein, Kadergröße, Spielzahl und
   der eigene Zugang dort — Position, Mannschaft und Saison hier. Drei bis vier
   Dinge in einer Zeile; untereinander wären es vier Zeilen für vier Wörter.

   **Der Name hieß bis Loop 135 `.team-head-meta`** und war damit eine Unwahrheit,
   sobald die Spielerinnenseite dieselbe Zeile bekam. Umbenannt statt kopiert: zwei
   Namen für ein Aussehen sind einer, den beim nächsten Mal niemand mitändert (die
   Lehre aus Loop 127 über doppelte Schlüssel, hier für Klassen). Neun Stellen in
   drei Dateien, einzeln ersetzt und von vier Zusicherungen bewacht.

   **Nicht `.team-meta`** — die ist vergeben (Karten in `clubs.php` und
   `teams.php`, dort 12 px im Akzent). Derselbe Griff wie bei `.scope-switch` in
   Loop 119.

   Der Trenner kommt aus CSS und nicht aus dem Markup: ein „·" zwischen zwei
   `<span>` wäre Text, den eine Sprachausgabe mitliest („Punkt"). Ein
   `::before` ist für sie nicht vorhanden. */
.detail-head-meta, .player-counts {
    display: flex; align-items: center; gap: 0 10px; flex-wrap: wrap;
    margin: 6px 0 20px; color: var(--text-muted); font-size: 14px;
}
.detail-head-meta > * + *::before,
.player-counts > * + *::before { content: '·'; margin-right: 10px; color: var(--line); }
/* `.player-counts` ist dieselbe Vorrichtung an einer anderen Stelle: die Zeile
   „12 Spiele · 2,4 Tore/Spiel" unter dem Kennzahlenraster (Loop 136). Sie steht
   **nicht** im Kopf, weil sie keine Auskunft über die Person ist, sondern über die
   gewählte Auswahl — sie ändert sich mit dem Spielfilter. Eine eigene Regel mit
   demselben Inhalt wären zwei, die auseinanderlaufen; eigener Abstand genügt, weil
   sie direkt unter einem Rahmen hängt und nicht unter einer Überschrift. */
.player-counts { margin: -12px 0 20px; }
/* Der eigene Zugang ist das Einzige in dieser Zeile, das nicht beschreibt,
   sondern **bestimmt**: wer „Mitglied" ist, sieht dieselben Zahlen und darf
   nichts ändern. Vor dem Abzeichen steht deshalb kein Trenner — es gehört nicht
   in die Aufzählung. */
.detail-head-meta .access-state { color: var(--text); }
.detail-head-meta .access-state::before { content: none; margin-right: 0; }

/* Die Aktionszeile der Mannschaftsseite (Loop 128). Was sie enthält, entscheidet
   das Recht des Lesers: zwischen zwei und vier Knöpfen. Deshalb `wrap` und
   gleiche Abstände statt eines Rasters mit festen Spalten — ein Raster für vier
   Knöpfe hätte bei zwei zwei Löcher.

   **Nicht `.export-bar`**: die Klasse steht auf vier weiteren Seiten
   (`games`, `players`, `matchday`, `myteams`) und bringt dort einen 13-px-Knopf
   mit. Hier sind es Hauptaktionen in voller Größe — 44 px bleiben 44 px.

   **Kein primärer Knopf darin** — der Grund steht in `pages/team.php`: der
   handball.net-Abschnitt trägt schon einen, und zwei primäre Knöpfe auf einer
   Ansicht meldet `render-check` zu Recht. Alle vier sind gleich laut; ihre
   Betonung ist der Platz direkt unter der Überschrift. */
.team-actions { display: flex; flex-wrap: wrap; gap: 10px; margin: 0 0 8px; }
.team-actions-hint { margin: 0 0 20px; }

/* Die Kadertabelle der Mannschaftsseite (Loop 130): elf Kategorien plus Nummer,
   Name, Position und Spiele sind breiter als jedes Telefon, also rollt sie
   waagerecht — und wer dabei die Namensspalte verliert, liest eine Zahlenwüste.

   Die Bauanleitung steht ausführlich bei `.sheet-table .sheet-who` (Loop 115);
   hier dieselben drei Dinge, die `position: sticky` braucht: **eigener Grund**
   (eine klebende Zelle ist durchsichtig, die Zahlen schieben sich sonst
   darunter durch), **eine rechte Kante** (sonst verschmilzt die letzte klebende
   Spalte mit der ersten Zahlenspalte) und eine **Stapelordnung** über den
   rollenden Zellen.

   Der Unterschied zum Spielbogen: dort ist es *eine* Spalte mit Nummer und
   Namen, hier sind es **zwei** — die Nummer bleibt eine eigene, sortierbare
   Spalte (der Grund steht in `pages/team.php`). `--rank-w` ist deshalb die eine
   Zahl, die beide lesen: die Breite der Nummernspalte **ist** der Abstand, an
   dem die Namensspalte hängen bleibt. Zwei getippte Werte wären zwei, die beim
   nächsten Mal um 2 px auseinanderliegen — und dann klafft beim Rollen eine
   Lücke oder die Spalten überdecken sich. */
.team-table { --rank-w: 34px; }
.team-table .rank, .team-table .team-who {
    position: sticky; z-index: 2;
    background: var(--surface);
}
.team-table .rank { left: 0; width: var(--rank-w); min-width: var(--rank-w); }
.team-table .team-who { left: var(--rank-w); border-right: 2px solid var(--text); }
/* In der Summenzeile denselben Grund wie die Zeile, sonst leuchten die beiden
   klebenden Zellen aus ihr heraus (`.sum-row td` setzt `--zebra`). */
.team-table .sum-row .rank, .team-table .sum-row .team-who { background: var(--zebra); }
/* Eine ausgegraute Zeile wird über `opacity` gedämpft (`tr.inactive td`). Auf
   einer klebenden Zelle heißt durchsichtig aber **wirklich** durchsichtig: die
   Zahlen der Nachbarspalten würden beim Rollen durch den Namen hindurch
   scheinen. Deshalb bekommt die klebende Zelle einer inaktiven Zeile ihre
   Dämpfung über die Schriftfarbe und behält einen deckenden Grund. */
.team-table tr.inactive .rank, .team-table tr.inactive .team-who {
    opacity: 1; color: var(--text-muted);
}
.team-table tr.inactive .team-who a { color: var(--text-muted); }

/* Die Überschrift einer Detailseite trägt links ein Quadrat daneben: das Wappen
   auf der Vereins- und Mannschaftsseite, die Rückennummer auf der Spielerinnenseite
   (Loop 135). Eine Regel für beide — zwei Blöcke mit demselben Inhalt wären zwei,
   die auseinanderlaufen. */
.club-head, .player-head { display: flex; align-items: center; gap: 14px; }
.club-head small { display: block; }

/* **Die Rückennummer im 2-px-Quadrat** (Loop 135, Auftrag). Dieselbe Kantenstärke
   wie Tabellenkopf, Summenzeile und Segment-Umschalter: 2 px in `--text` heißt in
   diesem Entwurf „eine eigene Ebene".

   Quadratisch heißt hier wirklich quadratisch — gleiche `width` und `height`, und
   `--radius` steht ohnehin auf 0. Die Größe folgt dem Wappen der Mannschaftsseite
   (`.club-logo-lg`, 52 px), damit zwei Detailseiten nebeneinander nicht zwei
   verschiedene Köpfe haben.

   `font-variant-numeric: tabular-nums` erbt die Seite von `body`; was die Zahl
   hier braucht, ist der schmale Schnitt — eine zweistellige Nummer soll ein
   Quadrat füllen und es nicht breiter machen. Deshalb auch `line-height: 1`:
   sonst schiebt die Zeilenhöhe die Ziffer aus der Mitte. */
.player-number {
    display: inline-flex; align-items: center; justify-content: center;
    width: 52px; height: 52px; flex: 0 0 auto;
    border: 2px solid var(--text); border-radius: var(--radius);
    font-family: var(--font-cond); font-weight: 800; font-size: 26px; line-height: 1;
    letter-spacing: 0;
}

.logo-editor { display: flex; flex-wrap: wrap; gap: 20px; align-items: flex-start; }
.logo-preview { margin: 0; display: flex; flex-direction: column; gap: 8px; align-items: center;
                text-align: center; max-width: 140px; }
.logo-forms { flex: 1 1 260px; display: flex; flex-direction: column; gap: 12px; }
/* Das Dateifeld über die volle Breite: der Dateiname darin ist lang, und auf
   dem Handy schiebt ein schmales Feld den Knopf aus dem Bild. */
.logo-forms input[type="file"] { width: 100%; box-sizing: border-box;
    background: var(--bg); color: var(--text); border: 1px solid var(--line);
    border-radius: var(--radius); padding: 8px 10px; font-size: 13px; }

.access-list { list-style: none; margin: 0; padding: 0; }
.access-list li { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px;
                  padding: 10px 2px; border-bottom: 1px solid var(--line); }
.access-list li:last-child { border-bottom: 0; }
.access-team { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; flex: 1 1 220px; }
.access-team em { font-style: italic; }

/* One shape for every access level; only the colour says which one it is.
   A level nobody styled falls back to the neutral grey of .access-state. */
/* Seit Loop 91 eckig — und deshalb zusätzlich in Versalien mit Sperrung. Eine
   Pille ist auch ohne Schriftauszeichnung als Zustand erkennbar, ein Rechteck
   nicht: ohne diesen Zusatz sähe die Plakette aus wie ein missratener Knopf.
   Dieselbe Auszeichnung tragen `.status` und `.demo-badge`. */
.access-state { font-size: 12px; padding: 3px 8px; border-radius: var(--radius);
                text-transform: uppercase; letter-spacing: .06em; font-weight: 600;
                border: 1px solid var(--line); color: var(--text-muted); white-space: nowrap; }
.access-state.badge-member  { color: var(--accent); border-color: var(--accent); }
.access-state.badge-editor  { color: var(--accent); border-color: var(--accent); }
/* Protokoll der Leitung (2026-10-22): ein zurückgenommener Eintrag bleibt
   stehen, tritt aber zurück — durchgestrichen, damit es auch ohne Farbe gilt. */
.team-log .is-undone .access-team strong { text-decoration: line-through; color: var(--text-muted); }
/* Postfach der Vereinsleitung (2026-10-22): Absätze bleiben, Erledigtes tritt zurück. */
.club-inbox .message-body { white-space: pre-line; overflow-wrap: anywhere; }
.club-inbox .is-done .message-body { color: var(--text-muted); }
/* Stat-Katalog (2026-10-22): der Name einer eigenen Kategorie ist im
   Bearbeitungsmodus ein Feld in der Tabelle. */
.catalog-table input[type="text"] { width: 100%; min-width: 10em; }
.access-state.badge-manager { color: var(--accent); border-color: var(--accent); font-weight: 600; }
.access-state.badge-pending { color: var(--accent); border-color: currentColor; }
/* Bearbeitungsrecht am Verein (A-27). Dieselbe Farbe wie `.badge-member`: es
   ist derselbe Satz — „dieses Konto ist zugelassen" —, nur eine Ebene höher. */
.access-state.badge-editor  { color: var(--accent); border-color: var(--accent); }
/* Die laufende Saison (L16). Dieselbe Pille, weil „laufend" dieselbe Art
   Aussage ist wie „freigegeben": ein Zustand, kein Knopf. */
.access-state.badge-current { color: var(--accent); border-color: var(--accent); font-weight: 600; }

/* --- Konto ↔ Spieler und mehrere Mannschaften (A-13) -------------------- */

/* „Das bin ich" sits between the name and the numbers, so it is a row, not a
   form block: on a phone it wraps into two lines instead of pushing the first
   chart below the fold. */
.link-state { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin: 12px 0 0; }
.link-state form { margin: 0; }

/* The squad checkboxes. A fieldset rather than a second <select multiple>:
   a multiple-select on a phone hides everything but the first two options and
   gives no hint that it can be scrolled. */
.squad-picker { grid-column: 1 / -1; margin: 0; padding: 10px 12px;
                border: 1px solid var(--line); border-radius: var(--radius); }
.squad-picker legend { padding: 0 6px; font-size: 13px; color: var(--text-muted); }
.squad-picker .check { display: inline-flex; align-items: center; gap: 6px;
                       margin: 4px 14px 4px 0; }

/* ---------- Die Palettenauswahl (Loop 145) ----------

   Fünf Zeilen, je ein echtes Radio. 56 px Höhe (Auftrag) — hoch genug für den
   Daumen und zugleich Platz für fünf Farbfelder neben dem Namen.

   **Der Trick dieser Ansicht steht im Markup, nicht hier**: jedes `<label>` trägt sein
   eigenes `data-palette`, also greift auf ihm derselbe Block wie am `<html>`. Die
   Farbfelder lesen deshalb `var(--bg)`, `var(--text)` usw. **ihrer eigenen** Palette.
   Ohne das Attribut wären es fünfmal die Farben der gerade aktiven — fünf identische
   Reihen, die behaupten, es gäbe nichts zu wählen.

   Was hier *nicht* steht, ist ein Hex-Wert: die Farben kommen aus den Palettenblöcken,
   und `tests/run.php` lässt außerhalb von ihnen keinen zu.

   Die Zeile der gewählten Palette ist am Radio zu erkennen und zusätzlich an der
   Rahmenstärke (`:has()` wird **nicht** gebraucht — das Radio selbst ist sichtbar).
   Der Fokusring kommt aus der allgemeinen Regel für `:focus-visible`. */
/* ---- Hell oder dunkel (Loop 291, Frage 11b) ----
   Dieselbe Form wie die Palettenwahl darunter, nur ohne Farbfelder: diese drei Zeilen
   unterscheiden sich im **Namen**, nicht in Farben (siehe die Begründung in
   `pages/account.php`). Die Maße sind dieselben — 56 px Zeilenhöhe und ein 22-px-Radio
   sind die Daumenmaße aus dem Auftrag, und zwei Listen übereinander, die sich in der
   Zeilenhöhe unterscheiden, sehen nach einem Fehler aus. */
.mode-set { margin: 0 0 14px; padding: 10px 12px;
            border: 1px solid var(--line); border-radius: var(--radius); }
.mode-set legend { padding: 0 6px; font-size: 13px; color: var(--text-muted); }
.mode-option {
    display: flex; flex-direction: row; align-items: center; gap: 12px;
    min-height: 56px; padding: 6px 4px;
    border-bottom: 1px solid var(--line);
    font-size: 15px; color: var(--text); cursor: pointer;
}
.mode-option:last-child { border-bottom: 0; }
.mode-option:hover { background: var(--zebra); }
.mode-option input { width: 22px; height: 22px; flex: 0 0 auto; }
.mode-name { flex: 1 1 auto; }

.palette-set { margin: 0 0 14px; padding: 10px 12px;
               border: 1px solid var(--line); border-radius: var(--radius); }
.palette-set legend { padding: 0 6px; font-size: 13px; color: var(--text-muted); }
.palette-option {
    display: flex; flex-direction: row; align-items: center; gap: 12px;
    min-height: 56px; padding: 6px 4px;
    border-bottom: 1px solid var(--line);
    font-size: 15px; color: var(--text); cursor: pointer;
}
.palette-option:last-child { border-bottom: 0; }
.palette-option:hover { background: var(--zebra); }
.palette-option input { width: 22px; height: 22px; flex: 0 0 auto; }
/* Der Name nimmt den Platz, die Felder bleiben rechts. */
.palette-name { flex: 1 1 auto; }
.palette-swatches { display: inline-flex; gap: 3px; flex: 0 0 auto; }
/* Jedes Feld 20 × 20, eckig (`--radius`), mit einer 1-px-Kante in `--line`: ohne sie
   verschwindet das Feld für `--bg` auf einer Karte, die fast dieselbe Farbe hat —
   genau der Fall, den Kreide hell zum Extrem treibt (Grund und Fläche sind beide
   weiß). Die Kante ist keine Zierde, sie ist die Sichtbarkeit. */
.palette-swatch {
    width: 20px; height: 20px; border-radius: var(--radius);
    border: 1px solid var(--line);
}
.swatch-bg     { background: var(--bg); }
.swatch-text   { background: var(--text); }
.swatch-accent { background: var(--accent-fill); }
.swatch-win    { background: var(--win); }
.swatch-loss   { background: var(--loss); }
/* Nur in der Probe auf der Vereinsseite: dort ist der Hinweisgrund die Stelle, an der
   die dritte Vereinsfarbe landet, und ohne dieses Feld sähe man sie in der Probe nicht. */
.swatch-warn   { background: var(--warn-bg); }

/* „Vorschau – noch nicht gespeichert" (Loop 158). Das Element legt `app.js` an,
   es steht also nur im Dokument, wenn es auch etwas zu sagen haben kann — eine
   Regel braucht es trotzdem, sonst steht der Satz ohne Abstand am Feld.

   Im Akzent und nicht in `--text-muted`: er sagt, dass der Zustand auf dem
   Bildschirm und der in der Datenbank auseinandergehen, und das ist dieselbe
   Aussage wie die des Prüfstreifens. Der Akzent erreicht in jeder Palette
   mindestens 4,5:1 auf `--surface` — `tests/run.php` besteht darauf. */
.palette-preview { margin: -8px 0 14px; color: var(--accent); font-weight: 600; }

/* Nur für Vorlesegeräte: Text, der im Bild keinen Platz hat (Loop 79). */
/* Nur für Vorlesegeräte. Zwei Namen, **eine** Regel: `.sr-text` ist der ältere
   Name und steht an einigen Stellen im Markup, `.sr-only` nennt der
   Gestaltungsauftrag. Eine zweite Regel mit denselben Werten wäre die Stelle,
   die beim nächsten Umbau nur halb geändert wird.
   Nicht `display: none`: was nicht angezeigt wird, wird auch nicht vorgelesen. */
.sr-only, .sr-text { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* Das Ausrufezeichen neben dem Kontonamen: nur für Administratoren, nur
   wenn wirklich etwas ansteht (siehe urgentProblems()).

   Die Fläche ist jetzt `--accent-fill` mit `--on-accent` darauf und nicht mehr
   ein eigenes Bernstein: die neue Palette hat **keine dritte Signalfarbe**,
   und der Auftrag nennt für das Zählerabzeichen ausdrücklich `--accent-fill`.
   Die beiden gehören zusammen und sind in jeder Palette gegeneinander
   gerechnet — das vorher hier fest eingetragene `#1c1200` galt dagegen nur für
   das eine Bernstein, das es nicht mehr gibt. */
.badge-alarm { display: inline-block; min-width: 18px; padding: 0 6px; border-radius: var(--radius);
               background: var(--accent-fill); color: var(--on-accent); font-size: 11px; font-weight: 700;
               line-height: 18px; text-align: center; }

/* The counter in the topbar and next to „Zugang verwalten". */
.badge-pending { display: inline-block; min-width: 18px; padding: 0 5px; border-radius: var(--radius);
                 background: var(--accent-fill); color: var(--on-accent); font-size: 11px; font-weight: 700;
                 line-height: 18px; text-align: center; }

.no-access { max-width: 46rem; }
.no-access form { margin: 14px 0; display: flex; flex-wrap: wrap; gap: 8px; }
.no-access input[type="text"] { flex: 1 1 200px; }

@media (max-width: 600px) {
    .access-list li { flex-direction: column; align-items: stretch; }
    .access-list form { justify-content: flex-start; }
}

/* ---------- lists ---------- */
.match-list { list-style: none; margin: 0; padding: 0; }
.match-list li { border-bottom: 1px solid var(--line); }
.match-list li:last-child { border-bottom: 0; }
.match-list a { display: grid; grid-template-columns: 62px 1fr auto 1fr; gap: 10px; align-items: center; padding: 9px 4px; color: var(--text); }
.match-list a:hover { background: var(--zebra); text-decoration: none; }
.m-date { color: var(--text-muted); font-size: 12px; }
/* Ergebnis in der Spieleliste: Barlow Condensed 700, einen Schritt größer als
   die Zeile. Es ist die Zahl, nach der man die Liste überfliegt. */
.m-score { font-family: var(--font-cond); font-weight: 700; font-size: 17px; }
.result { font-weight: 700; padding: 2px 8px; border-radius: var(--radius); }
.result.win { background: var(--win); color: var(--on-win); }
/* **Sieg war bis Loop 111 in der Akzentfarbe gezeichnet, nicht in `--win`.**
   Das ist der Fund dieses Loops: `--win` und `--on-win` standen seit Loop 108
   in jeder Palette, aber die Plakette las weiter `--accent` — in Hallenboden
   also dasselbe Rotbraun wie die **Niederlage**, nur heller hinterlegt. Sieg
   und Niederlage unterschieden sich allein in der Sättigung derselben Farbe.
   Der Kontrasttest hat das nicht gesehen, weil er die Tokens gegeneinander
   rechnet und nicht nachsieht, wer sie liest. Jetzt ist Sieg das Blau.

   Unentschieden bleibt **umrandet, nicht gefüllt** — so steht es im Auftrag
   („U nur umrandet"), und das ist auch der Grund, warum die Palette ohne dritte
   Signalfarbe auskommt: Sieg und Niederlage sind Flächen, das Unentschieden
   ist die Abwesenheit einer Fläche. Der Rahmen liegt innen (`inset`), damit
   alle drei gleich groß bleiben und die Zeile beim Wechsel nicht springt. */
.result.draw { background: transparent; color: var(--text); box-shadow: inset 0 0 0 2px var(--text); }
.result.loss { background: var(--loss); color: var(--on-loss); }

/* ---------- Kreide: die Niederlage braucht eine Kante (Loop 143) ----------

   In Kreide ist `--loss` die Farbe des Grundes — hell `#FFFFFF` auf `#FFFFFF`, dunkel
   `#000000` auf `#000000`. Jede Fläche in `--loss` ist dort also unsichtbar, und keine
   Kontrastprüfung dieses Projekts merkt das: sie rechnen **Schrift auf Fläche**
   (`--on-loss` auf `--loss` sind 21:1) und nie **Fläche gegen Seite**.

   Der Auftrag nennt eine Stelle („dem N-Badge einen 2-px-Rahmen"). Es sind **sieben**
   Leser von `--loss`, und davon brauchen **sechs** etwas (`.btn-danger` nicht: der
   erbt von `.btn` schon 2 px in `--text`). Gesucht statt geraten — `grep var(--loss)`.

   **Warum die Plaketten gestrichelt sind und nicht einfach umrandet.** Das
   Unentschieden ist in diesem Entwurf schon „umrandet statt gefüllt"
   (`.result.draw`, `.form-draw`, beide mit `inset 0 0 0 2px var(--text)`). Eine
   Niederlage mit derselben Kante wäre von einem Unentschieden nicht zu unterscheiden
   — das wäre schlimmer als unsichtbar, weil es etwas Falsches behauptet. Ohne Kante
   wiederum sieht die Niederlage aus wie „kein Ergebnis". Gestrichelt ist die dritte
   Form, und der Buchstabe (S/U/N) steht ohnehin darin (N-09).

   `outline` und nicht `border`: es verändert die Größe nicht, und mit
   `outline-offset: -2px` liegt es innen wie der Schatten des Unentschiedens — alle
   drei Plaketten bleiben gleich groß, die Zeile springt beim Wechsel nicht.

   Die übrigen vier Stellen haben kein konkurrierendes „Unentschieden" und bekommen
   deshalb eine gewöhnliche Kante in `--text`. */
[data-palette="kreide"] .result.loss,
[data-palette="kreide"] .form-loss {
    outline: 2px dashed var(--text);
    outline-offset: -2px;
}
[data-palette="kreide"] .counter-input.is-error { border-color: var(--text); }
[data-palette="kreide"] .flash-error { border-color: var(--text); }
[data-palette="kreide"] .chart-bar.chart-bad {
    stroke: var(--text); stroke-width: 1.5; vector-effect: non-scaling-stroke;
}
[data-palette="kreide"] .chart-bar-stack.chart-bad { stroke: var(--text); }

.scorer-list { margin: 0; padding-left: 22px; }
.scorer-list li { display: grid; grid-template-columns: 1fr auto; gap: 4px 10px; padding: 6px 0; border-bottom: 1px solid var(--line); }
.scorer-list li:last-child { border-bottom: 0; }
.scorer-name { font-weight: 600; }
.scorer-goals { grid-row: span 2; align-self: center; font-weight: 700; color: var(--accent); }

/* ---------- scoreboard ---------- */
.scoreboard { display: grid; grid-template-columns: 1fr auto 1fr; gap: 20px; align-items: center; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 26px 20px; margin: 18px 0 20px; }
.sb-team { display: flex; align-items: center; gap: 10px; font-size: 18px; font-weight: 600; }
.sb-team.right { justify-content: flex-end; }
.sb-team a { color: var(--text); }
.sb-badge { font-size: 30px; }
/* Der Spielstand selbst: die größte Zahl der Anwendung. Condensed 800 trägt
   hier zusätzlich, dass „28 : 27" auch auf einem schmalen Telefon neben die
   beiden Mannschaftsnamen passt. */
.sb-score {
    font-family: var(--font-cond); font-size: 44px; font-weight: 800;
    line-height: 1; letter-spacing: .01em; text-align: center;
}
/* Die Zeile darunter („nach Bogen", Halbzeitstand) ist Text, nicht Ergebnis. */
.sb-meta { display: block; font-family: var(--font-sans); font-size: 12px; font-weight: 400; color: var(--text-muted); margin-top: 6px; }
@media (max-width: 640px) {
    .scoreboard { grid-template-columns: 1fr; text-align: center; }
    .sb-team, .sb-team.right { justify-content: center; }
}

/* ---------- badges ---------- */
.pos { display: inline-block; font-size: 11px; font-weight: 700; padding: 1px 6px; border-radius: var(--radius); background: var(--zebra); color: var(--text-muted); }
/* Handball positions — keys match SS_POSITIONS in index.php. */
/* Die Torhüterin bekommt `--win` (das Blau), weil es die einzige zweite
   Farbfamilie der Palette ist — vorher stand hier das Bernstein, das es nicht
   mehr gibt. Dass Flügel und Kreis sich damit die Akzentfarbe teilen, ist
   hingenommen: die Position steht als **Wort** da, die Farbe ist Beiwerk
   (N-09 — Farbe allein trägt in diesem Projekt nie eine Aussage). */
.pos-goalkeeper { color: var(--win); }
.pos-left_wing { color: var(--accent); }
.pos-right_wing { color: var(--accent); }
.pos-back { color: var(--text); }
.pos-pivot { color: var(--accent); }
.pos-defence_specialist { color: var(--accent); }
.status { font-size: 12px; padding: 3px 8px; border-radius: var(--radius); background: var(--zebra);
          color: var(--text-muted); text-transform: uppercase; letter-spacing: .06em; font-weight: 600; }
.status-finished { color: var(--accent); }
.status-postponed { color: var(--accent); }

/* ---------- forms ---------- */
.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; align-items: end; }
.form-inline { display: flex; flex-wrap: wrap; gap: 12px; align-items: end; }
label { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--text-muted); }
input, select { background: var(--bg); color: var(--text); border: 1px solid var(--line); border-radius: var(--radius); padding: 8px 10px; }
/* Sichtbarer Fokus für **jedes** bedienbare Element (N-09, Loop 25).
   Vorher stand hier nur `input, select`: wer mit der Tabulatortaste durch
   die Kopfzeile, die Blätterleiste oder die +/−-Tasten des Spielbogens
   ging, war auf den Standardring des Browsers angewiesen — auf dem
   dunklen Grund je nach Browser fast unsichtbar.
   `:focus-visible` und nicht `:focus`: ein angeklickter Knopf soll keinen
   Ring bekommen, ein angesprungener schon. Der Ring liegt außen
   (outline-offset), damit er auch auf gefüllten Flächen zu sehen ist. */
input:focus-visible, select:focus-visible, textarea:focus-visible,
a:focus-visible, button:focus-visible, summary:focus-visible,
label:focus-visible, [tabindex]:focus-visible {
    outline: 3px solid var(--accent);
    outline-offset: 2px;
    border-radius: var(--radius);
}
/* Browser ohne :focus-visible bekommen weiterhin den alten Ring — jetzt
   dasselbe Maß wie oben. Zwei verschiedene Ringe (3 px/2 px, Abstand 2 px/1 px)
   waren eine Unterscheidung ohne Aussage: wer sie sieht, soll denselben Ring
   sehen, egal wie alt sein Browser ist. */
input:focus, select:focus { outline: 3px solid var(--accent); outline-offset: 2px; }

/* **Auf der dunklen Leiste gilt ihr Akzent**, nicht der der Seite. Ohne diese
   Zeile wäre der Ring in Hallenboden hell `#B3380A` auf fast schwarzem Grund:
   2,95:1, und damit genau das, was dieser Ring verhindern soll. */
.topbar :focus-visible, .topbar input:focus, .topbar select:focus {
    outline-color: var(--topbar-accent);
}
/* ---------- Knöpfe: zwei Stufen ----------

   **`.btn` ist ab Loop 111 der sekundäre Knopf, nicht mehr der primäre.** Das
   ist die Umkehrung einer Voreinstellung, und sie hat einen messbaren Grund:
   vorher war `.btn` allein die gefüllte Akzentfläche, und `class="btn"` ohne
   Zusatz stand 25 Mal im Markup — auf der Mannschaftsseite siebenmal, beim
   Verein sechsmal, im Spielbogen fünfmal. Sieben gefüllte Akzentknöpfe auf
   einer Seite sind keine Hervorhebung, sondern eine Tapete: wenn alles
   hervorgehoben ist, ist nichts hervorgehoben.

   Der Auftrag nennt deshalb genau zwei Stufen und dazu eine Mengenangabe:
   „höchstens einen [primären] pro Ansicht". Diese Zahl ist prüfbar, und sie
   wird geprüft — in `tests/render-check.php`, am **gerenderten** HTML und nicht
   an den Dateien, weil die meisten Seiten ihren primären Knopf in einem
   `if`-Zweig haben und eine Zählung im Quelltext zwei Zweige addieren würde,
   die nie gemeinsam erscheinen.

   `.btn-primary` war vorher „derselbe Knopf, aber über die ganze Breite" und
   stand schon an den 20 Stellen, die wirklich die Hauptsache einer Seite sind
   (Anmelden, Speichern, Importieren bestätigen). Diese Stellen bleiben, wo sie
   sind — sie bekommen jetzt bloß das Aussehen dazu, das sie immer meinten.
   Die volle Breite ist davon getrennt: dafür gibt es `.btn-block`.

   Beide Stufen tragen einen 2 px breiten Rahmen, auch die gefüllte. Ohne ihn
   wäre der primäre Knopf 4 px niedriger als der sekundäre daneben, und eine
   Reihe aus „Speichern" und „Abbrechen" stünde nicht auf einer Linie. */
.btn, .btn-quiet {
    display: inline-flex; align-items: center; justify-content: center;
    min-height: 44px; padding: 9px 18px;
    background: transparent; color: var(--text);
    border: 2px solid var(--text); border-radius: var(--radius);
    font-weight: 600; cursor: pointer; text-decoration: none;
}
.btn:hover, .btn-quiet:hover { background: var(--zebra); text-decoration: none; }

/* `.btn-quiet` ist nur noch ein **zweiter Name** für denselben Knopf. Der
   Auftrag kennt zwei Stufen, nicht drei; die 46 Stellen im Markup, die „quiet"
   sagen, meinten immer „nicht die Hauptsache", und genau das ist jetzt die
   Voreinstellung. Die Klasse verschwindet, wenn sie nirgends mehr steht —
   dieselbe Brücke, die die alten Farbnamen bis Loop 157 hatten, aus demselben
   Grund: ein Suchen-und-Ersetzen über 46 Aufrufstellen ist hier niemandes
   Freund. Bei den Farbnamen ist der Weg inzwischen zu Ende gegangen — 97 Leser
   umbenannt, Brücke gelöscht —, und er lief in genau dieser Reihenfolge. */

.btn-primary {
    background: var(--accent-fill); color: var(--on-accent);
    border-color: var(--accent-fill);
}
.btn-primary:hover { background: var(--accent-fill); filter: brightness(1.08); }
/* Über die ganze Breite — in den Anmeldeformularen. Vorher steckte das in
   `.btn-primary` mit drin, wodurch „Hauptsache" und „volle Breite" dasselbe
   Wort hatten und man das eine nicht ohne das andere haben konnte. Das war
   nicht nur unschön: auf der Mannschaftsseite stehen zwei primäre Knöpfe
   nebeneinander („überschreiben" / „als neues Spiel"), und mit `width: 100%`
   an beiden brachen sie untereinander um, als wären es zwei Fragen statt
   zweier Antworten auf eine.

   Die gestapelten Formulare bekommen die Breite jetzt aus ihrem Zusammenhang —
   eine Regel statt einer Klasse an fünf Stellen. `.btn-block` bleibt für den
   Fall, dass ein Knopf die Breite ohne so einen Zusammenhang braucht. */
.btn-block, .form-stack .btn-primary { width: 100%; }
.filterbar { display: flex; gap: 10px; margin-bottom: 16px; flex-wrap: wrap; }
form.inline { display: inline-flex; gap: 8px; align-items: center; margin: 0; }
/* A checkbox is the one label that reads left to right, not top to bottom. */
label.check { flex-direction: row; align-items: center; gap: 7px; }
label.check input { width: 20px; height: 20px; }
.btn-danger { background: var(--loss); color: var(--on-loss); }
.btn-danger[disabled] { opacity: .45; cursor: not-allowed; filter: none; }
.danger-zone { margin-top: 16px; border-top: 1px solid var(--line); padding-top: 14px; align-items: center; }

/* ---------- access pages ----------
   Deliberately narrow and stacked: login and the game sheet are the two
   screens that get used on a phone (§1 CONVENTIONS, mobile first). */
.auth-card { max-width: 420px; margin: 40px auto 20px; }
.auth-card h1 { margin-top: 0; }
.form-stack { display: flex; flex-direction: column; gap: 14px; }
.form-stack input { padding: 11px 12px; font-size: 16px; } /* 16px stops iOS zooming on focus */
.form-stack label { gap: 5px; }
/* „Passwort anzeigen" (2026-10-22): Feld und Knopf in einer Zeile; das Feld
   nimmt den Rest, der Knopf bleibt so breit wie sein Text. */
.pw-wrap { display: flex; gap: 8px; align-items: stretch; }
.pw-wrap input { flex: 1 1 auto; min-width: 0; }
.pw-wrap .pw-toggle { flex: 0 0 auto; white-space: nowrap; }
.user-menu { display: flex; align-items: center; gap: 10px; margin-left: auto; }
.user-menu a { color: var(--text-muted); padding: 6px 10px; border-radius: var(--radius); }
.user-menu a:hover { background: var(--zebra); text-decoration: none; }
.user-menu a.active { background: var(--zebra); color: var(--text); }
@media (max-width: 640px) {
    .auth-card { margin-top: 18px; }
    .user-menu { width: 100%; justify-content: space-between; margin-left: 0; }
}
.stat-form { margin-top: 14px; border-top: 1px solid var(--line); padding-top: 12px; }
.stat-form summary { cursor: pointer; color: var(--accent); font-size: 14px; }
.stat-form form { margin-top: 12px; }

/* ---------- game sheet ----------
   The one screen that is really used one-handed, standing next to the court:
   every target is at least 44px, the counters stack on a phone and spread out
   on a laptop, and the save bar stays reachable at the bottom edge. */
.sheet-heading { margin: 22px 0 12px; }
.sheet-player { padding: 14px; margin-bottom: 14px; }
.sheet-player-head { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
.sheet-player-head h3 { font-size: 16px; margin: 0; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.sheet-player-head h3 a { color: var(--text); }
.sheet-player-head .btn-small { margin-left: auto; }
.btn-small { padding: 6px 12px; font-size: 13px; min-height: 34px; }

.counters { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; }
/* `.counter` war bis Loop 112 ein `<label>`, das Beschriftung **und** Feld
   umschloss. Jetzt ist es ein `<div>`, und das Label beschriftet über `for`:
   ein Klick auf ein Label wird an sein Feld weitergegeben — auf älterem mobilem
   Safari auch dann, wenn er einen Knopf darin getroffen hat, und dort öffnete
   jeder Tipper auf „+" zusätzlich die Zahlentastatur. */
.counter { display: flex; flex-direction: column; gap: 6px; }
.counter-label { font-size: 12px; color: var(--text-muted); line-height: 1.2; min-height: 2.4em; }
.counter-input { width: 100%; text-align: center; font-size: 18px; font-weight: 700;
                 font-variant-numeric: tabular-nums; padding: 10px 4px; min-height: 44px; }
/* **Keine Drehpfeile im Zähler.** Ein `type="number"` bringt am Rechner zwei
   kleine Pfeile mit, und die nehmen sich rund 16 px vom Feld — in der Bogentabelle
   blieb daneben kein Platz mehr für die Zahl, man sah nur „−" und „+". Die Pfeile
   sind hier ohnehin doppelt: die beiden Knöpfe daneben tun dasselbe, nur
   daumengroß. */
.counter-input { -moz-appearance: textfield; appearance: textfield; }
.counter-input::-webkit-outer-spin-button,
.counter-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.counter-input[readonly] { color: var(--text-muted); }
/* **Die abgeschwächte Null** — der erste Leser von `--zero` (seit Loop 108
   vorbereitet). Auf einem Bogen mit 198 Feldern sind die meisten null; eine
   Seite voll kräftiger Nullen verdeckt die drei Zahlen, auf die es ankommt.
   `--zero` ist 55 % Schrift auf der Fläche und schafft mindestens 3:1 — denn
   „nicht erfasst" und „null" sind auf einem Spielbogen zwei verschiedene
   Aussagen, und eine Null, die man für eine leere Zelle hält, wäre schlimmer
   als eine kräftige. Die Klasse setzt PHP beim Rendern und `app.js` nach jedem
   Tipper. */
.counter-input.is-zero { color: var(--zero); }
.counter-input.is-saved { border-color: var(--accent); }
.counter-input.is-error { border-color: var(--loss); }

/* Der Spieltag-Bogen (F-14): zwei Zahlen je Zeile, nebeneinander wie das
   Ergebnis, das sie ergeben. Dieselben 44 px Höhe wie die Zähler des
   Spielbogens — auch dieses Formular wird am Hallenrand mit dem Daumen
   bedient. Die feste Breite hält acht Zeilen untereinander in einer Flucht. */
.score-cell { white-space: nowrap; }
.score-input { width: 4.2em; min-height: 44px; padding: 8px 4px; text-align: center;
               font-variant-numeric: tabular-nums; font-weight: 600; }

/* Built by app.js — without JavaScript the bare number field stays. */
/* Die Spieleliste der Übersicht (Loop 126): sechs Angaben in der Reihenfolge des
   Auftrags — Plakette, Gegner, Datum, Heim/Auswärts, Ergebnis, Bogenstatus.

   Eigenes Raster und nicht das von `.match-list a`: jenes hat vier Spalten und
   trägt drei weitere Listen. Der Platz der Plakette ist fest (`24px`), auch wenn
   sie fehlt — sonst stünde die Spalte je Zeile woanders.

   Auf dem Telefon bricht die Zeile in zwei: Plakette, Gegner und Ergebnis oben,
   das Beiwerk darunter. Sechs Spalten auf 360 px wären sechs Spalten à 60 px. */
.game-list a {
    grid-template-columns: 24px 1fr auto auto auto auto;
    gap: 4px 10px;
}
.m-badge { display: inline-flex; justify-content: center; }
.m-where { color: var(--text-muted); font-size: 12px; white-space: nowrap; }

@media (max-width: 640px) {
    .game-list a { grid-template-columns: 24px 1fr auto; }
    /* Datum, Heim/Auswärts und Bogenstatus in die zweite Zeile, unter den Namen. */
    .game-list .m-date, .game-list .m-where, .game-list .sheet-state { grid-column: 2 / -1; }
}

/* ---------- Der Bogenstatus in einer Spieleliste (Loop 126) ----------

   „vollständig", „drei Tore fehlen", „nicht erfasst". Er steht als **Wort** da
   und nicht nur als Farbe (N-09) — die Farbe sagt nur, ob man hinsehen muss.

   Zwei von fünf Zuständen verlangen eine Handlung (`short`, `over`): dort steht
   der Akzent. Die anderen drei sind Feststellungen und bleiben leise. Ohne diese
   Trennung wäre eine Liste aus sechs Spielen sechsmal gleich dringlich, also
   nirgends.

   Die Fassung hängt an `data-state` und nicht an einer Klassenliste: der Zustand
   kommt aus `sheetStatus()` als ein Wort, und es gibt keinen Grund, daraus im
   Markup fünf Klassen zu machen, von denen immer vier falsch wären. */
.sheet-state {
    font-size: 12px; font-weight: 600; color: var(--text-muted);
    white-space: nowrap;
}
.sheet-state[data-state="short"], .sheet-state[data-state="over"] { color: var(--accent); }

/* ---------- Die Bilanz neben der Formkurve (Loop 123) ----------

   „3 – 1 – 1" unter fünf Plaketten. Tabellenziffern erbt sie von `body`; was sie
   hier braucht, ist der schmale Schnitt und etwas Gewicht — sie steht neben
   fünf gleich großen Quadraten und soll als **Zahl** gelesen werden, nicht als
   Beiwerk. Leise bleibt sie trotzdem: die Reihe darüber ist die Aussage, die
   Bilanz ist ihre Zusammenfassung. */
.form-record {
    font-family: var(--font-cond); font-weight: 700; font-size: 16px;
    letter-spacing: .02em; color: var(--text-muted); white-space: nowrap;
}

/* ---------- Karte „Letztes Spiel" (Loop 120) ----------

   Die erste Frage nach einem Spiel ist „wie ist es ausgegangen und ist der Bogen
   fertig?". Bis hierher stand das Ergebnis nur als eine Zeile unter fünf gleich
   aussehenden in der Liste der letzten Spiele.

   Das Ergebnis ist die große Zahl — dieselbe Größe und dieselbe Schrift wie im
   Kopf des Spielbogens, wo man sie eintippt. Es ist dieselbe Angabe, und sie
   soll zweimal gleich aussehen.

   Die Plakette davor trägt den **Buchstaben** und nicht nur die Farbe (N-09);
   sie ist derselbe Baustein wie in der Formkurve, damit man gefülltes Blau nicht
   zweimal lernen muss. */
.last-game-line {
    display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
    margin: 0 0 14px;
}
.last-game-score {
    font-family: var(--font-cond); font-weight: 800; font-size: 34px; line-height: 1;
    letter-spacing: .01em;
}
.last-game-who { font-weight: 600; }
/* Die beiden Knöpfe: „Spielbogen" ist der Weg weiter, „PDF" das Blatt für die
   Mappe. Keiner von beiden ist primär — der primäre Knopf einer Ansicht ist
   höchstens einer, und auf der Übersicht ist das nicht der Sprung in ein
   einzelnes Spiel. */
.last-game-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 0; }

/* ---------- Karte „Nächstes Spiel" (Loop 121) ----------

   Das Datum ist hier die große Zahl — anders als beim letzten Spiel, wo es das
   Ergebnis ist. Das ist der Unterschied zwischen den beiden Karten: beim
   gespielten Spiel will man wissen, **wie** es ausging, beim nächsten **wann**
   es ist.

   Die Uhrzeit fehlt, weil `games` keine Spalte dafür hat (siehe den Punkt im
   Plan). Deshalb steht hier auch kein Platz für sie frei — ein leerer Platz
   sähe aus wie ein Fehler statt wie eine fehlende Angabe. */
.next-game-line {
    display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
    margin: 0 0 14px;
}
.next-game-date {
    font-family: var(--font-cond); font-weight: 800; font-size: 28px; line-height: 1.1;
    letter-spacing: .01em;
}
.next-game-who { font-weight: 600; }
/* „Vorbereiten" ist der Weg weiter, „Spielplan importieren" der Weg zurück zu
   den Daten. Keiner von beiden ist primär: auf der Übersicht ist der primäre
   Knopf nicht der Sprung in ein einzelnes Spiel. */
.next-game-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 0; }

/* ---------- Augenbraue und Titelzeile (Loop 119) ----------

   Die Zeile über dem Titel sagt, **in welchem Zusammenhang** die Zahlen
   darunter gelten: Verein · Saison. Vorher hing das als `<small>` am Titel
   selbst — also in derselben Zeile wie „Übersicht", und damit las es sich wie
   ein Teil der Überschrift statt wie ihre Voraussetzung.

   Klein, in Versalien und leise: sie soll gelesen werden, bevor man den Titel
   liest, und danach nicht mehr auffallen. `margin-bottom: 0` und der negative
   Abstand am Titel darunter rücken beide zu einem Block zusammen — zwei
   getrennte Zeilen mit normalem Abstand wären zwei Aussagen. */
.eyebrow {
    margin: 26px 0 0; color: var(--text-muted);
    font-size: 12px; font-weight: 600;
    text-transform: uppercase; letter-spacing: .08em;
}
.eyebrow + h1 { margin-top: 2px; }

/* Die Zeile unter dem Umschalter: Teamauswahl und, falls nötig, der Hinweis auf
   das Verknüpfen eines Spielerprofils. */
.scope-row { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin: 0 0 20px; }

/* ---------- Der Segment-Umschalter ----------

   Ein Rahmen um die Gruppe, 2 px in `--text`, und die Teile darin durch 2 px
   getrennt. Die Trennstriche entstehen wie bei der Kennzahlenleiste aus
   `gap` über einem Grund in `--text`: die Lücken **sind** die Linien. Der
   naheliegende Weg (`border-left` außer am ersten Teil) bricht beim Umbruch,
   weil `:first-child` nur der erste Teil *der Gruppe* ist und nicht der erste
   jeder Zeile — bei drei Segmenten auf einem schmalen Telefon steht dann mitten
   in der zweiten Zeile ein Strich zu viel.

   Der gewählte Teil ist **gefüllt** (`--text` auf `--bg`) und nicht nur
   gefärbt: Farbe allein ist in diesem Projekt nie ein Merkmal (N-09), und das
   `aria-current` im Markup sagt dasselbe noch einmal für die Sprachausgabe.

   `--text` als Füllung und nicht der Akzent: der Akzent heißt hier „das ist die
   Hauptsache dieser Seite", und davon gibt es höchstens eine. Ein Umschalter
   sagt „du bist hier", nicht „drücke das". */
.segments {
    display: flex; flex-wrap: wrap; gap: 2px;
    background: var(--text); border: 2px solid var(--text);
    margin: 0 0 18px;
}
.segment {
    display: inline-flex; align-items: center; justify-content: center;
    flex: 1 1 auto; min-height: 44px; padding: 8px 16px;
    background: var(--surface); color: var(--text);
    font-family: var(--font-cond); font-weight: 700; text-transform: uppercase;
    font-size: 16px; letter-spacing: .02em; text-align: center;
}
.segment:hover { background: var(--zebra); text-decoration: none; }
.segment.is-current { background: var(--text); color: var(--bg); }
/* Die Füllung bleibt beim Überfahren stehen — sie ist eine Aussage über den
   Zustand, keine Rückmeldung auf die Maus. */
.segment.is-current:hover { background: var(--text); color: var(--bg); }

/* ---------- Der Prüfstreifen ----------

   Er erscheint, wenn zwei Zahlen nicht zusammenpassen, die **beide stimmen
   dürfen**: das Ergebnis von der Anzeigetafel und die Summe aus dem Bogen.
   Deshalb kein Rot und kein Ausrufezeichen — es ist nichts kaputt. Wer am
   Hallenrand 27 statt 28 zählt, hat sich nicht vertan, sondern eine Parade
   übersehen, und die Software weiß nicht, welche der beiden Zahlen die richtige
   ist. Sie fragt nach, sie beschwert sich nicht.

   `--warn-bg` bekommt damit seinen ersten Leser (vorbereitet in Loop 108). Die
   2-px-Oberkante in `--accent` ist die einzige Stelle des Projekts, an der eine
   Kante den Akzent trägt statt `--text`: der Streifen soll aus einer Seite voll
   grauer Hinweise herausfallen, und er ist pro Ansicht so selten wie der
   primäre Knopf.

   `[hidden]` muss hier ausdrücklich gewinnen: `display: flex` in der Regel
   darunter würde das Attribut sonst überstimmen — eine versteckte Meldung, die
   trotzdem steht, ist schlimmer als keine. */
.check-stripe {
    display: flex; flex-wrap: wrap; gap: .4em;
    margin: 14px 0 0; padding: 12px 14px;
    background: var(--warn-bg);
    border-top: 2px solid var(--accent);
    font-size: 14px; line-height: 1.5;
}
.check-stripe[hidden] { display: none; }
.check-stripe strong { color: var(--accent); font-weight: 700; }

/* ---------- Der Kopf des Spielbogens (Loop 117) ----------

   Datum, Heim oder Auswärts, und darunter die zwei großen Ergebnisfelder. Sie
   lagen vorher **unter** den Tabellen, hinter 198 Zählern — die falsche
   Reihenfolge für den Ablauf am Spielfeldrand, denn das Ergebnis steht auf der
   Anzeigetafel, bevor der Bogen fertig ist.

   Die Zahlen sind so groß wie der Spielstand auf der Übersichtsseite: es ist
   dieselbe Angabe, und sie soll sich nicht kleiner anfühlen, nur weil man sie
   hier eintippt statt abliest.

   `width: 4ch` und nicht `100%`: zwei Felder, die die ganze Zeile füllen, sehen
   aus wie ein Formular für zwei Texte. Vier Ziffern breit sieht aus wie ein
   Spielstand — und mehr als drei Stellen hat ein Handballergebnis nicht. */
.sheet-head { padding: 20px; }
.sheet-meta {
    display: flex; flex-wrap: wrap; gap: 6px 14px; margin: 0 0 14px;
    color: var(--text-muted); font-size: 13px;
    text-transform: uppercase; letter-spacing: .06em; font-weight: 600;
}
/* Das Datum ist die Angabe, über die man dieses Spiel wiederfindet, also steht
   es in `--text` und nicht im Leisen der übrigen Zeile. */
.sheet-when { color: var(--text); }
/* Heim oder Auswärts ist eine von zwei Möglichkeiten — eine Form sagt das
   schneller als ein Wort in derselben Zeile wie alles andere. Umrandet und
   nicht gefüllt: es ist eine Angabe, keine Auswahl und keine Warnung. */
.sheet-where {
    padding: 1px 7px; border: 1px solid var(--line); border-radius: var(--radius);
    color: var(--text);
}

.sheet-score { display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap; }
.score-field { display: flex; flex-direction: column; gap: 6px; }
.score-label {
    color: var(--text-muted); font-size: 12px;
    text-transform: uppercase; letter-spacing: .04em;
}
.score-field input {
    width: 4ch; min-height: 72px; padding: 6px 10px; text-align: center;
    font-family: var(--font-cond); font-size: 52px; font-weight: 800; line-height: 1;
    border: 2px solid var(--text); border-radius: var(--radius);
    background: var(--surface); color: var(--text);
}
/* Der Platzhalter ist die Bogensumme, also eine **Vermutung** und kein Wert.
   `--zero` sagt genau das: lesbar, aber nicht als Eintrag zu verwechseln. */
.score-field input::placeholder { color: var(--zero); opacity: 1; }
/* Ein Doppelpunkt zwischen den Feldern, auf der Grundlinie der Zahlen. Er ist
   `aria-hidden`: vorgelesen wäre „Doppelpunkt" zwischen zwei beschrifteten
   Feldern nur Lärm. */
.score-colon {
    font-family: var(--font-cond); font-size: 52px; font-weight: 800; line-height: 1;
    color: var(--text-muted); padding-bottom: 8px;
}

@media (max-width: 640px) {
    /* Auf dem Telefon einen Schritt kleiner — zwei 52-px-Zahlen plus
       Doppelpunkt sind dort breiter als der Schirm. */
    .score-field input, .score-colon { font-size: 40px; }
    .score-field input { min-height: 60px; }
}

/* ---------- Die Sprungleiste zu den Kategorien (Loop 116) ----------

   Das Inhaltsverzeichnis der Tabelle. Mit elf Spalten, von denen vier ins Bild
   passen, sieht man ohne sie nicht, **was es noch gibt**.

   Die Nummer ist der Faden zwischen Leiste und Tabelle: im Kopf stehen die
   Namen gekürzt („7 Meter ra…"), hier vollständig. Wer „7 Fehlpässe" liest,
   sucht in der Tabelle die siebte Spalte.

   Die Leiste selbst darf umbrechen und rollt **nicht** — eine Leiste, die mit
   ihrem Ziel wegrollt, ist kein Inhaltsverzeichnis. Darum `flex-wrap` und kein
   `overflow`.

   Kleiner als 44 px mit Absicht: das sind bis zu elf Ziele nebeneinander, und
   bei 44 px Höhe wäre die Leiste auf einem Telefon drei Zeilen hoch und
   verdrängte genau die Tabelle, zu der sie führt. Es ist ein Verweis, keine
   Eingabe — die 44-px-Regel gilt für das, was den Bogen *ändert*. */
/* ---------- Kategorie-Chips über einem Diagramm (Loop 134) ----------

   Zwanzig Kategorien standen bis hierher in einem `<select>` mit Optgruppen. Ein
   Auswahlfeld **verbirgt**, was es zu wählen gibt, bis man es öffnet; mit zwanzig
   Einträgen sind das zwei Griffe für jeden Blick auf eine andere Zahl. Chips
   zeigen alles und kosten einen.

   **Eigene Klassen, nicht `.jump-link`.** Die Leiste darunter sieht ähnlich aus,
   ist aber eine **Sprungleiste**: dort ist nichts gewählt, weil ein Anker keinen
   Zustand wählt (Loop 114). Hier ist genau einer gefüllt. Dieselben Klassen für
   beides hätten die eine Rolle in die andere geschoben — der Fehler, vor dem die
   Doppelrolle von `segmentNav()` seit Loop 114 ausdrücklich warnt.

   Gefüllt heißt `--text` auf `--bg` — dieselbe Füllung wie beim aktiven Segment,
   damit man „gewählt" nicht zweimal lernen muss. 32 px Höhe wie die Sprungleiste:
   die Reihe ist zwanzig Elemente lang, und 44 px daraus wären auf dem Telefon
   sechs Zeilen Bedienung über einem Diagramm. Getroffen wird waagerecht, und
   dafür sorgt das Innenmaß. */
.chip-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; margin: 0 0 14px; }
.chip {
    display: inline-flex; align-items: center;
    min-height: 32px; padding: 5px 10px;
    border: 1px solid var(--line); border-radius: var(--radius);
    color: var(--text-muted); font-size: 13px; line-height: 1.3; white-space: nowrap;
}
.chip:hover {
    background: var(--zebra); color: var(--text);
    border-color: var(--text); text-decoration: none;
}
.chip.is-current {
    background: var(--text); color: var(--bg);
    border-color: var(--text); font-weight: 600;
}
/* Die Füllung bleibt beim Überfahren stehen — sie ist eine Aussage über den
   Zustand, keine Rückmeldung auf die Maus (wie bei `.segment.is-current`). */
.chip.is-current:hover { background: var(--text); color: var(--bg); }
/* Die Zwischenüberschrift einer Chip-Gruppe („Spielerinnen", „Mannschaft") —
   dieselbe Rolle wie ein `<optgroup>`: sie ordnet, sie ist kein Chip. */
.chip-group {
    font-size: 11px; text-transform: uppercase; letter-spacing: .04em;
    color: var(--text-muted); margin-left: 6px;
}
.chip-group:first-child { margin-left: 0; }

.jump-bar { display: flex; flex-wrap: wrap; gap: 4px; margin: 0 0 10px; }
.jump-link {
    display: inline-flex; align-items: baseline; gap: 5px;
    min-height: 32px; padding: 5px 9px;
    border: 1px solid var(--line); border-radius: var(--radius);
    color: var(--text-muted); font-size: 13px; line-height: 1.3;
}
.jump-link:hover {
    background: var(--zebra); color: var(--text);
    border-color: var(--text); text-decoration: none;
}
.jump-no {
    font-family: var(--font-cond); font-weight: 800; font-size: 14px;
    color: var(--accent);
}
/* Der Name bricht **nicht** in sich um. „7m-Tore" über zwei Zeilen macht aus
   einem Verweis zwei Zeilen Leiste, und die Leiste soll die Tabelle nicht
   verdrängen, zu der sie führt. Umbrechen darf die Leiste, nicht der Name. */
.jump-name { white-space: nowrap; }

/* **Sanft rollen, aber nicht gegen den Wunsch des Geräts.** Die Eigenschaft
   steht unten in der `.table-scroll`-Regel selbst — zwei Regeln für denselben
   Selektor wären zwei Stellen, an denen jemand nachsieht, und eine, an der er
   es nicht tut. Abbestellt wird sie hier:
   wer in seinem System Bewegung abgewählt hat, bekommt den Sprung ohne
   Animation. Das ist keine Feinheit — für manche Menschen löst gleitende
   Bewegung Schwindel aus, und der Spielbogen ist die Seite, die man am
   längsten in der Hand hat.

   In CSS und nicht in JavaScript, damit es auch für den gewöhnlichen
   Ankersprung ohne Skript gilt. */
@media (prefers-reduced-motion: reduce) {
    .table-scroll { scroll-behavior: auto; }
}

/* ---------- Der Bogen als Tabelle (Loop 115) ----------

   Elf Kategorien sind breiter als jedes Telefon, also rollt die Tabelle
   waagerecht — und die Spielerinnenspalte **klebt** am linken Rand, weil eine
   Zahl ohne Namen daneben keine Angabe ist.

   `position: sticky` braucht dreierlei, und keines davon ist offensichtlich:

     1. **Einen eigenen Grund.** Eine klebende Zelle ist durchsichtig, und beim
        Rollen schieben sich die Zahlen *darunter* durch. Deshalb `--surface`,
        und in der Summenzeile `--zebra`, damit sie dort nicht plötzlich heller
        wird als der Rest der Zeile.
     2. **Eine rechte Kante.** Ohne sie verschmilzt die klebende Spalte beim
        Rollen mit der ersten Zahlenspalte, und man sieht nicht, wo der Name
        aufhört. 2 px in `--text`, weil sie zwei Ebenen trennt — dieselbe Regel
        wie beim Tabellenkopf.
     3. **Eine Stapelordnung.** Der Kopf klebt oben, die erste Spalte links; die
        Zelle, die *beides* ist (oben links), muss über beiden liegen, sonst
        rollt eine Zahl darüber hinweg.

   Die Kategorie der Zelle steht **nicht** über jedem Zähler: sie steht im
   Spaltenkopf. Vierzehnmal „Tore" untereinander in einer Spalte mit der
   Überschrift „Tore" macht die Zeile dreimal so hoch und sagt nichts dazu. Das
   `<label>` bleibt im Markup (es verbindet Beschriftung und Feld und wird von
   der Sprachausgabe gebraucht), nur sichtbar ist es nicht. */
.sheet-table { min-width: 100%; }
.sheet-table th, .sheet-table td { vertical-align: middle; }

/* Die klebende Spielerinnenspalte. */
.sheet-table .sheet-who {
    position: sticky; left: 0; z-index: 2;
    background: var(--surface);
    border-right: 2px solid var(--text);
    text-align: left; white-space: nowrap;
    font-size: 14px; text-transform: none; letter-spacing: 0; color: var(--text);
}
/* Der Kopf klebt oben **und** links — also über beidem. */
.sheet-table thead .sheet-who { z-index: 3; }
/* In der Summenzeile denselben Grund wie die Zeile, sonst leuchtet die klebende
   Zelle heraus. */
.sheet-table .sum-row .sheet-who { background: var(--zebra); }
.sheet-table .sheet-shirt { font-variant-numeric: tabular-nums; color: var(--text-muted); }
.sheet-table .sheet-who a { color: var(--text); font-weight: 600; }
/* Der Entfernen-Knopf sitzt in der Namenszelle und darf sie nicht breit machen.
   Kleiner als 44 px mit Absicht: er ist die seltenste Handlung des Bogens und
   steht direkt neben dem, was er entfernt — ein großes Ziel wäre hier ein
   großes Ziel für einen Fehlgriff. */
.sheet-table .sheet-remove-btn { min-height: 32px; padding: 2px 8px; margin-left: 4px; }

/* Eine Zelle ist ein Zähler. 44 + Zahl + 44 plus Innenabstand — darunter wird
   der Knopf kleiner als ein Daumen. **Die Zahl bekommt 48 px, und die sind
   zugesichert** (`.stepper`): mit 132 px blieben ihr 24, abzüglich Rahmen und
   Innenabstand 14 — eine zweistellige Zahl passte nicht hinein, und der Wert,
   den man gerade hochzählt, war nicht zu sehen. */
.sheet-table td.num { min-width: 156px; padding: 6px; }
.sheet-table .counter { gap: 0; }
.sheet-table .counter-input { padding-left: 2px; padding-right: 2px; }
/* Die Beschriftung bleibt im Markup, aber außerhalb des Bildes — dieselbe
   Technik wie `.sr-only`, und nicht `display: none`: was verborgen ist, wird
   auch nicht vorgelesen und verbindet kein Label mit keinem Feld. */
.sheet-table .counter-label {
    position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden;
    min-height: 0;
}
/* Der Spaltenkopf trägt die Kategorie und ist das Sprungziel der
   Kategorie-Leiste. Rechtsbündig wie die Zahlen darunter. */
.sheet-table thead th.num { text-align: right; min-width: 156px; }
.sheet-table .sum-row td { font-family: var(--font-cond); font-size: 19px; font-weight: 800; }

@media (max-width: 640px) {
    /* Auf dem Telefon darf die Namensspalte schmaler sein — der Daumen rollt
       sowieso, und 132 px für die Zähler sind wichtiger als ein ganzer Name. */
    .sheet-table .sheet-who { max-width: 10rem; overflow: hidden; text-overflow: ellipsis; }
}

/* ---------- Der Zähler: [−] Wert [+] ----------

   Die Knöpfe sind **echte Absendeknöpfe** und stehen seit Loop 112 im HTML,
   nicht mehr in `app.js`. Das ist der Unterschied zwischen „ohne JavaScript
   benutzbar" und „ohne JavaScript vorhanden": vorher blieb ohne Skript ein
   nacktes Zahlenfeld, und eine Zahl eintippen heißt am Hallenrand, die
   Zahlentastatur über den halben Bogen zu legen und danach die Stelle
   wiederzufinden.

   „+" ist **gefüllt**, „−" nur **umrandet** (Auftrag). Das ist keine Zierde:
   auf dem Bogen wird fast nur hochgezählt — ein Tor, eine Parade, ein
   Fehlpass —, und das Herunterzählen ist die Ausnahme für den Fall, dass man
   sich verzählt hat. Die häufige Handlung ist die auffällige.

   „+" ist in `--text` gefüllt und **nicht** im Akzent: der Akzent heißt in
   diesem Projekt „das hier ist die Hauptsache dieser Seite", und davon gibt es
   höchstens eine. Auf dem Bogen stünden sonst 198 Hauptsachen.

   Beide 44 × 44 px. Die Zahl dazwischen darf schmaler werden, die Knöpfe nicht
   — getroffen werden muss der Knopf. */
.stepper { display: grid; grid-template-columns: 44px minmax(48px, 1fr) 44px; gap: 4px; align-items: stretch; }
.step {
    min-width: 44px; min-height: 44px; padding: 0;
    font-family: var(--font-cond); font-size: 24px; font-weight: 800; line-height: 1;
    border-radius: var(--radius); cursor: pointer;
}
.step-down { background: transparent; color: var(--text); border: 2px solid var(--text); }
.step-down:hover { background: var(--zebra); }
.step-up { background: var(--text); color: var(--bg); border: 2px solid var(--text); }
.step-up:hover { filter: brightness(1.15); }
.step:active { filter: brightness(1.3); }

/* ---------- Die feste Fußleiste des Bogens (Loop 118) ----------

   Sie klebt unten, weil ein Bogen mit vierzehn Spielerinnen länger ist als jeder
   Schirm und „Speichern" sonst eine Reise ist.

   **2 px in `--text` als Oberkante**, nicht 1 px in `--line`: die Leiste ist eine
   eigene Ebene über dem Inhalt, und das Liniensystem trennt Ebenen mit 2 px.
   Vorher war sie 1 px, also so stark wie eine Tabellenzeile — beim Rollen fuhr
   der Bogen fast nahtlos unter ihr durch.

   Der Grund ist `--bg` und **deckend**: eine durchsichtige Leiste über rollenden
   Zahlen ist unlesbar, und die Zahl darunter ist es auch. */
.sheet-actions { position: sticky; bottom: 0; z-index: 4; display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
                 background: var(--bg); border-top: 2px solid var(--text); padding: 12px 0; margin-bottom: 18px; }
.sheet-actions .btn { min-height: 44px; }
/* Das PDF ist der leisere der beiden Knöpfe und darf schmaler sein — aber nicht
   niedriger: es wird mit demselben Daumen getroffen wie „Speichern". */
.sheet-actions .btn-small { min-height: 44px; padding: 6px 14px; }
[data-sheet-status].is-error { color: var(--accent); }
.hidden-form { display: none; }

/* Die beiden Druckknöpfe. Der Hinweis dahinter rückt auf dem Handy in eine
   eigene Zeile, statt die Knöpfe schmal zu quetschen. */
.sheet-print { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 14px 0 0; }
.sheet-print .btn { min-height: 38px; }
.sheet-print .small:last-child { flex: 1 1 220px; }
/* Ansichts- und Bearbeitungsmodus des Bogens (2026-10-22). Der Bearbeitungsmodus
   trägt den Akzent als Rahmen: man soll sehen, dass hier gerade geschrieben wird. */
.sheet-mode { display: flex; align-items: center; justify-content: space-between; gap: 10px;
              flex-wrap: wrap; margin: 14px 0 0; padding: 10px 12px; border: 1px solid var(--line); }
.sheet-mode > span { display: flex; flex-direction: column; gap: 2px; }
.sheet-mode-view { border-color: var(--line); }
.sheet-mode-edit { border: 2px solid var(--accent); }
.sheet-mode .btn { min-height: 38px; }

/* Formkurve (F-12). Links das ältere Spiel, rechts das letzte.
   Jede Marke trägt ihren Buchstaben — **Farbe allein ist keine Information**,
   die jeder lesen kann (N-09), und ein rotgrüner Punktestreifen ist für rund
   acht Prozent der Männer eine graue Reihe. Die Farben sind dieselben wie bei
   den Ergebnissen in den Tabellen: eigene Hinterlegung, Textfarbe aus den
   `-ink`-Variablen (A-20). */
.form-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 0 0 18px; }
.form-strip { display: inline-flex; gap: 4px; }
.form-dot {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 24px; height: 24px; padding: 0 4px;
    border-radius: var(--radius); font-size: 12px; font-weight: 700;
    font-family: var(--font-cond); letter-spacing: .02em;
}
/* Dieselben drei Zustände wie bei den Ergebnissen, und mit Absicht dieselben
   Farben: wer in der Spieleliste gelernt hat, dass gefülltes Blau ein Sieg ist,
   soll es in der Formkurve nicht neu lernen müssen.
   **Quadrate, nicht Kreise**: gleiche `min-width` und `height`, und
   `--radius` steht ohnehin auf 0. */
.form-win  { background: var(--win);  color: var(--on-win); }
.form-draw { background: transparent; color: var(--text); box-shadow: inset 0 0 0 2px var(--text); }
.form-loss { background: var(--loss); color: var(--on-loss); }
/* In der Tabelle „Deine Mannschaften" steht die Kurve in einer Zelle und darf
   die Zeilenhöhe nicht sprengen. */
.table .form-dot { min-width: 20px; height: 20px; font-size: 11px; }

/* Seitennavigation (N-11). Die Ziffern sind 40 px hoch und breit — ein
   Blätterlink, den man auf dem Telefon zweimal treffen muss, ist keiner. Die
   Mengenangabe steht am Ende und wandert auf schmalen Geräten in die zweite
   Zeile, weil sie die einzige Angabe hier ist, die man lesen und nicht treffen
   muss. */
.pager { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin: 14px 0 0; }
.pager a, .pager .pager-number, .pager .pager-step {
    min-width: 40px; min-height: 40px; padding: 0 10px;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: var(--radius); text-decoration: none;
}
.pager a:hover { background: var(--zebra); }
/* Dieselbe Paarung wie die aktive Seite in der Kopfzeile: `--accent-fill` mit
   `--on-accent` darauf. Vorher stand hier ein festes `#08130c`, weil der
   damalige Akzent ein helles Grün war, auf dem Weiß nicht zu lesen ist. Genau
   das ist der Grund, warum das Paar jetzt zwei Namen trägt: Hallenboden hell
   hat ein dunkles Rotbraun als Fläche und braucht **weiße** Schrift darauf —
   mit dem alten festen Wert wäre es dunkel auf dunkel gewesen, und niemand
   hätte den aktiven Seitenknopf mehr gesehen. */
.pager .active { background: var(--accent-fill); color: var(--on-accent); font-weight: 600; }
.pager .disabled { color: var(--text-muted); }
.pager .pager-gap { padding: 0 2px; color: var(--text-muted); }
.pager .small:last-child { flex: 1 1 160px; text-align: right; }

/* Die CSV-Knöpfe (F-17) tragen dieselbe Zeile wie die Druckknöpfe — eine
   zweite Anordnung für dieselbe Sorte Aktion wäre eine Behauptung, es sei eine
   andere. Auf dem Telefon rutscht der Hinweis unter die Knöpfe, statt sie zu
   quetschen. */
.export-bar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 0 0 14px; }
.export-bar .btn { min-height: 38px; }
.export-bar .small:last-child { flex: 1 1 220px; }

.pick-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 4px; margin-bottom: 14px; }
.pick { min-height: 44px; padding: 4px 6px; border-radius: var(--radius); color: var(--text); }
.pick:hover { background: var(--zebra); }
label.wide { grid-column: 1 / -1; }

@media (max-width: 640px) {
    .counters { grid-template-columns: 1fr 1fr; }
    .counter-label { min-height: 0; }
}

/* ---------- Diagramme und Filter (Phase F) ---------- */

/* One column on a phone, two from tablet width up. The charts are the one
   place where side by side genuinely helps — Feldspieler next to Torhüter. */
.chart-grid { display: grid; grid-template-columns: 1fr; gap: 18px; }
@media (min-width: 720px) {
    .chart-grid { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
}

.chart-figure { margin: 0; }
.chart-figure figcaption { font-size: 13px; color: var(--text-muted); text-transform: uppercase;
                           letter-spacing: .04em; margin-bottom: 8px; }

/* The SVG carries its own coordinate system and is stretched to the column;
   height comes from the viewBox, so a chart with six bars is taller than one
   with four without a single number in the CSS. */
.chart { display: block; width: 100%; height: auto; overflow: visible; }
.chart-label { font-size: 11px; fill: var(--text); }
.chart-value { font-size: 11px; font-weight: 700; fill: var(--text);
               text-anchor: end; font-variant-numeric: tabular-nums; }
.chart-track { fill: var(--zebra); }
.chart-bar.chart-good { fill: var(--accent); }
/* **Der Höchstwert in `--accent`** (Loop 134, Auftrag) — an der *Zahl* und nicht
   am Balken. Der Grund steht ausgerechnet in `svgBarChart()`: für die Balken gibt
   es keinen Anteil, der gleichzeitig 3:1 gegen die Fläche und 3:1 gegen den
   Vollakzent trägt (65 % → 3,10:1 / 1,94:1; 40 % → 1,94:1 / 3,10:1), ein neutrales
   Grau liegt bei 1,53:1 gegen den Akzent, und eine Kante in `--text` auf dem
   Akzentbalken bei 2,95:1 — dieselbe Zahl, die in Loop 109 den Unterstrich in der
   Kopfzeile unmöglich gemacht hat.

   Als **Text** auf der Fläche sind es 6,02:1 (hell) und 7,27:1 (dunkel), also mehr
   als die 4,5:1, die Text braucht. Und die Farbe ist nicht das einzige Merkmal
   (N-09): der Höchstwert ist auch der längste Balken, und die Zahl steht
   ausgeschrieben daneben. */
.chart-value-max { fill: var(--accent); }
/* **Der gestapelte Teil eines Balkens** (Loop 125): bei „Wer hat getroffen?"
   die Tore vom 7-m-Punkt. Umrandet und nicht gefüllt — und das ist nachgerechnet,
   nicht Geschmack: der Auftrag wünscht „zwei Farben", aber `--accent` und
   `--win` sind in Linienblau, Petrol und Kreide **derselbe Wert**, und zwei
   Helligkeiten eines Akzents erreichen gegeneinander nur 2,1:1. Zwei Farben, die
   man nicht unterscheiden kann, sind eine Farbe mit zwei Namen.

   Also dieselbe Unterscheidung wie bei den Ergebnisplaketten: gefüllt gegen
   umrandet. `vector-effect` hält die Linie 1 px breit, auch wenn das SVG
   skaliert — ohne das wird sie auf einem breiten Schirm fett und auf einem
   Telefon unsichtbar. */
.chart-bar-stack { fill: none; stroke-width: 1.5; vector-effect: non-scaling-stroke; }
.chart-bar-stack.chart-good { stroke: var(--accent); }
.chart-bar-stack.chart-bad  { stroke: var(--loss); }
.chart-bar.chart-bad  { fill: var(--loss); }

/* Verlaufsdiagramm (F-11). Die Linie trägt die laufende Summe, die Fläche
   darunter macht die Richtung auch dann sichtbar, wenn die Linie flach liegt.
   `vector-effect` hält die Linie 2 px breit, egal wie weit die Grafik
   gestreckt wird — ohne das wird sie auf einem breiten Bildschirm dünn und auf
   dem Telefon fett. */
.chart-line {
    fill: none; stroke: var(--accent); stroke-width: 2;
    /* **Harte Ecken, auch hier** (Loop 137). `stroke-linejoin: round` rundete jeden
       Knick der Linie ab und `linecap: round` ihre Enden — A-39 verlangt eckig, und
       seit Loop 125 ausdrücklich auch im SVG, wo die Prüfung auf `border-radius`
       nichts sieht. `miter` ist dabei nicht nur regelkonform, sondern richtiger:
       eine laufende Summe knickt an einem Spiel, und der Knick *ist* die Angabe. */
    stroke-linejoin: miter; stroke-linecap: butt;
    vector-effect: non-scaling-stroke;
}
/* Die Fläche unter der Linie ist **Tönung und keine Angabe**: die Zahl steht in der
   Linie, in den Punkten und im `aria-label`. Deshalb gilt hier *nicht* die Rechnung
   aus Loop 134 (3:1 für Grafik, die man unterscheiden muss) — 18 % Akzent auf dem
   Grund trägt 1,4:1 und darf das, weil nichts davon abhängt. Sie macht die Richtung
   auch dann sichtbar, wenn die Linie flach liegt. */
.chart-area { fill: color-mix(in srgb, var(--accent) 18%, transparent); stroke: none; }
/* **Gefüllt gegen umrandet** (Loop 137): der letzte Punkt ist die Zahl, die heute
   gilt, die übrigen sind der Weg dorthin. Beide im Akzent — eine gedämpfte Variante
   wäre entweder gegen den Grund oder gegen den Vollakzent zu schwach (die Rechnung
   steht in Loop 134). Unterschieden wird also durch die Form, wie bei den
   Ergebnisplaketten und den gestapelten Balken.

   Der umrandete Punkt braucht `--surface` als Füllung und nicht `none`: er sitzt
   **auf** der Linie, und durch ein `none` liefe sie mitten durch ihn hindurch. */
.chart-point { fill: var(--surface); stroke: var(--accent); stroke-width: 2; }
.chart-point-last { fill: var(--accent); stroke: var(--surface); stroke-width: 1.5; }
.chart-grid-line { stroke: var(--line); stroke-width: 1; vector-effect: non-scaling-stroke; }
.chart-axis { font-size: 10px; fill: var(--text-muted); font-variant-numeric: tabular-nums; }
.chart-axis-end { text-anchor: end; }
/* Ein Verlauf über zwanzig Spiele braucht die ganze Breite, kein halbes Raster. */
@media (min-width: 720px) {
    .chart-figure-wide { grid-column: 1 / -1; }
}

.filter-bar { display: flex; flex-wrap: wrap; gap: 10px; align-items: end; margin: 0 0 18px;
              background: var(--surface); border: 1px solid var(--line);
              border-radius: var(--radius); padding: 12px 14px; }
.filter-bar label { display: flex; flex-direction: column; gap: 4px; font-size: 13px;
                    color: var(--text-muted); flex: 1 1 220px; }
.filter-bar select { min-height: 44px; }
/* Without JavaScript the button is the only way to apply the filter, so it
   must never be hidden by CSS — app.js removes it once it takes over. */
/* Der Absendeknopf verschwindet erst, wenn app.js läuft und ihn ersetzt
   (Klasse `is-live` kommt von dort). `js-hide` markiert genau die Knöpfe,
   die dann nichts mehr zu tun haben — ohne JavaScript bleiben sie stehen. */
.filter-bar.is-live button, form.is-live .js-hide { display: none; }

.rate-row { display: flex; flex-wrap: wrap; gap: 18px; margin: 12px 0 0; }
.rate { display: flex; align-items: baseline; gap: 6px; }
.rate-value { font-size: 20px; font-weight: 700; color: var(--accent);
              font-variant-numeric: tabular-nums; }
.rate-label { font-size: 13px; color: var(--text-muted); }

/* ---------- Startseite ----------
   The one page a visitor without an account ever sees. It reuses .card, .stat,
   .table, .chart and .counters unchanged — the point of the page is to show
   what the application looks like, so anything invented only for the shop
   window would be a lie. What is added here is the frame around them. */

.hero { padding: 34px 0 12px; max-width: 46rem; }
.hero-kicker { margin: 0 0 8px; font-size: 12px; text-transform: uppercase;
               letter-spacing: .12em; color: var(--accent); }
.hero h1 { margin: 0 0 14px; font-size: clamp(26px, 5vw, 38px); line-height: 1.2; }
.hero-lead { margin: 0 0 22px; font-size: 17px; color: var(--text-muted); }
.hero-actions { display: flex; flex-wrap: wrap; gap: 12px; margin: 0 0 12px; }
/* .btn is a <button> everywhere else; as a link it needs the box back. */
.hero-actions .btn { display: inline-block; min-height: 44px; padding: 12px 22px;
                     text-decoration: none; line-height: 1.5; }
.hero-actions .btn:hover { text-decoration: none; }

/* Blickwinkel der Übersicht: „eine meiner Mannschaften / meine eigenen Zahlen /
   Beispieldaten". Steht über den Kacheln, nicht daneben — wer die Zahlen liest,
   muss vorher wissen, wessen Zahlen es sind. */
/* Die Teamauswahl. Bis Loop 119 war dieser Kasten die **ganze**
   Blickwinkelwahl — mit Rahmen, Fläche und Innenabstand, weil er allein dastand
   und zwei Fragen in einem Feld stellte. Jetzt steht er neben dem
   Segment-Umschalter und stellt nur noch eine: welche Mannschaft. Also kein
   Kasten mehr, sondern ein Feld in einer Zeile — ein Rahmen um ein einzelnes
   Auswahlfeld behauptet eine Gruppe, die es nicht mehr gibt. */
.scope-switch { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 0; }
.scope-switch label { color: var(--text-muted); font-size: 13px; }
.scope-switch select { max-width: 100%; min-height: 44px; }
.scope-heading { margin: 4px 0 16px; font-size: 20px; }

.demo-note { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
             color: var(--text-muted); margin: 0 0 16px; }

.landing-heading { margin: 34px 0 14px; display: flex; align-items: center;
                   gap: 10px; flex-wrap: wrap; }
.demo-badge { font-size: 11px; font-weight: 700; text-transform: uppercase;
              letter-spacing: .08em; padding: 3px 9px; border-radius: var(--radius);
              background: var(--warn-bg);
              color: var(--accent); }

.feature-grid { list-style: none; margin: 0 0 12px; padding: 0; display: grid;
                grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 14px; }
.feature { background: var(--surface); border: 1px solid var(--line);
           border-radius: var(--radius); padding: 16px; }
/* `.feature-icon` ist in Loop 146 weggefallen — mit den sechs Emojis, die es
   gestaltet hat. Eine Regel ohne Leser bleibt nicht stehen: sie ist ein Versprechen,
   das niemand hält (die Lehre aus Loop 128, dort umgekehrt gefunden — eine Klasse im
   Markup ohne Regel im Stylesheet).
   Die Überschrift beginnt jetzt oben in der Karte, nicht unter einem Zeichen. */
.feature h3 { margin: 0 0 6px; font-size: 16px; }
.feature p { margin: 0; font-size: 14px; }

/* The games on the landing page lead nowhere — same grid as .match-list a,
   without the promise of a link. */
.landing-game { display: grid; grid-template-columns: 62px 1fr auto 1fr; gap: 10px;
                align-items: center; padding: 9px 4px; }

/* A counter without its buttons. Same size as .counter-input so the preview
   keeps the proportions of the real sheet. */
.counter-static { display: block; text-align: center; font-size: 18px; font-weight: 700;
                  font-variant-numeric: tabular-nums; padding: 9px 6px;
                  border: 1px solid var(--line); border-radius: var(--radius);
                  background: var(--zebra); color: var(--text); }

.landing-cta { text-align: center; }
.landing-cta .hero-actions { justify-content: center; }

@media (max-width: 640px) {
    .hero { padding-top: 22px; }
    .hero-actions .btn { flex: 1 1 100%; text-align: center; }
    .landing-game { grid-template-columns: 54px 1fr auto; }
    .landing-game .m-team:last-child { grid-column: 2 / -1; }
}

/* ---------- Spielplan-Import (loop 18) ---------- */
/* Übersprungene Zeilen bleiben lesbar, treten aber zurück: sie stehen in der
   Vorschau, damit niemand ein fehlendes Spiel suchen muss — nicht, damit man
   sie liest. */
.import-skip { opacity: .55; }
.import-skip td { font-style: italic; }
.import-steps { margin: 0 0 16px; padding-left: 20px; color: var(--text-muted); font-size: 14px; }
.import-steps li { margin-bottom: 4px; }
/* Ein .ics ist breit; ohne monospace und Umbruch wird das Einfügefeld zur
   unlesbaren Schlange. */
.form-grid textarea { width: 100%; box-sizing: border-box; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 13px; padding: 10px; border: 1px solid var(--line); border-radius: var(--radius);
    background: var(--surface); color: inherit; resize: vertical; }

/* ---------- misc ---------- */
.flash { padding: 11px 15px; border-radius: var(--radius); margin: 18px 0 0; border: 1px solid var(--line); }
.flash-ok { background: color-mix(in srgb, var(--accent) 15%, transparent); border-color: var(--accent); }
.flash-error { background: color-mix(in srgb, var(--loss) 15%, transparent); border-color: var(--loss); }
.install-log { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 16px; white-space: pre-wrap; }
.site-footer { display: flex; justify-content: space-between; gap: 14px; flex-wrap: wrap; padding-block: 26px; font-size: 13px; color: var(--text-muted); border-top: 1px solid var(--line); margin-top: 30px; }


/* =====================================================================
   Layout-Überarbeitung (2026-10-08, Auftrag des Betreibers: „wie eine
   professionelle Seite, optimal am Desktop und am Handy").

   **Dieser Block ändert Anordnung, keine Funktion und keine Farbe.** Er steht
   am Ende und überschreibt gezielt, statt die Regeln weiter oben umzuschreiben:
   so bleibt nachlesbar, was vorher galt und warum, und wer diesen Block
   löscht, hat den alten Stand zurück.

   Was vorher schief stand, gemessen an Bildschirmfotos bei 1440 und 500 px:

     1. Die Kopfzeile brach am Schreibtisch in zwei bis vier ungeordnete Zeilen
        um; auf der Spielseite stand die Navigation senkrecht.
     2. Am Telefon belegte die Kopfzeile drei Zeilen (Name, Saison, Menüknopf).
     3. „2 – 0 – 0" in der Kennzahlenleiste brach mitten in der Zahl um, und
        vier Kennzahlen in drei Spalten ließen ein graues Loch.
     4. Die Spaltenköpfe des Bogens standen rechts, die Zähler darunter mittig.
     5. Auf dem Telefon schnitt die Namensspalte des Bogens den Knopf „vom
        Bogen nehmen" ab, und die Ergebnisfelder ihre zweite Ziffer.
     6. Inhalt 1100 px breit auf einem 1440-px-Schirm, darin drei Karten
        untereinander, die je eine Zeile Inhalt tragen.
   ===================================================================== */

/* ---- 6. Breite ---- */
.wrap { max-width: 1240px; padding: 0 24px; }
@media (max-width: 640px) {
    .wrap { padding: 0 16px; }
}

/* ---- 1. Kopfzeile am Schreibtisch: zwei geordnete Zeilen ----

   Oben, was die Seite **einstellt** (Marke, Mannschaft, Saison — rechts
   Sportart, Konto, Sprache), darunter, wohin man **geht**. Zwölf Dinge in einer
   Zeile passen bei keiner Breite verlässlich; zwei Zeilen mit je einer Aufgabe
   brechen nicht mehr zufällig um.

   `display: contents` löst nur den Kasten von `.topbar-menu` auf: seine Kinder
   werden zu Elementen der Leiste und lassen sich einzeln setzen. Auf dem
   Telefon gilt das nicht — dort ist die Gruppe weiter der Block hinter dem
   Menüknopf. */
@media (min-width: 641px) {
    .topbar-inner { column-gap: 14px; row-gap: 0; padding-block: 0; min-height: 0; }
    .topbar-inner > .brand { min-height: 60px; }
    .topbar-menu { display: contents; }
    .topbar-menu > .sport-switch { margin-left: auto; }
    .topbar-menu > .user-menu { margin-left: 0; gap: 4px; }
    .topbar-menu > .user-menu:first-child { margin-left: auto; }
    .topbar .user-menu .btn { min-height: 40px; padding: 6px 14px; margin-left: 6px; }
    /* Der Versatz von 14 px stellt den **Text** des ersten Punkts unter die
       Marke; die Trennlinie zeichnet deshalb ein Pseudoelement, das um
       dieselben 14 px eingerückt ist — sonst ragte sie links über die Marke
       hinaus. */
    .topbar-menu > .nav {
        order: 10; flex: 0 0 calc(100% + 14px); gap: 0; margin-left: -14px;
        position: relative;
    }
    .topbar-menu > .nav::before {
        content: ""; position: absolute; top: 0; left: 14px; right: 0;
        border-top: 1px solid var(--topbar-line);
    }
    .topbar-menu > .nav a { min-height: 44px; padding: 0 14px; }
}

/* ---- 2. Kopfzeile am Telefon: Name und Menüknopf in einer Zeile ----

   Nur die **Anzeige**reihenfolge: der Menüknopf rückt neben die Marke, die
   Saison bekommt die zweite Zeile für sich. Vorher stand der Knopf allein in
   einer dritten. */
@media (max-width: 640px) {
    .topbar-inner > .menu-button { order: 1; }
    .topbar-inner > .season-switch { order: 2; }
    .topbar-inner > .topbar-menu { order: 3; }
    .topbar-inner > .brand, .topbar-inner > .topbar-team { min-height: 44px; display: inline-flex; align-items: center; }
    .topbar-inner > .topbar-team { display: block; line-height: 44px; }
}

/* ---- 3. Kennzahlenleiste ----

   Eine Kennzahl bricht nicht um: „2 – 0 –" über einer einzelnen „0" ist keine
   Zahl mehr. Die 52 px bleiben (Auftrag, von `tests/run.php` zugesichert);
   Platz schafft der schmalere seitliche Innenabstand und die Spaltenzahl.

   Die Spaltenzahl richtet sich nach der **Anzahl** der Kennzahlen, nicht nach
   dem Zufall der Fensterbreite: sechs stehen als 6×1 oder 3×2, vier als 4×1
   oder 2×2 — nie mit einem leeren Feld am Ende. `.stat-grid-2x2` bleibt, was
   es ist. */
.stat { padding-inline: 8px; }
.stat-value { white-space: nowrap; }
@media (max-width: 640px) {
    .stat-grid:not(.stat-grid-2x2) { grid-template-columns: repeat(2, 1fr); }
    /* Eine ungerade letzte Kennzahl nimmt die ganze Zeile, statt ein Loch zu lassen. */
    .stat-grid:not(.stat-grid-2x2) > .stat:last-child:nth-child(odd) { grid-column: 1 / -1; }
}
/* Sechs Kennzahlen als 3×2 erst, wenn drei 38-px-Zahlen nebeneinander passen. */
@media (min-width: 480px) and (max-width: 640px) {
    .stat-grid:not(.stat-grid-2x2):has(> .stat:nth-child(6):last-child) { grid-template-columns: repeat(3, 1fr); }
}
@media (min-width: 641px) and (max-width: 1040px) {
    .stat-grid:not(.stat-grid-2x2):has(> .stat:nth-child(5)) { grid-template-columns: repeat(3, 1fr); }
    .stat-grid:not(.stat-grid-2x2):has(> .stat:nth-child(4):last-child) { grid-template-columns: repeat(4, 1fr); }
}

/* ---- 4. und 5. Der Bogen ---- */
/* Der Kopf steht über dem, was er beschriftet: der Zähler ist mittig. */
.sheet-table thead th.num { text-align: center; }
/* Und ungekürzt: die Spalte ist 156 px breit, „7 Meter verursacht" passt
   hinein. Die drei Punkte aus `.th-ell` waren für schmale Zahlenspalten
   gedacht, nicht für diese. */
.sheet-table thead .th-ell { max-width: none; white-space: normal; overflow: visible; text-align: center; }
@media (max-width: 640px) {
    /* Die Namenszelle darf umbrechen, statt abzuschneiden: vorher verschwand
       bei längeren Namen der Knopf „vom Bogen nehmen" hinter drei Punkten —
       eine Funktion, die es am Telefon dann nicht gab. */
    .sheet-table .sheet-who { white-space: normal; overflow: visible; text-overflow: clip; min-width: 7.5rem; max-width: 9.5rem; }
}
/* Die Ergebnisfelder ohne Drehpfeile: `4ch` minus die Pfeile ließ am Telefon
   nur anderthalb Ziffern übrig. Die Breite selbst bleibt, wie sie war. */
.score-field input { -moz-appearance: textfield; appearance: textfield; padding-inline: 4px; }
.score-field input::-webkit-outer-spin-button,
.score-field input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* ---- 6. Die drei Karten der Übersicht nebeneinander ----

   Letztes Spiel, nächstes Spiel, Form. `auto-fit`: fehlt eine (kein Spiel
   gespielt, keines angesetzt), teilen sich die übrigen die Breite. Unter
   900 px stehen sie untereinander wie bisher. */
.dash-cards { display: grid; grid-template-columns: 1fr; gap: 0 20px; align-items: stretch; }
@media (min-width: 900px) {
    .dash-cards { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
}

/* ---- Filterleisten am Schreibtisch ----
   Ein Auswahlfeld mit drei Wörtern Inhalt über 1200 px Breite sieht aus wie ein
   Versehen. Am Telefon bleibt es so breit wie der Schirm — dort ist die Breite
   die Trefferfläche. */
@media (min-width: 641px) {
    .filter-bar label { max-width: 26rem; }
}

/* ---------- Spielverlauf nach Abschnitten (2026-10-08) ----------

   Der Satz über den Balken ist die Auskunft, die Balken sind ihr Beleg —
   deshalb in Schriftgröße und Farbe des Fließtexts und nicht leise wie ein
   Hinweis. Die Tabelle darunter (nur beim einzelnen Spiel) ist schmal gehalten:
   vier Spalten über 1200 px wären vier Zahlen mit viel Luft dazwischen. */
.phase-verdict { margin: 0 0 14px; font-weight: 600; color: var(--text); }
.phase-table { width: auto; min-width: min(100%, 30rem); margin-top: 16px; }
/* Der Zeilenkopf ist ein Name, keine Spaltenüberschrift: normale Schrift und
   die dünne Linie der Zeile statt der 2-px-Kante des Tabellenkopfs. */
.phase-table tbody th {
    font-size: inherit; font-weight: 400; text-transform: none; letter-spacing: 0;
    color: var(--text); border-bottom: 1px solid var(--line); white-space: nowrap;
}

/* ---------- Live-Erfassung (Auftrag 2026-10-09) ----------

   Der Spielbogen auf **einem** Bildschirm für Laptop und iPad: links eine
   schmale Randleiste, daneben das Raster, darunter eine Statuszeile. Kopf- und
   Fußzeile der Anwendung sind hier weg — das Raster braucht jede Zeile, und der
   Weg zurück steht in der Randleiste.

   **Die Höhe ist der Bildschirm** (`100dvh`, davor `100vh` für Browser, die
   `dvh` nicht kennen; `dvh` rechnet die ein- und ausfahrende Adressleiste des
   iPads mit). Die Zeilen des Rasters teilen sich, was übrig ist. */
.page-live .topbar, .page-live .site-footer { display: none; }
.page-live main.wrap { max-width: none; padding: 0; margin: 0; }
.page-live .live-blocked { max-width: 40rem; margin: 24px auto; }

.live {
    --live-rail: 78px;
    --live-who: 9.5rem;
    position: relative; display: grid;
    grid-template-columns: var(--live-rail) minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr) auto;
    height: 100vh; height: 100dvh;
    background: var(--bg);
}

/* ---- Neues Spiel ohne Netz (PWA-Schritt 4b, Loop 743) ----
   Im Entwurf zeigt `.live` zuerst nur das Formular über die ganze Fläche; mit
   „Spiel beginnen" nimmt `app.js` die Klasse weg und blendet die Zeilen der
   Spieler aus, die nicht spielen (`.is-off`). */
.live-draft { grid-column: 1 / -1; grid-row: 1 / -1; overflow: auto; padding: 16px; }
.live.is-draft-setup > :not(.live-draft):not(.live-toggle) { display: none; }
.live:not(.is-draft-setup) > .live-draft { display: none; }
.live-draft-players { display: grid; grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr)); gap: 6px 16px; }
.live-row.is-off { display: none; }
/* „Mein Bogen ist fertig" in der Randleiste (Loop 745): ein Formular nur, damit
   es ohne JavaScript abschickt — es soll keinen eigenen Abstand mitbringen. */
.live-finish { margin: 0; }
/* Die Spieluhr angehalten (Loop 748, Frage 14): die ganze Live-Fläche ist zu, bis
   „Weiter" gedrückt wird. Rot, wie bestellt: der Knopf ist `.btn-danger` — die
   Farbe kommt von dort, nicht aus einer eigenen Regel (die Leser von `--loss`
   sind gezählt). Hier nur die Größe. */
.live-clock-overlay {
    position: absolute; inset: 0; z-index: 20;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px;
    background: color-mix(in srgb, var(--bg) 85%, transparent);
}
.live-clock-overlay[hidden] { display: none; }
.live-clock-paused { font-family: var(--font-cond); font-size: 1.6rem; text-transform: uppercase; margin: 0; }
.live-clock-continue {
    min-width: 12rem; min-height: 4.5rem; font-size: 1.6rem; font-weight: 700;
}

/* ---- Die Randleiste ----
   Senkrecht und schmal statt eines Balkens oben: Breite hat ein Querformat
   genug, Höhe nicht — ein Balken oben kostete eine Spielerzeile. Die Kante zum
   Raster trennt zwei Ebenen, also 2 px in `--text` (Loop 111). */
.live-rail {
    grid-column: 1; grid-row: 1 / -1;
    display: flex; flex-direction: column; align-items: stretch; gap: 6px;
    padding: 6px; background: var(--surface); border-right: 2px solid var(--text);
    overflow: hidden;
}
.live-rail form { margin: 0; display: flex; }
.live-rail-btn {
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
    width: 100%; min-height: 54px; padding: 4px 2px;
    border: 2px solid var(--text); border-radius: var(--radius);
    background: transparent; color: var(--text); cursor: pointer;
    font-family: var(--font-sans); text-decoration: none;
}
.live-rail-btn:hover { background: var(--zebra); text-decoration: none; }
.live-rail-btn[disabled] { opacity: .45; cursor: not-allowed; }
.live-rail-btn svg { fill: currentColor; display: block; }
.live-rail-icon { font-size: 22px; line-height: 1; }
.live-rail-text { font-size: 11px; line-height: 1.15; text-align: center; color: inherit; }
.live-back { margin-top: auto; }
.live-score { margin: 6px 0 0; display: flex; flex-direction: column; align-items: center; gap: 2px; color: var(--text-muted); }
.live-score-value { font-family: var(--font-cond); font-size: 22px; font-weight: 800; line-height: 1; color: var(--text); }

/* Der Schalter ist ein Kontrollkästchen außerhalb des Bildes (`.sr-only` im
   Markup); es **behält den Fokus**, also gehört der Ring an das `<label>`, das
   an seiner Stelle sichtbar ist (wie beim Menüknopf, Loop 110). Offen ist der
   Knopf gefüllt: ein Zustand, den der Server kennt oder das Kästchen trägt. */
.live-toggle:focus-visible ~ .live-rail .live-log-button { outline: 3px solid var(--accent); outline-offset: 2px; }
.live-toggle:checked ~ .live-rail .live-log-button { background: var(--text); color: var(--bg); }

/* ---- Das Protokoll ----
   Es liegt **über** dem Raster und schiebt es nicht: auf einem iPad blieben
   neben einer 340 px breiten Spalte keine treffbaren Zellen übrig, und wer
   zurücknimmt, tippt in diesem Moment nicht. */
.live-log {
    position: absolute; z-index: 10; top: 0; bottom: 0; left: var(--live-rail);
    width: min(21rem, calc(100vw - var(--live-rail)));
    padding: 14px; overflow-y: auto;
    background: var(--surface); border-right: 2px solid var(--text);
}
.live-toggle:not(:checked) ~ .live-log { display: none; }
.live-log h2 { margin: 0 0 4px; }
.live-log-list { list-style: none; margin: 12px 0 0; padding: 0; }
.live-entry {
    display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 2px 10px; align-items: center;
    padding: 8px 0; border-bottom: 1px solid var(--line);
}
.live-entry-what { font-weight: 600; color: var(--text); }
.live-entry-meta { grid-column: 1; font-size: 12px; color: var(--text-muted); }
.live-entry .btn, .live-entry-state { grid-column: 2; grid-row: 1 / span 2; }
.live-entry .btn { min-height: 40px; }
.live-entry-state { font-size: 12px; color: var(--text-muted); }
/* Zurückgenommen: durchgestrichen **und** beschriftet — der Strich allein wäre
   eine Angabe, die nur das Auge bekommt. */
.live-entry.is-undone .live-entry-what { text-decoration: line-through; font-weight: 400; color: var(--text-muted); }

/* ---- Das Raster ----
   Eine flache Folge von Zeilen in einer Flex-Spalte. Jede Spielerzeile nimmt
   denselben Anteil der Höhe, mindestens 34 px (darunter trifft ein Daumen die
   Zelle nicht mehr — dann rollt das Raster) und höchstens 64 px (ein Kader von
   sechs soll nicht den Schirm füllen). Die Kopfzeilen nehmen, was ihr Text
   braucht. */
.live-sheet {
    grid-column: 2; grid-row: 1; margin: 0;
    display: flex; flex-direction: column; min-height: 0; overflow: auto;
}
.live-row { display: flex; flex: 1 1 0; min-height: 34px; max-height: 64px; border-bottom: 1px solid var(--line); }
.live-row:nth-child(even) { background: var(--zebra); }
.live-head {
    flex: 0 0 auto; min-height: 0; max-height: none; align-items: flex-end;
    background: var(--surface); border-bottom: 2px solid var(--text);
}
.live-row:nth-child(even).live-head { background: var(--surface); }
/* Die Kopfzeile mit den Kategorien klebt oben, wenn das Raster rollt (Auftrag
   2026-10-12) — auf dem Telefon rollt es immer, und zehn Zeilen tiefer weiß
   niemand mehr, welche Spalte „Fehlwurf" war. Feld- und Torhüterkopf kleben an
   derselben Stelle: der spätere liegt über dem früheren und löst ihn ab, sobald
   seine Zeilen oben ankommen. Über der klebenden Namensspalte (2), damit keine
   Zeile durch den Kopf scheint. */
.live-head { position: sticky; top: 0; z-index: 3; }
.live-who {
    flex: 0 0 var(--live-who); min-width: 0; display: flex; align-items: center; gap: 6px;
    padding: 0 8px; overflow: hidden; white-space: nowrap; font-weight: 600; color: var(--text);
    border-right: 1px solid var(--line);
}
.live-name { overflow: hidden; text-overflow: ellipsis; }
.live-shirt { flex: 0 0 auto; min-width: 2.1em; color: var(--text-muted); }
.live-head .live-who {
    align-self: stretch; font-family: var(--font-cond); font-weight: 800; font-size: 15px;
    text-transform: uppercase; letter-spacing: .04em;
}
/* Die Kategorie steht ungekürzt da und bricht um, statt abgeschnitten zu
   werden: eine handgeschriebene Kurzform je Kategorie wäre eine zweite Wahrheit
   darüber, wie sie heißt (siehe `thShort()`). `hyphens` trennt nach der Sprache
   am `<html>`. */
.live-th {
    flex: 1 1 0; min-width: 0; padding: 4px 2px; text-align: center;
    font-family: var(--font-cond); font-weight: 700; font-size: 12px; line-height: 1.1;
    color: var(--text-muted); overflow-wrap: anywhere; hyphens: auto;
}
.live-cell {
    flex: 1 1 0; min-width: 0; margin: 0; padding: 0 2px;
    display: flex; align-items: center; justify-content: center; gap: 8px;
    border: 0; border-right: 1px solid var(--line); border-radius: var(--radius);
    background: transparent; color: var(--text); cursor: pointer;
    font-family: var(--font-cond); font-size: 21px; font-weight: 800; line-height: 1;
    touch-action: manipulation; -webkit-tap-highlight-color: transparent;
}
.live-cell:hover { background: color-mix(in srgb, var(--text) 10%, transparent); }
.live-cell:active { background: color-mix(in srgb, var(--text) 22%, transparent); }
.live-cell:focus-visible { outline-offset: -3px; }
/* Die Null ist leise, aber da (`--zero`, mindestens 3:1): auf einem Bogen sind
   die meisten Zellen null, und eine Seite voll kräftiger Nullen verdeckt die
   Zahlen, auf die es ankommt. */
.live-cell.is-zero { color: var(--zero); font-weight: 600; }
/* Die Quittung eines Tipps: die Zelle füllt sich kurz. Fläche und Schrift als
   Paar (`--accent-fill` trägt `--on-accent`, A-20). */
.live-cell.is-saved, .live-cell.is-saved.is-zero { background: var(--accent-fill); color: var(--on-accent); }
.live-cell.is-error { outline: 3px dashed var(--text); outline-offset: -3px; }
.live-cell-label {
    font-family: var(--font-sans); font-size: 12px; font-weight: 500; line-height: 1.15;
    color: var(--text-muted); text-align: left; overflow-wrap: anywhere;
}
.live-cell.is-saved .live-cell-label { color: inherit; }
.live-team { border-top: 2px solid var(--text); border-bottom: 0; }

.live-status {
    grid-column: 2; grid-row: 2; margin: 0; padding: 4px 10px; min-height: 26px;
    font-size: 13px; color: var(--text-muted); background: var(--surface);
    border-top: 1px solid var(--line); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Wie `[data-sheet-status].is-error`: Schrift in `--accent`, keine Fläche. */
.live-status.is-error { color: var(--accent); font-weight: 600; }

/* ---- Schmal (Telefon, Tablet hochkant) ----
   Die Randleiste wird zur Zeile oben, und das Raster rollt seitwärts — die
   Live-Ansicht ist für das Querformat gebaut, soll hochkant aber bedienbar
   bleiben. Die Namensspalte klebt dann links und braucht dafür Grund und
   Stapelordnung (Loop 115); die Kante bringt sie schon mit. */
@media (max-width: 760px) {
    .live { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr) auto; }
    .live-rail {
        grid-column: 1; grid-row: 1; flex-direction: row; align-items: stretch;
        border-right: 0; border-bottom: 2px solid var(--text);
    }
    .live-rail-btn { width: auto; min-width: 58px; min-height: 48px; }
    .live-score { margin: 0 6px; justify-content: center; }
    .live-back { margin-top: 0; margin-left: auto; }
    .live-sheet { grid-column: 1; grid-row: 2; }
    .live-status { grid-column: 1; grid-row: 3; }
    .live-log { left: 0; top: 62px; width: min(21rem, 100vw); }
    .live-row { min-width: 46rem; min-height: 44px; }
    /* Der Namensstreifen so schmal, wie Nummer und ein gekürzter Nachname es
       erlauben (Auftrag 2026-10-12): jeder Millimeter hier ist eine Kategorie
       weniger, die ohne Rollen zu sehen ist. Der volle Name steht im `title`
       und im `aria-label` der Zeile. */
    .live { --live-who: 5.25rem; }
    .live-who {
        position: sticky; left: 0; z-index: 2; background: var(--surface);
        padding: 0 4px; gap: 4px; font-size: 13px;
    }
    .live-shirt { min-width: 1.9em; }
    .live-head .live-who { font-size: 12px; letter-spacing: 0; }
}
