/* ==========================================================================
   booxtar - Gestaltungssystem
   ==========================================================================

   DIE WELT: Schweizer Sportplakat. Strenges Raster, grosse Grotesk, wenige
   Farben flaechig eingesetzt. Kanten sind gerade. Trennung entsteht durch
   Linie und Flaeche, nicht durch Schatten.

   WAS DARAUS FOLGT, und warum es hier oben steht:

   1. RADIUS IST NULL. Ein Plakat hat keine abgerundeten Ecken. Ausnahme sind
      Formen, die ihre Rundung tragen - Punkte, Kreise, Bahnen.
   2. KEINE SCHATTEN. Wer Tiefe braucht, nimmt eine Linie oder eine Flaeche.
      Weiche Schatten waren der Grund, warum die Seite vorher nach halbfertigem
      Bootstrap aussah.
   3. EINE FARBE TRAEGT. --brand kommt vom Verein und ist frei waehlbar; alles
      Uebrige ist Papier und Tinte. Signalfarben sind gedaempft, damit sie mit
      keiner Vereinsfarbe streiten.
   4. FARBE IST NIE DER EINZIGE TRAEGER EINER AUSSAGE. Jeder Zustand hat
      zusaetzlich Text, Form oder Position. WCAG 2.2 AA, 1.4.1.
   5. HIERARCHIE AUS GROESSE UND GEWICHT. Archivo ist variabel in beidem.

   Kein Framework. bootstrap.min.css war eine leere Platzhalterdatei, und
   saemtliche Klassen wurden von Hand nachgebaut - daher der halb-vertraute,
   halb-fremde Eindruck. Die Klassennamen bleiben, damit die Ansichten weiter
   funktionieren; ihr Aussehen kommt jetzt von hier.

   @see PRODUCT.md, DESIGN.md
   ========================================================================== */

/* --- Schrift --------------------------------------------------------------
   Lokal, nicht von Google. Sonst ginge die IP jedes Besuchers an Google -
   auf einem Portal, das eine Datenschutzerklaerung ausliefert.
   Siehe public/fonts/HERKUNFT.md. */

@font-face {
    font-family: 'Archivo';
    font-style: normal;
    font-weight: 100 900;
    font-stretch: 62% 125%;
    font-display: swap;
    src: url('/fonts/archivo-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: 'Archivo';
    font-style: normal;
    font-weight: 100 900;
    font-stretch: 62% 125%;
    font-display: swap;
    src: url('/fonts/archivo-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;
}

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

:root {
    /* Papier und Tinte. Kein reines Weiss als Grund: Ein Hauch Kuehle nimmt
       dem Bildschirm die Haerte, ohne ins Cremefarbene zu kippen. */
    --paper: #ffffff;
    --paper-sunk: #f2f3f5;
    --paper-edge: #e3e5e9;

    --ink: #14161a;
    --ink-soft: #4a4f57;

    /* Die hellste erlaubte Textfarbe - und sie ist an der VERSENKTEN Flaeche
       gemessen, nicht an weissem Papier. Auf Weiss schaffte ein helleres Grau
       noch 4.8:1, auf `--paper-sunk` nur 4.3 - und genau dort steht sie in
       jeder Kartenfussszeile. Wer nur gegen Weiss prueft, uebersieht das.
       Jetzt 5.5:1 auf Papier und 4.9:1 auf der versenkten Flaeche. */
    --ink-faint: #646a74;
    /* Platzhalter: bewusst blasser als jede echte Angabe (3.2:1 statt 18:1),
       damit ein Beispielwert nicht für einen Eintrag gehalten wird. */
    --ink-placeholder: #8b9099;

    /* Die Farbe des Vereins. Ueberschrieben im Kopf der Seite, sobald ein
       Verein eine eigene hinterlegt hat. Die Vorgabe ist ein Kobalt aus der
       Plakattradition - kraeftig, kontraststark, und deutlich kein
       SaaS-Indigo. */
    --brand: #1b34c8;
    --brand-ink: #ffffff;

    /* DIESELBE FLAECHE, EINE STUFE WEITER WEG VON DER SCHRIFT - fuer das
     * Ueberfahren mit der Maus.
     *
     * Die Richtung haengt an --brand-ink und wird deshalb gerechnet, nicht
     * hier gemischt: Bei weisser Schrift geht es abwaerts, bei schwarzer
     * aufwaerts. Vorher stand hier ein festes color-mix() mit Schwarz - bei
     * einem Vereinsgelb mit schwarzer Schrift lief das genau falsch herum,
     * und die Beschriftung verschwand beim Hinzeigen.
     *
     * Gerechnet in Support\BrandColor::hoverFor(), gesetzt im Seitenkopf. */
    --brand-hover: #1529a2;

    /* DIESELBE FARBE, SO WEIT ABGEDUNKELT, DASS SIE AUF PAPIER TRAEGT.
     *
     * --brand liegt als FLAECHE unter Schrift; dort traegt sie, weil
     * --brand-ink sich danach richtet UND die Flaeche selbst nachgibt, wo
     * weder Schwarz noch Weiss reicht (surfaceFor()). Als LINIE - Verweise,
     * Umrisse, aktive Zustaende auf Weiss - muss sie dunkel genug sein.
     *
     * Anfangs war es eine Farbe fuer beides, und jede helle Vereinsfarbe wurde
     * abgewiesen: Ein Verein mit Gelb-Schwarz konnte seine eigenen Farben nicht
     * verwenden. Jetzt zwei Werte, und JEDE Farbe ist erlaubt.
     *
     * Gerechnet in SupportBrandColor::lineFor(), gesetzt im Seitenkopf. */
    --brand-line: #1b34c8;

    /* DIESELBE LINIE FÜR DIE ANDEREN DARSTELLUNGEN - aufgehellt im Dunkeln,
     * auf 7:1 gezogen bei hohem Kontrast. Gerechnet in Support\BrandColor
     * und im Seitenkopf je Verein überschrieben; hier die Werte für die
     * Vorgabefarbe. Gewählt wird unten nach den Attributen am <html>. */
    --brand-line-dark: #7a8ae0;
    --brand-line-hc: #1b34c8;
    --brand-line-hc-dark: #9fabea;

    /* DIE AKZENTFARBE - zweitrangig, als SCHMUCK. Die Vereinsfarbe trägt
     * alles, was etwas bedeutet (Knöpfe, Verweise, Preise, Überfahren,
     * Farbfeld, aktive Seite); der Akzent bringt Abwechslung: ein Punkt vor
     * den Abschnittstiteln, eine Kante oben an den Kacheln. Gesetzt im
     * Seitenkopf als --accent, nur wenn der Verein eine gewählt hat
     * (dann trägt <html> data-accent). Siehe "Schmuck" am Ende. */

    /* Signale. Bewusst dunkel und gedaempft: Sie muessen neben JEDER
       Vereinsfarbe bestehen, ohne mit ihr zu streiten. Jede erreicht auf
       Papier mindestens 4.5:1. */
    --signal-good: #1c6b3f;
    --signal-warn: #8a5000;
    --signal-bad: #ac1f21;
    --signal-info: #1f4f8f;

    /* Ihre Flaechen - hell genug, dass die dunkle Tinte darauf ruht. */
    --field-good: #e8f5ed;
    --field-warn: #fdf3e2;
    --field-bad: #fdeceb;
    --field-info: #eaf1fb;

    /* Raster. Ein Plakat lebt vom Mass: 4px-Basis, plakative Spruenge. */
    --s1: 0.25rem;
    --s2: 0.5rem;
    --s3: 0.75rem;
    --s4: 1rem;
    --s5: 1.5rem;
    --s6: 2rem;
    --s7: 3rem;
    --s8: 4rem;
    --s9: 6rem;

    /* Linien statt Schatten. Zwei Staerken, mehr braucht es nicht. */
    --rule: 1px solid var(--paper-edge);
    --rule-strong: 2px solid var(--ink);

    /* Gerade Kanten. Steht als Marke da, damit der Vorsatz sichtbar ist und
       niemand versehentlich einen Radius einstreut. */
    --radius: 0;
    --radius-round: 999px;

    /* Bewegung: knapp, ohne Schnoerkel. */
    --ease: cubic-bezier(0.2, 0, 0, 1);
    --fast: 120ms;
    --slow: 240ms;

    /* Breiten */
    --measure: 68ch;
    --page: 1180px;
    --page-narrow: 760px;
}

/* Wer weniger Bewegung wuenscht, bekommt weniger. Nicht abschalten -
   Zustandswechsel muessen sichtbar bleiben, nur nicht wandern. */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* --- Grundlagen ------------------------------------------------------------ */

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

* {
    margin: 0;
    padding: 0;
}

/* Das Attribut hidden muss jede display-Angabe schlagen. Die Voreinstellung
   des Browsers hat die schwaechste Spezifitaet und wurde still von jeder
   Klasse ueberstimmt - ein <button class="btn" hidden> war sichtbar. Genau
   die Sorte Fehler, die man erst im Bild sieht. */
[hidden] {
    display: none !important;
}

html {
    -webkit-text-size-adjust: 100%;
}

/* min-height statt height: Mit einer festen Hoehe klemmt der Flex-Container
   den Inhalt auf Bildschirmhoehe fest - laengere Seiten wurden abgeschnitten
   und die Fusszeile lag ueber dem Text. */
html,
body {
    min-height: 100%;
}

body {
    font-family: 'Archivo', system-ui, -apple-system, 'Segoe UI', sans-serif;
    font-size: 1rem;
    line-height: 1.6;
    color: var(--ink);
    background: var(--paper-sunk);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    /* Tabellare Ziffern ueberall: Betraege und Zahlen in Listen stehen damit
       untereinander, statt zu zappeln. */
    font-variant-numeric: tabular-nums;
    display: flex;
    flex-direction: column;
}

main {
    flex: 1 0 auto;
}

/* --- Typografie ------------------------------------------------------------
   Die Skala springt plakativ. Ueberschriften stehen breit (font-stretch), wie
   auf dem Plakat; Fliesstext bleibt normal breit und ruhig. */

h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6 {
    font-weight: 700;
    line-height: 1.1;
    letter-spacing: -0.02em;
    text-wrap: balance;
    color: var(--ink);
}

/* Die untere Grenze lag bei 2.25rem und war fuer das gedacht, was hier meist
   steht: "Angebote", "Kasse", ein Kursname. Eine Seite traegt aber auch den
   Namen, den der Verein vergeben hat - "Vereinsmeisterschaften im
   Geraeteturnen mit Siegerehrung". Auf 320 Pixeln brauchte der acht Zeilen
   und damit den halben Bildschirm, bevor der Leser das erste Wort ueber die
   Sache gelesen hatte.

   1.875rem statt 2.25rem: ein Sechstel kleiner, und die Ueberschrift bleibt
   das Groesste auf der Seite. Die obere Grenze ist unveraendert - auf dem
   grossen Schirm traegt das Plakat. */
h1, .h1 {
    font-size: clamp(1.875rem, 1.4rem + 2.6vw, 3.75rem);
    font-stretch: 112%;
    font-weight: 800;
    letter-spacing: -0.03em;
}

h2, .h2 {
    font-size: clamp(1.75rem, 1.4rem + 1.5vw, 2.5rem);
    font-stretch: 106%;
}

h3, .h3 { font-size: clamp(1.375rem, 1.2rem + 0.8vw, 1.75rem); }
h4, .h4 { font-size: 1.25rem; }
h5, .h5 { font-size: 1.0625rem; }
h6, .h6 { font-size: 0.9375rem; }

p {
    max-width: var(--measure);
}

.lead {
    font-size: clamp(1.0625rem, 1rem + 0.4vw, 1.25rem);
    line-height: 1.5;
    color: var(--ink-soft);
}

small,
.small {
    font-size: 0.875rem;
    line-height: 1.5;
}

strong,
.fw-bold {
    font-weight: 700;
}

/* Die Beschriftung ueber einem Wert: klein, versal, gesperrt. Das ist die
   Stimme des Plakats fuer alles Nebensaechliche - und der Grund, warum die
   Werte selbst gross und nackt dastehen duerfen. */
.label {
    font-size: 0.6875rem;
    font-weight: 650;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ink-faint);
    font-stretch: 88%;
}

.text-muted { color: var(--ink-faint); }
.text-center { text-align: center; }
.text-end { text-align: right; }
.text-nowrap { white-space: nowrap; }

/* --- Links ----------------------------------------------------------------- */

a {
    color: var(--brand-line);
    text-decoration-thickness: 1px;
    text-underline-offset: 0.2em;
    transition: color var(--fast) var(--ease);
}

/* NICHT FUER SCHALTFLAECHEN. `a:hover` hat (0,1,1) und schlaegt damit `.btn`
 * mit (0,1,0) - jeder Knopf, der ein Verweis ist, bekam beim Ueberfahren
 * tiefschwarze Schrift, gleichgueltig was seine Spielart vorsah. Auf einem
 * Knopf, der sich dabei mit der Vereinsfarbe fuellt, hiess das Schwarz auf
 * Dunkelblau.
 *
 * Sichtbar wurde es nur an Verweisen: "Filtern" ist ein <button> und war
 * richtig, "CSV-Export" und "Oeffnen" daneben sind <a> und waren falsch.
 * Dieselbe Falle wie bei `.field a:not(.btn)`, eine Ebene hoeher. */
a:not(.btn):hover {
    color: var(--ink);
}

/* --- Fokus -----------------------------------------------------------------
   EINE Regel fuer alles. Der Ring sitzt aussen, hat zwei Farben und ist
   damit auf jedem Grund sichtbar - auch auf der Vereinsfarbe selbst.
   :focus-visible statt :focus, damit ein Mausklick keinen Ring hinterlaesst,
   die Tastatur aber immer einen bekommt. */

:focus-visible {
    outline: 3px solid var(--ink);
    outline-offset: 2px;
    box-shadow: 0 0 0 6px var(--paper);
}

/* Auf FARBIGEM Grund kehrt sich der Ring um.
 *
 * Hier stand einmal zusaetzlich `.navbar` - ein Fehler: Die Navigationsleiste
 * liegt selbst auf Papier, und ein papierfarbener Ring darauf ist kein Ring.
 * In der gesamten Leiste war die Tastaturbedienung damit unsichtbar, und
 * genau das faellt beim Klicken mit der Maus niemandem auf. */
.on-brand :focus-visible {
    outline-color: var(--paper);
    box-shadow: 0 0 0 6px rgba(0, 0, 0, 0.45);
}

/* Der Sprunglink. Fuer jeden, der mit der Tastatur bedient, ist er der
   erste Halt der Seite - und damit die Stelle, an der sich zeigt, ob
   Barrierefreiheit gemeint oder nur behauptet ist.
 *
 * ZWEI GRUENDE FUER DIESE FASSUNG, beide im Bild gefunden:
 *
 * `position: absolute` haengt am Dokument, nicht am Fenster. Wer mitten auf
 * einer langen Seite die Tabulatortaste drueckt, bekam den Link irgendwo
 * oberhalb des sichtbaren Bereichs - fokussiert, aber unsichtbar. `fixed`
 * haelt ihn am Fenster.
 *
 * `transform` statt `top: -100%`: Prozentwerte fuer `top` rechnen gegen den
 * umgebenden Block, `translateY` gegen die eigene Hoehe des Elements. Nur
 * das zweite ist unabhaengig davon, wie das Seitengeruest gebaut ist. */
.skip-link {
    position: fixed;
    left: var(--s4);
    top: var(--s4);
    z-index: 100;
    background: var(--ink);
    color: var(--paper);
    padding: var(--s3) var(--s5);
    font-weight: 700;
    text-decoration: none;
    transform: translateY(calc(-100% - var(--s5)));
    transition: transform var(--fast) var(--ease);
}

/* Die Schriftfarbe steht auch beim Ueberfahren fest. Ohne die zweite Zeile
   griff die allgemeine Regel fuer Verweise und faerbte ihn auf --ink - auf
   einem Grund, der selbst --ink ist. Der Sprunglink erschien dann zwar, war
   aber leer: schwarz auf schwarz, genau fuer den, der ihn braucht. */
/* Mit dem Elementnamen davor, sonst reicht die Spezifitaet nicht:
   `a:not(.btn):hover` zaehlt (0,2,1), `.skip-link:hover` nur (0,2,0). */
a.skip-link:focus,
a.skip-link:hover {
    color: var(--paper);
}

.skip-link:focus {
    transform: translateY(0);
}

/* --- Raster ----------------------------------------------------------------
   Das Fundament, das vorher fehlte. Karten in einer Reihe sind ab jetzt
   gleich hoch, weil das Raster sie streckt - nicht weil jede Ansicht daran
   denkt. */

.container {
    width: 100%;
    max-width: var(--page);
    margin-inline: auto;
    padding-inline: var(--s5);
}

.container-narrow {
    max-width: var(--page-narrow);
}

.row {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    gap: var(--s5);
    align-items: stretch;
}

/* Die Spalten. Unterhalb von 48rem faellt alles auf volle Breite - ein
   Telefon hat keine zwoelf Spalten. */
.col-md-1  { grid-column: span 12; }
.col-md-2  { grid-column: span 12; }
.col-md-3  { grid-column: span 12; }
.col-md-4  { grid-column: span 12; }
.col-md-5  { grid-column: span 12; }
.col-md-6  { grid-column: span 12; }
.col-md-7  { grid-column: span 12; }
.col-md-8  { grid-column: span 12; }
.col-md-9  { grid-column: span 12; }
.col-md-10 { grid-column: span 12; }
.col-md-11 { grid-column: span 12; }
.col-md-12 { grid-column: span 12; }

@media (min-width: 48rem) {
    .col-md-1  { grid-column: span 1; }
    .col-md-2  { grid-column: span 2; }
    .col-md-3  { grid-column: span 3; }
    .col-md-4  { grid-column: span 4; }
    .col-md-5  { grid-column: span 5; }
    .col-md-6  { grid-column: span 6; }
    .col-md-7  { grid-column: span 7; }
    .col-md-8  { grid-column: span 8; }
    .col-md-9  { grid-column: span 9; }
    .col-md-10 { grid-column: span 10; }
    .col-md-11 { grid-column: span 11; }
    .col-md-12 { grid-column: span 12; }
}

.g-2 { gap: var(--s2); }
.g-4 { gap: var(--s4); }

/* Ein Stapel gleichrangiger Bloecke. */
.stack {
    display: flex;
    flex-direction: column;
    gap: var(--s4);
}

/* Automatisch umbrechende Kacheln gleicher Hoehe - fuer Angebotslisten. */
.tiles {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 20rem), 1fr));
    gap: var(--s5);
    align-items: stretch;
}

/* --- Flaechen --------------------------------------------------------------
   Eine Karte ist ein Blatt: weisse Flaeche, eine Linie ringsum, keine
   Rundung, kein Schatten. Sie fuellt ihre Rasterzelle ganz aus - damit ist
   die Sache mit den ungleichen Hoehen erledigt. */

.card {
    display: flex;
    flex-direction: column;
    background: var(--paper);
    border: var(--rule);
    border-radius: var(--radius);
}

/* GLEICH HOCH NUR DORT, WO ES STIMMT.
 *
 * `height: 100%` stand einmal auf `.card` selbst - richtig, solange die Karte
 * ihre Rasterzelle IST. Stehen zwei untereinander in einer Spalte, wurde jede
 * so hoch wie die ganze Spalte, und aus drei Zeilen Beschreibung wurde ein
 * weisses Feld über den halben Bildschirm.
 *
 * Deshalb hier statt dort: Eine Karte fuellt ihre Zelle, wenn sie allein
 * darin steht. Zwei teilen sie sich. */
.tiles > .card,
.row > [class*='col-'] > .card:only-child {
    height: 100%;
}

.card-header {
    padding: var(--s4) var(--s5);
    border-bottom: var(--rule);
    font-weight: 700;
}

.card-body {
    padding: var(--s5);
    flex: 1 1 auto;
}

.card-footer {
    padding: var(--s4) var(--s5);
    border-top: var(--rule);
    background: var(--paper-sunk);
    margin-top: auto;
}

.card-title { margin-bottom: var(--s2); }

.card-text {
    color: var(--ink-soft);
    margin-bottom: var(--s3);
}

.card-text:last-child { margin-bottom: 0; }

.card-footer__line {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--s3);
}

/* Eine anklickbare Kachel. Die ganze Flaeche ist das Ziel, nicht nur die
   Ueberschrift - am Telefon ist das der Unterschied zwischen treffen und
   danebentippen. */
.card--link {
    position: relative;
    transition: border-color var(--fast) var(--ease),
                transform var(--fast) var(--ease);
}

.card--link:hover {
    border-color: var(--ink);
}

.card--link:focus-within {
    border-color: var(--brand-line);
}

/* Der Fangbereich: ein Link deckt die Karte ab. Die uebrigen Links darin
   liegen darueber und bleiben eigenstaendig anklickbar. */
.card--link .stretched::after {
    content: '';
    position: absolute;
    inset: 0;
}

.card--link .card-footer a,
.card--link .card-footer button {
    position: relative;
    z-index: 1;
}

/* --- Das Farbfeld ----------------------------------------------------------
   Der eine Ort, an dem die Vereinsfarbe die Flaeche traegt. Auf dem Plakat
   ist das der Grund, auf dem der Titel steht. */

.field {
    background: var(--brand);
    color: var(--brand-ink);
    padding-block: var(--s8);
}

/* EIN VERLAUF, UND ZWAR NUR HIER.
 *
 * Die Welt ist flaechig - Verlaeufe stehen ausdruecklich auf der Liste
 * dessen, was sie nicht kennt. Das Kopffeld der Startseite ist die eine
 * Ausnahme: Es ist die groesste zusammenhaengende Flaeche der Seite, und
 * genau dort wirkt ein reiner Farbblock am flachsten.
 *
 * DIE LESBARKEIT BLEIBT BEWEISBAR. Gemischt wird zwischen --brand und
 * --brand-hover, also zwischen zwei Werten, die BEIDE gegen --brand-ink
 * mindestens 4.5:1 erreichen (surfaceFor und hoverFor sorgen dafuer, siehe
 * Support\BrandColor). Jeder Punkt dazwischen liegt zwischen den beiden -
 * es gibt keine Stelle, an der die Schrift durchfaellt.
 *
 * Ein beliebiger zweiter Farbton waere genau das nicht: Er brauchte eine
 * eigene Pruefung, und niemand faende die Stelle, an der es kippt.
 *
 * Kein Verlauf fuer den, der Bewegung und Effekte reduziert haben will -
 * dort bleibt die Flaeche. */
.field--gradient {
    background-image: linear-gradient(135deg, var(--brand) 0%, var(--brand-hover) 100%);
}

@media (prefers-contrast: more) {
    .field--gradient { background-image: none; }
}

/* Links auf dem Farbfeld nehmen die Schriftfarbe des Feldes - aber NICHT
   die Schaltflaechen darin. `.field a` schlaegt sonst `.btn` in der
   Spezifitaet, und eine helle Schaltflaeche bekommt helle Schrift: weiss auf
   weiss. Genau die Sorte Fehler, die man erst im Bild sieht. */
.field a:not(.btn) { color: var(--brand-ink); }

.field .text-muted,
.field .lead {
    /* Auf farbigem Grund wird Sekundaeres aus derselben Farbe getoent, nie
       grau - Grau auf Farbe wird schmutzig und verliert den Kontrast. */
    color: color-mix(in srgb, var(--brand-ink) 82%, transparent);
}

/* --- Schaltflaechen --------------------------------------------------------
   Rechteckig, mit Gewicht. Die Hauptschaltflaeche traegt die Vereinsfarbe,
   alles Uebrige ist Tinte auf Papier. */

.btn {
    --btn-bg: transparent;
    --btn-fg: var(--ink);
    --btn-edge: var(--ink);

    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--s2);
    padding: 0.6875rem var(--s5);
    min-height: 2.75rem; /* 44px - die Mindestgroesse fuer einen Finger */
    border: 2px solid var(--btn-edge);
    border-radius: var(--radius);
    background: var(--btn-bg);
    color: var(--btn-fg);
    font: inherit;
    font-weight: 650;
    line-height: 1.2;
    text-align: center;
    text-decoration: none;
    cursor: pointer;
    transition: background-color var(--fast) var(--ease),
                color var(--fast) var(--ease),
                border-color var(--fast) var(--ease);
}

/* JEDER KNOPF BLEIBT BEIM UEBERFAHREN, WAS ER IST.
 *
 * Hier wurde einmal jeder Knopf tiefschwarz - der primaere, der sekundaere,
 * der helle. Damit verschwand genau in dem Moment, in dem jemand hinzeigt,
 * der Unterschied zwischen Haupt- und Nebenhandlung: Zwei nebeneinander
 * stehende Knoepfe sahen beim Ueberfahren identisch aus.
 *
 * Jetzt vertieft jeder seine eigene Farbe. Der neutrale Knopf bekommt eine
 * Flaeche, statt sich umzufaerben. */
.btn:hover {
    --btn-bg: var(--paper-sunk);
    --btn-fg: var(--ink);
    --btn-edge: var(--ink);
}

.btn:disabled,
.btn[aria-disabled='true'] {
    opacity: 0.45;
    cursor: not-allowed;
}

.btn:disabled:hover {
    --btn-bg: transparent;
    --btn-fg: var(--ink);
}

.btn-primary {
    --btn-bg: var(--brand);
    --btn-fg: var(--brand-ink);
    --btn-edge: var(--brand);
}

/* Der Hauptknopf vertieft die Vereinsfarbe, statt sie zu verlassen. Das
   haelt ihn erkennbar - und die Schrift bleibt dieselbe, weil --brand-hover
   sich VON ihr wegbewegt: abwaerts bei weisser Schrift, aufwaerts bei
   schwarzer. Hier stand einmal ein festes color-mix() mit Schwarz; bei einem
   Vereinsgelb wurde der Knopf dunkler, die Schrift blieb schwarz, und die
   Beschriftung verschwand genau beim Hinzeigen. */
.btn-primary:hover {
    --btn-bg: var(--brand-hover);
    --btn-edge: var(--brand-hover);
    --btn-fg: var(--brand-ink);
}

/* AUF PAPIER GILT DIE LINIENFARBE, NICHT DIE FLAECHENFARBE.
   Hier stand --brand: Bei einem hellen Vereinston war die Beschriftung dann
   Gelb auf Weiss - 1,3:1. Der Umriss ebenso, sonst steht eine lesbare
   Schrift in einem unsichtbaren Rahmen. */
.btn-outline-primary {
    --btn-fg: var(--brand-line);
    --btn-edge: var(--brand-line);
}

.btn-outline-primary:hover {
    --btn-bg: var(--brand);
    --btn-fg: var(--brand-ink);
}

.btn-outline-secondary {
    --btn-edge: var(--paper-edge);
    --btn-fg: var(--ink-soft);
}

/* Der zurueckhaltende Knopf bleibt zurueckhaltend: Flaeche statt Umfaerben.
   Wuerde er schwarz, saehe er neben dem Hauptknopf genauso wichtig aus. */
.btn-outline-secondary:hover {
    --btn-bg: var(--paper-sunk);
    --btn-fg: var(--ink);
    --btn-edge: var(--ink);
}

.btn-light {
    --btn-bg: var(--paper);
    --btn-edge: var(--paper-edge);
}

/* AUF FARBIGEM GRUND WIRD NICHTS SCHWARZ.
 *
 * `.btn:hover` faerbt jeden Knopf tiefschwarz - richtig auf hellem Papier,
 * falsch im Farbfeld: Dort sprang der helle Knopf beim Ueberfahren von Weiss
 * auf Schwarz, mitten in einer gruenen Flaeche. Ein Bruch, kein Hinweis.
 *
 * Hier bleibt er hell und setzt sich nur ab. Die Schrift wird zur
 * Vereinsfarbe - die Rueckmeldung kommt aus der Farbwelt, in der der Knopf
 * steht. In der LINIENFASSUNG: Der Knopf ist jetzt hell, und darauf gilt
 * dieselbe Regel wie auf Papier. */
.field .btn-light:hover {
    --btn-bg: var(--paper-sunk);
    --btn-fg: var(--brand-line);
    --btn-edge: var(--paper-sunk);
}

.btn-outline-danger {
    --btn-fg: var(--signal-bad);
    --btn-edge: var(--signal-bad);
}

.btn-outline-danger:hover {
    --btn-bg: var(--signal-bad);
    --btn-fg: var(--paper);
}

.btn-danger {
    --btn-bg: var(--signal-bad);
    --btn-fg: var(--paper);
    --btn-edge: var(--signal-bad);
}

.btn-sm {
    padding: var(--s2) var(--s3);
    min-height: 2.25rem;
    font-size: 0.875rem;
}

.btn-lg {
    padding: var(--s4) var(--s6);
    min-height: 3.25rem;
    font-size: 1.0625rem;
}

.w-100 { width: 100%; }

.button-row,
.form-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s3);
    align-items: center;
}

.form-actions {
    margin-top: var(--s6);
    padding-top: var(--s5);
    border-top: var(--rule);
}

/* Eine Filterzeile ueber einer Liste: Beschriftung, Auswahl, Knopf in
   einer Reihe. Bricht auf schmalen Geraeten um, statt zu quetschen. */
.filter-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s2);
    align-items: center;
}

/* Eine Auswahl in einer Filterzeile ist so breit wie ihr laengster
   Eintrag, nicht so breit wie die Seite: Sonst steht der Knopf darunter,
   und aus einer Zeile werden drei. */
.filter-row .form-select {
    display: inline-block;
    width: auto;
}

/* Die Blaetterung unter einer langen Liste. Wie eine button-row, nur
   enger: Zahlen stehen dicht beieinander, damit die Reihe als EINE
   Sache gelesen wird und nicht als sieben Knoepfe. */
.pagination-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s2);
    align-items: center;
    margin-top: var(--s5);
}

/* Die zweite Leiste ÜBER der Liste: Abstand nach unten statt nach oben. */
.pagination-row--top {
    margin-top: 0;
    margin-bottom: var(--s4);
}

/* --- Formulare -------------------------------------------------------------
   Ein Feld ist eine Linie, auf der etwas steht. Beschriftung darueber, nie
   daneben: bei schmaler Breite bricht daneben immer.  */

.form-label {
    display: block;
    margin-bottom: var(--s2);
    font-weight: 650;
}

.form-control,
.form-select {
    display: block;
    width: 100%;
    padding: 0.625rem var(--s3);
    min-height: 2.75rem;
    font: inherit;
    color: var(--ink);
    background: var(--paper);
    border: 1px solid var(--ink-faint);
    border-radius: var(--radius);
    transition: border-color var(--fast) var(--ease),
                box-shadow var(--fast) var(--ease);
}

.form-control::placeholder,
input::placeholder,
textarea::placeholder {
    /* Lesbar, aber deutlich blasser als ein Eintrag: Mit dem früheren
       Grau (5.5:1) sah ein Beispielwert aus wie eine echte Angabe. */
    color: var(--ink-placeholder);
    opacity: 1;
}

.form-control:hover,
.form-select:hover {
    border-color: var(--ink);
}

.form-control:focus,
.form-select:focus {
    border-color: var(--brand-line);
    outline: none;
    box-shadow: inset 0 -2px 0 var(--brand-line);
}

/* Der Tastaturring gewinnt trotzdem - er ist die Zugaenglichkeitszusage. */
.form-control:focus-visible,
.form-select:focus-visible {
    outline: 3px solid var(--ink);
    outline-offset: 2px;
}

.form-select {
    appearance: none;
    padding-right: var(--s7);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5' fill='none' stroke='%2314161a' stroke-width='1.75' stroke-linecap='square'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right var(--s3) center;
}

textarea.form-control {
    min-height: 7rem;
    resize: vertical;
}

/* Die IBAN in Vierergruppen (custom.js setzt die Leerzeichen). Gleich breite
   Ziffern, damit die Gruppen untereinander gleich lang wirken, und ein etwas
   weiterer Abstand zwischen ihnen - ein normales Leerzeichen ist in dieser
   Schrift so schmal, dass die Gruppen fast wieder zusammenlaufen. */
.form-control[data-iban] {
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.02em;
    word-spacing: 0.3em;
}

.form-text {
    display: block;
    margin-top: var(--s2);
    font-size: 0.875rem;
    color: var(--ink-faint);
    max-width: var(--measure);
}

/* Ein Feld mit Fehler traegt Farbe UND Linie UND Text - nie Farbe allein. */
.form-control[aria-invalid='true'] {
    border-color: var(--signal-bad);
    border-width: 2px;
}

/* Kontrollkaestchen und Schalter */

.form-check {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: var(--s3);
    align-items: start;
}

.form-check-input {
    width: 1.375rem;
    height: 1.375rem;
    margin-top: 0.125rem;
    border: 2px solid var(--ink);
    border-radius: var(--radius);
    background: var(--paper);
    appearance: none;
    cursor: pointer;
    flex: none;
    transition: background-color var(--fast) var(--ease);
}

.form-check-input[type='radio'] {
    border-radius: var(--radius-round);
}

.form-check-input:checked {
    background-color: var(--brand);
    border-color: var(--brand);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3 8.5 6.5 12 13 4.5' fill='none' stroke='%23fff' stroke-width='2.25' stroke-linecap='square'/%3E%3C/svg%3E");
    background-size: 100%;
    background-repeat: no-repeat;
}

.form-check-input[type='radio']:checked {
    background-image: none;
    box-shadow: inset 0 0 0 4px var(--paper);
}

.form-check-label {
    cursor: pointer;
}

.form-check-label .form-text {
    margin-top: var(--s1);
}

.form-switch .form-check-input {
    width: 2.75rem;
    border-radius: var(--radius-round);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='5' fill='%2314161a'/%3E%3C/svg%3E");
    background-size: 1.375rem;
    background-position: left center;
    background-repeat: no-repeat;
    transition: background-position var(--fast) var(--ease),
                background-color var(--fast) var(--ease);
}

.form-switch .form-check-input:checked {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='5' fill='%23fff'/%3E%3C/svg%3E");
    background-position: right center;
}

fieldset {
    border: none;
}

legend {
    padding: 0;
}

/* --- Abzeichen -------------------------------------------------------------
   Rechteckig, klein, versal. Kein Pillenformat - Pillen gehoeren zu der
   weichen Welt, die diese Seite gerade verlassen hat. */

.badge {
    display: inline-flex;
    align-items: center;
    gap: var(--s1);
    padding: 0.1875rem var(--s2);
    border-radius: var(--radius);
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    font-stretch: 90%;
    white-space: nowrap;
    border: 1px solid currentColor;
}

/* Die Farbe des Abzeichens steht in einer eigenen Marke und nicht nur in
   `color`. Der Grund: Ein gefuelltes Abzeichen braucht dieselbe Farbe fuer
   die FLAECHE - und dann muss die Schrift eine andere sein. Mit `color`
   allein ginge das nicht auseinander. */
.bg-primary   { --badge-color: var(--brand);       color: var(--badge-color); }
.bg-success   { --badge-color: var(--signal-good); color: var(--badge-color); }
.bg-warning   { --badge-color: var(--signal-warn); color: var(--badge-color); }
.bg-danger    { --badge-color: var(--signal-bad);  color: var(--badge-color); }
.bg-info      { --badge-color: var(--signal-info); color: var(--badge-color); }
.bg-secondary { --badge-color: var(--ink-faint);   color: var(--badge-color); }

/* Ein gefuelltes Abzeichen fuer den einen Zustand, der herausstechen soll.
 *
 * HIER STAND EINMAL `background: currentColor` MIT `.badge--solid > *`.
 * Beides zusammen ergab gruene Schrift auf gruenem Grund: Der Grund nahm die
 * Textfarbe, und die Aufhellung erreichte nur ELEMENT-Kinder - das Symbol
 * wurde weiss, der blosse Text daneben nicht. Ein Abzeichen, das seine
 * eigene Aussage verschluckt.
 *
 * Jetzt traegt die Flaeche die Marke und die Schrift steht eigenstaendig. */
.badge--solid {
    background: var(--badge-color);
    border-color: var(--badge-color);
    color: var(--paper);
}

/* Auf der VEREINSFARBE gilt die dafuer berechnete Schrift, nicht pauschal
   Weiss: Bei einem hellen Vereinston waere Weiss darauf unlesbar - und genau
   diese Farbe darf jeder Verein selbst waehlen. */
.badge--solid.bg-primary {
    color: var(--brand-ink);
}

.badge-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s2);
    margin-bottom: var(--s3);
}

/* --- Hinweise --------------------------------------------------------------
   DIE FLAECHE TRAEGT, NICHT EIN BALKEN.

   Hier stand erst ein farbiger Streifen links, dann einer oben - beide ueber
   1px, beide dasselbe Muster: ein Akzentbalken an einer Kante, der die
   Aussage tragen soll. Das ist die verbreitetste Angewohnheit maschinell
   erzeugter Oberflaechen, und in einer Welt aus Flaeche und Linie hat sie
   keinen Platz.

   Stattdessen: die Feldfarbe als Grund, eine Haarlinie in der Signalfarbe
   ringsum, und das Symbol samt Text in derselben Farbe. Damit steht die
   Aussage dreifach da - Flaeche, Zeichen, Wort - und nicht nur in einem
   Streifen, den ein farbenblinder Blick nicht unterscheidet. */

.alert {
    padding: var(--s4) var(--s5);
    /* Wie bei .notice: der Abstand gehoert an den Kasten. */
    margin-bottom: var(--s5);
    border: 1px solid currentColor;
    border-radius: var(--radius);
}

.alert > * { color: var(--ink); }
.alert strong { color: inherit; }

.alert-success { color: var(--signal-good); background: var(--field-good); }
.alert-warning { color: var(--signal-warn); background: var(--field-warn); }
.alert-danger  { color: var(--signal-bad);  background: var(--field-bad); }
.alert-info    { color: var(--signal-info); background: var(--field-info); }

.alert-secondary {
    color: var(--ink-faint);
    background: var(--paper-sunk);
}

/* Der hervorgehobene Hinweis eines Vereins. */
/* Der hervorgehobene Hinweis eines Vereins.
 *
 * Zuerst stand hier ein Akzentbalken links - das Muster, das diese Welt nicht
 * kennt. Dann eine Kante ringsum in der Vereinsfarbe, und im Bild zeigte
 * sich der naechste Fehler: Drei Hinweise untereinander ergaben drei bunt
 * umrandete Kaesten, und die Farbe schmueckte, statt zu bedeuten.
 *
 * Jetzt traegt der Titel die Farbe und der Rahmen ist neutral. Ein Stapel
 * liest sich als Liste, und die Vereinsfarbe fuehrt das Auge zur
 * Ueberschrift. */
.notice {
    padding: var(--s5);
    /* ABSTAND NACH UNTEN GEHOERT AN DEN KASTEN, nicht an jede Verwendung.
       Er fehlte, und jede Ansicht musste selbst an `mb-4` denken - an
       mehreren Stellen tat sie es nicht, und das naechste Element klebte
       unmittelbar darunter. Die Abstandsklassen stehen weiter unten in
       dieser Datei und schlagen den Wert weiterhin, wo eine Ansicht
       ausdruecklich `mb-0` sagt. */
    margin-bottom: var(--s5);
    background: var(--paper);
    border: var(--rule);
}

.notice__title {
    font-size: 1rem;
    font-weight: 700;
    margin-bottom: var(--s2);
    color: var(--brand-line);
}

.notice__body {
    color: var(--ink-soft);
    margin-bottom: var(--s3);
}

.notice__body:last-child { margin-bottom: 0; }

/* --- Kennzahlen ------------------------------------------------------------
   Beschriftung klein und versal darueber, der Wert gross und nackt darunter.
   Das ist die Stelle, an der die tabellaren Ziffern zaehlen. */

.stat-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 11rem), 1fr));
    gap: 0;
    border: var(--rule);
    background: var(--paper);

    /* Der Abstand gehoert an den Kasten, wie bei .alert und .notice: Sonst
       muss ihn jede Seite, die ihn benutzt, selbst mitbringen - und die
       eine, die es vergisst, klebt am naechsten Block. */
    margin-bottom: var(--s5);
}

.stat {
    padding: var(--s5);
    border-right: var(--rule);
    display: flex;
    flex-direction: column;
    gap: var(--s1);
}

.stat:last-child { border-right: none; }

@media (max-width: 47.999rem) {
    .stat {
        border-right: none;
        border-bottom: var(--rule);
    }
    .stat:last-child { border-bottom: none; }
}

.stat__label {
    font-size: 0.6875rem;
    font-weight: 650;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ink-faint);
    font-stretch: 88%;
}

.stat__value {
    /* Eine Spur kleiner als frueher (2,75rem): In vier Kacheln nebeneinander
       braucht ein Tausenderbetrag samt Eurozeichen mehr Platz, als eine
       Viertelbreite hergibt. Zusammen mit dem geschuetzten Leerzeichen in
       InvoiceService::money() bleibt "1.234,00 EUR" damit auf einer Zeile. */
    font-size: clamp(1.75rem, 1.4rem + 1.2vw, 2.375rem);
    font-weight: 800;
    font-stretch: 108%;
    line-height: 1;
    letter-spacing: -0.03em;
}

.stat__note {
    font-size: 0.8125rem;
    color: var(--ink-faint);
}

/* --- Listen ---------------------------------------------------------------- */

.entry-list {
    list-style: none;
}

.entry {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--s4);
    padding: var(--s4) var(--s5);
    border-bottom: var(--rule);
}

.entry:last-child { border-bottom: none; }

.entry__main {
    display: flex;
    flex-direction: column;
    gap: var(--s1);
    min-width: 0;
}

.entry__sub {
    color: var(--ink-faint);
    font-size: 0.9375rem;
}

.entry__aside {
    display: flex;
    align-items: center;
    gap: var(--s3);
}

.meta-list {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));
    gap: var(--s4);
}

.meta-list dt {
    font-size: 0.6875rem;
    font-weight: 650;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ink-faint);
    font-stretch: 88%;
    margin-bottom: var(--s1);
}

.meta-list dd { font-weight: 650; }

/* --- Tabellen -------------------------------------------------------------- */

.table-responsive {
    overflow-x: auto;
    /* Eine Tabelle, die seitlich rollt, muss mit der Tastatur erreichbar
       sein - sonst kommt niemand an die rechten Spalten. */
    scrollbar-gutter: stable;
}

.table-responsive:focus-visible {
    outline: 3px solid var(--ink);
    outline-offset: 2px;
}

.table {
    width: 100%;
    border-collapse: collapse;
}

.table th,
.table td {
    padding: var(--s3) var(--s4);
    border-bottom: var(--rule);
    text-align: left;
    vertical-align: top;
}

.table th {
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ink-faint);
    font-stretch: 88%;
    border-bottom: var(--rule-strong);
    white-space: nowrap;
}

/* Rechtsbuendig muss die Spaltenregel schlagen koennen. Vorher gewann
   `.table td { text-align: left }` mit hoeherer Spezifitaet, und jede
   Betragsspalte stand still links - ein Fehler, den man nur im Bild sieht. */
.table td.text-end,
.table th.text-end {
    text-align: right;
}

.table tbody tr:hover { background: var(--paper-sunk); }

/* Die Summenzeile. Sie ist keine weitere Zeile, sondern der Schlussstrich -
   deshalb eine kraeftige Linie darueber und kein Hoverwechsel darunter. */
.table tfoot th,
.table tfoot td {
    border-top: 2px solid var(--ink);
    border-bottom: 0;
    font-weight: 650;
}

.table tfoot tr:hover { background: transparent; }

/* --- Leere Zustaende ------------------------------------------------------- */

.empty-state {
    padding: var(--s8) var(--s5);
    text-align: center;
    background: var(--paper);
    border: 1px dashed var(--paper-edge);
}

.empty-state--compact { padding: var(--s6) var(--s5); }

.empty-state__title {
    font-size: 1.125rem;
    font-weight: 700;
    margin-bottom: var(--s2);
}

.empty-state p { margin-inline: auto; }

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

.page-header {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--s4);
    margin-bottom: var(--s6);
    padding-bottom: var(--s4);
    border-bottom: var(--rule-strong);
}

.page-header__text { min-width: 0; }

.breadcrumb-simple {
    margin-bottom: var(--s4);
    font-size: 0.9375rem;
}

/* --- Navigation ------------------------------------------------------------
   Der Verein steht vorn, booxtar traegt es. Deshalb ist der Vereinsname die
   grosse Zeile und booxtar die kleine darunter - nicht umgekehrt. */

.navbar {
    background: var(--paper);
    border-bottom: var(--rule-strong);
    position: sticky;
    top: 0;
    z-index: 20;
}

.navbar-container {
    max-width: var(--page);
    margin-inline: auto;
    padding: var(--s3) var(--s5);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s4);
}

.navbar-logo {
    display: flex;
    /* Logo links, Name rechts daneben. */
    align-items: center;
    gap: var(--s2);
    text-decoration: none;
    color: var(--ink);
    min-width: 0;
}

.navbar-logo__text {
    display: flex;
    flex-direction: column;
    gap: 0.0625rem;
    min-width: 0;
}

.navbar-tenant {
    font-size: 1.0625rem;
    font-weight: 800;
    font-stretch: 106%;
    letter-spacing: -0.02em;
    line-height: 1.15;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.navbar-platform {
    font-size: 0.625rem;
    font-weight: 650;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--ink-faint);
    font-stretch: 88%;
}

/*
 * Das Vereinslogo in der Kopfzeile.
 *
 * DAS BILD WIRD EINGEPASST, NICHT GEDEHNT. Vorher stand hier nur
 * `max-height` und `width: auto` - in einem Flex-Container mit
 * `align-items: stretch` zieht das ein quadratisches Bild in die Breite und
 * drückt es auf die erlaubte Höhe zusammen. Genau das war beim ersten
 * hochgeladenen Wappen zu sehen.
 *
 * Feste Höhe, Breite nach oben begrenzt, `object-fit: contain`: Ein
 * Schriftzug bleibt breit, ein Quadrat bleibt quadratisch, und beide sind
 * gleich hoch - sonst springt die Kopfzeile je nach Verein.
 */
.navbar-logo__mark {
    display: block;
    flex: 0 0 auto;
    align-self: center;
    height: 2.25rem;
    width: auto;
    max-width: 9rem;
    object-fit: contain;
}

.navbar-nav {
    display: flex;
    align-items: center;
    gap: var(--s1);
    list-style: none;
}

.nav-link {
    display: block;
    padding: var(--s2) var(--s3);
    color: var(--ink-soft);
    font-weight: 650;
    text-decoration: none;
    border-bottom: 2px solid transparent;
    transition: color var(--fast) var(--ease),
                border-color var(--fast) var(--ease);
}

.nav-link:hover {
    color: var(--ink);
    border-bottom-color: var(--ink);
}

/* Die aktuelle Seite traegt die Vereinsfarbe UND bleibt fett - Farbe allein
   traegt keine Aussage. aria-current setzt sie, nicht eine Klasse. */
.nav-link[aria-current='page'] {
    color: var(--brand-line);
    border-bottom-color: var(--brand-line);
}

.nav-link-btn {
    border: 2px solid var(--brand-line);
    color: var(--brand-line);
    padding: var(--s2) var(--s4);
}

/* Mit dem Elementnamen davor, aus demselben Grund wie beim Sprunglink:
   `a:not(.btn):hover` zaehlt (0,2,1), `.nav-link-btn:hover` nur (0,2,0) -
   und faerbte "Registrieren" beim Ueberfahren auf --ink, waehrend sich der
   Grund gleichzeitig mit der Vereinsfarbe fuellte. Bei einem dunklen
   Vereinsblau war das Schwarz auf Dunkelblau.

   Der Knopf traegt keine .btn-Klasse, sondern ist ein Verweis im Menue -
   deshalb greift die allgemeine Regel hier ueberhaupt. */
a.nav-link-btn:hover {
    background: var(--brand);
    color: var(--brand-ink);
    border-color: var(--brand);
}

/* --- Fusszeile -------------------------------------------------------------- */

.footer {
    margin-top: var(--s9);
    padding-block: var(--s6);
    border-top: var(--rule);
    background: var(--paper);
    color: var(--ink-faint);
    font-size: 0.875rem;
}

.footer a { color: var(--ink-soft); }
.footer a:hover { color: var(--ink); }

.footer__inner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--s4);
}

.footer-legal {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s4);
    margin: 0;
}

/* --- Menue am Telefon -------------------------------------------------------
   Ueber 48rem steht die Navigation offen da; darunter klappt sie auf
   Knopfdruck auf. Der Knopf traegt aria-expanded, damit ein Bildschirmleser
   den Zustand ansagt. */

.hamburger {
    display: none;
    flex-direction: column;
    justify-content: center;
    gap: 5px;
    width: 2.75rem;
    height: 2.75rem;
    padding: 0 0.6rem;
    background: none;
    border: 2px solid var(--ink);
    border-radius: var(--radius);
    cursor: pointer;
}

.hamburger span {
    display: block;
    height: 2px;
    background: var(--ink);
    transition: transform var(--fast) var(--ease),
                opacity var(--fast) var(--ease);
}

/* Aufgeklappt wird der Knopf zum Kreuz - der Zustand ist damit auch ohne
   Farbe erkennbar. */
.hamburger[aria-expanded='true'] span:nth-child(1) {
    transform: translateY(7px) rotate(45deg);
}

.hamburger[aria-expanded='true'] span:nth-child(2) {
    opacity: 0;
}

.hamburger[aria-expanded='true'] span:nth-child(3) {
    transform: translateY(-7px) rotate(-45deg);
}

@media (max-width: 47.999rem) {
    .hamburger { display: flex; }

    /*
     * DAS TELEFONMENUE MUSS IN SICH ROLLEN KOENNEN.
     *
     * Hier stand nur `position: absolute` ohne jede Hoehenbegrenzung. Das ging
     * gut, solange das Menue kurz war - und brach in dem Moment, in dem jemand
     * mit mehreren Rollen es aufklappte: Mit Verein, Trainerbereich und Technik
     * wird die Liste ueber 1600 Pixel lang, das Fenster eines Telefons ist rund
     * 800 hoch. Der Rest hing unter dem Bildschirmrand.
     *
     * UND SEITEN-SCROLLEN HALF NICHT. Die Leiste ist `position: sticky` und
     * klebt am oberen Fensterrand; das Menue haengt als absolut positioniertes
     * Kind daran und wandert mit. Wer scrollte, bewegte die Seite unter einem
     * Menue, das stehenblieb - "Abmelden" war schlicht nicht erreichbar.
     *
     * Deshalb rollt jetzt das Menue selbst.
     *
     * `dvh` statt `vh`: Auf iOS zaehlt `vh` die Adresszeile mit, die beim
     * Scrollen verschwindet - mit `vh` waere der untere Rand des Menues genau
     * um diese Leiste zu tief. `vh` bleibt als Rueckfall fuer aeltere Browser
     * darueber stehen.
     *
     * Die 5rem sind die Hoehe der Leiste, grosszuegig gerundet (gemessen rund
     * 4,4rem). Lieber etwas zu viel abgezogen: Dann endet das Menue ein paar
     * Pixel ueber dem Fensterrand, statt eine Zeile darunter zu verschwinden.
     *
     * `overscroll-behavior: contain` haelt die Bewegung im Menue. Ohne das
     * scrollt am Ende der Liste die Seite dahinter weiter - und das fuehlt sich
     * an, als haette man danebengegriffen.
     */
    .navbar-menu {
        display: none;
        position: absolute;
        left: 0;
        right: 0;
        top: 100%;
        max-height: calc(100vh - 5rem);
        max-height: calc(100dvh - 5rem);
        overflow-y: auto;
        overscroll-behavior: contain;
        padding: var(--s4) var(--s5) var(--s5);
        background: var(--paper);
        border-bottom: var(--rule-strong);
    }

    .navbar-menu.active { display: block; }

    .navbar-container { position: relative; }

    .navbar-nav {
        flex-direction: column;
        align-items: stretch;
        gap: 0;
    }

    .navbar-nav .nav-link {
        padding: var(--s3) 0;
        border-bottom: var(--rule);
        border-left: none;
    }

    .navbar-nav .nav-link-btn {
        margin-top: var(--s3);
        border: 2px solid var(--brand-line);
        text-align: center;
    }
}

/* --- Werkzeuge -------------------------------------------------------------- */

.visually-hidden {
    position: absolute !important;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.d-block { display: block; }
.d-none  { display: none; }
.d-flex  { display: flex; }
/* Fuer den Knopf, der neben einem Verweis stehen soll - ein Formular ist
   von Haus aus ein Block und braeche die Zeile. */
.d-inline { display: inline-block; }

.mb-0 { margin-bottom: 0; }
.mb-1 { margin-bottom: var(--s1); }
.mb-2 { margin-bottom: var(--s2); }
.mb-3 { margin-bottom: var(--s3); }
.mb-4 { margin-bottom: var(--s5); }
.mb-5 { margin-bottom: var(--s6); }

.mt-2 { margin-top: var(--s2); }
.mt-3 { margin-top: var(--s3); }
.mt-4 { margin-top: var(--s5); }
.mt-5 { margin-top: var(--s6); }

.py-5 { padding-block: var(--s7); }

.align-items-end { align-items: flex-end; }
.justify-content-between { justify-content: space-between; }

/* --- Icons -----------------------------------------------------------------
   Gezeichnet, eine Strichstaerke, ein Raster. Emoji standen hier vorher als
   Icons - sie sehen auf jedem System anders aus, lassen sich nicht einfaerben
   und werden von Screenreadern vorgelesen ("Frau hebt Gewicht"). */

.icon {
    width: 1.25em;
    height: 1.25em;
    flex: none;
    stroke: currentColor;
    fill: none;
    stroke-width: 1.75;
    stroke-linecap: square;
    stroke-linejoin: miter;
    vertical-align: -0.25em;
}

/* --- Startseite ------------------------------------------------------------
   Das Plakat: Der Vereinsname steht gross im Farbfeld, breit gesetzt. Er ist
   die eine Sache, die ein Besucher nach einem Blick behalten soll. */

.home-title {
    font-size: clamp(2.75rem, 1.5rem + 5.4vw, 6rem);
    font-weight: 800;
    font-stretch: 118%;
    line-height: 0.95;
    letter-spacing: -0.035em;
    max-width: 18ch;
    color: var(--brand-ink);
}

.home-claim {
    margin-top: var(--s5);
    max-width: 42ch;
    font-size: clamp(1.125rem, 1rem + 0.6vw, 1.5rem);
}

.home-actions {
    margin-top: var(--s6);
}

/* Eine Schaltflaeche AUF der Vereinsfarbe: Umriss in der Schriftfarbe, damit
   sie auf jedem Farbton steht - ein festes Weiss oder Schwarz waere bei der
   einen Haelfte der moeglichen Vereinsfarben unsichtbar. */
/* NUR IM FARBFELD. Ausserhalb waere sie unsichtbar: --brand-ink ist bei den
   meisten Vereinsfarben Weiss, und Weiss auf Papier ist nichts. Gebunden an
   .field faellt ein falsch gesetzter Knopf auf das gewoehnliche Aussehen
   zurueck, statt zu verschwinden. */
.field .btn-on-brand {
    --btn-bg: transparent;
    --btn-fg: var(--brand-ink);
    --btn-edge: var(--brand-ink);
}

/* Beim Ueberfahren fuellt sie sich mit der Flaeche eine Stufe tiefer - NICHT
   mit der Schriftfarbe. Vorher tauschte sie Grund und Schrift: weisser Grund,
   Schrift in der Vereinsfarbe. Bei einem mittelhellen Ton (Tuerkis: 3,9:1)
   war die Beschriftung damit beim Hinzeigen schlechter lesbar als davor -
   und genau auf der Startseite, wo dieser Knopf steht. */
.field .btn-on-brand:hover {
    --btn-bg: var(--brand-hover);
    --btn-fg: var(--brand-ink);
    --btn-edge: var(--brand-ink);
}

/* Der Kopf eines Abschnitts. Mehr Raum darueber als darunter - die
   Ueberschrift gehoert zu dem, was folgt. */
.section-head {
    margin-bottom: var(--s6);
    padding-bottom: var(--s4);
    border-bottom: var(--rule-strong);
}

.section-head h2 {
    margin-bottom: var(--s2);
}

/* Ein Abschnitt, auf den ein Sprunglink zeigt, darf keinen Fokusring
   bekommen - er ist nur Ziel, nicht Bedienelement. */
#offen:focus,
#veranstaltungen:focus {
    outline: none;
}

/* --- Das Blatt (.card--sheet) ----------------------------------------------
   Fuer Karten, die in Reihen und Listen stehen: Angebots-, Veranstaltungs-
   und Mietkacheln, die Liste der Ticketverkaeufe.

   EIN BLATT, ZWEI ZONEN. Oben steht, was das Angebot ist (Art, Titel,
   Beschreibung), unten, was man wissen muss (Termin, Preis). Die untere Zone
   stand einmal auf `--paper-sunk` - und das ist zugleich der Seitengrund. In
   einer Reihe zerfiel jede Kachel in ein weisses Oben und ein graues Unten,
   das in den Hintergrund lief: Man sah nicht mehr, wo eine Kachel endet.
   Jetzt bleibt die Fusszeile weiss, und die durchgehende Haarlinie aus
   `.card-footer` teilt die Zonen. Die Kante der Kachel ist die Kante der
   weissen Flaeche.

   ZURUECKGENOMMEN (0.18.0, nach Durchsicht): ein Band in der Vereinsfarbe
   oben, die Art-Marke unten bei den Angaben, eine eingerueckte Linie, ein
   fetterer Preis. Das Band machte aus zwoelf Kacheln zwoelf farbige Balken
   und stritt mit "Nur fuer Mitglieder" und dem Hauptknopf; ohne Art-Marke
   oben standen die Titel einer Reihe auf verschiedener Hoehe. Siehe
   docs/gestaltung.md. */

.card--sheet > .card-footer {
    background: var(--paper);
}

/* GLEICHER TAKT IN DER REIHE. Jede Kachel belegt zwei Zeilen des Rasters -
   Text und Angaben - und uebernimmt sie per subgrid. Damit steht die
   Trennlinie aller Kacheln einer Reihe auf derselben Hoehe, gleich wie lang
   die Beschreibung ist. Ohne subgrid bleibt es beim Flex-Verhalten von
   .card: Fusszeile unten, Linie je Kachel - nichts bricht.
   VORAUSSETZUNG: genau zwei Kinder, .card-body und .card-footer. */
@supports (grid-template-rows: subgrid) {
    .tiles > .card--sheet,
    .card-grid > .card--sheet {
        display: grid;
        grid-row: span 2;
        grid-template-rows: subgrid;
        row-gap: 0;
    }
}

/* --- Angebotskachel --------------------------------------------------------
   Angebot, Veranstaltung und Mietobjekt teilen sich diese Kachel - ein
   Blatt (.card--sheet), die ganze Flaeche ist der Link. */

.offer-card {
    text-decoration: none;
    color: inherit;
}

.offer-card:hover .card-title {
    text-decoration: underline;
    text-decoration-thickness: 2px;
    text-underline-offset: 0.15em;
}

/* DIE VEREINSFARBE, GANZ DEZENT (Wunsch des Vereins, 0.18.0): Der Preis
   steht in ihr, und beim Ueberfahren oder Antippen faerben sich Titel und
   Rahmen. Sonst ruht die Kachel neutral - kein Band, keine Flaeche.
   `--brand-line` und nicht `--brand`: Sie ist auf Weiss als Schrift lesbar,
   auch bei einer hellen Vereinsfarbe (BrandColor::lineFor()). Der Preis traegt
   damit keine Aussage, die nur an der Farbe haengt. */
.offer-card:hover,
.offer-card:focus-visible {
    border-color: var(--brand-line);
}

.offer-card:hover .card-title,
.offer-card:focus-visible .card-title {
    color: var(--brand-line);
}

/* DIE TICKETLISTE ANTWORTET GENAUSO. Ihre Karte ist kein Link wie die
   Kachel, sondern traegt zwei: den Titel - der per `.stretched` die ganze
   Karte abdeckt - und den Knopf "Tickets", der darueber liegt
   (`.card--link .card-footer a`). In Ruhe steht der Titel in Tinte wie auf
   der Kachel; beim Ueberfahren werden Rahmen und Titel vereinsfarben, der
   Titel unterstrichen. `focus-within` statt `focus-visible`, weil der Fokus
   auf dem Link IN der Karte liegt. */
.ticket-card .card-title a {
    color: inherit;
    text-decoration: none;
}

.ticket-card:hover,
.ticket-card:focus-within {
    border-color: var(--brand-line);
}

.ticket-card:hover .card-title a,
.ticket-card:focus-within .card-title a {
    color: var(--brand-line);
    text-decoration: underline;
    text-decoration-thickness: 2px;
    text-underline-offset: 0.15em;
}

.offer-card__when {
    display: inline-flex;
    align-items: center;
    gap: var(--s2);
    font-size: 0.875rem;
    color: var(--ink-soft);
}

/* Der Preis steht IMMER in eigener Zeile, nicht neben dem Termin. Nebeneinander
   brach er bei "18,00 € · Mitglieder 12,00 €" um und bei "Kostenlos" nicht -
   in einer Reihe von Kacheln sah das aus wie zwei verschiedene Entwuerfe. */
.offer-card .card-footer__line {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--s2);
}

/* Ruhig, nicht laut: Der Preis ist eine Angabe, keine Schlagzeile. */
.offer-card__price {
    font-weight: 700;
    font-size: 1.0625rem;
    color: var(--brand-line);
}

/* --- Terminliste -----------------------------------------------------------
   Das Datum ist die Marke links. Bei einer Liste von Veranstaltungen wird
   danach gesucht, nicht nach dem Titel. */

/* KEINE eigene Linie oben. Die Liste steht immer unter einem
   `.section-head`, und der bringt seine Abschlusslinie schon mit - zwei
   starke Linien mit zwei Zentimetern Luft dazwischen sahen aus wie ein
   Fehler, und das waren sie auch. */
.event-list {
    list-style: none;
}

.event {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    gap: var(--s5);
    padding: var(--s4) 0;
    border-bottom: var(--rule);
    transition: background-color var(--fast) var(--ease);
}

.event:hover {
    background: var(--paper);
}

.event__date {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    width: 3.75rem;
    padding: var(--s2) 0;
    border: 2px solid var(--ink);
    line-height: 1;
}

.event__day {
    font-size: 1.5rem;
    font-weight: 800;
    font-stretch: 105%;
}

.event__month {
    font-size: 0.6875rem;
    font-weight: 650;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ink-soft);
}

.event__body {
    display: flex;
    flex-direction: column;
    gap: var(--s1);
    min-width: 0;
}

.event__title {
    font-size: 1.125rem;
    font-weight: 700;
    color: var(--ink);
    text-decoration: none;
}

/* Der Fangbereich deckt die ganze Zeile: am Telefon der Unterschied
   zwischen treffen und danebentippen. */
.event__title::after {
    content: '';
    position: absolute;
    inset: 0;
}

.event {
    position: relative;
}

.event__title:hover {
    text-decoration: underline;
    text-decoration-thickness: 2px;
    text-underline-offset: 0.15em;
}

.event__meta {
    font-size: 0.9375rem;
    color: var(--ink-faint);
}

.event__action {
    color: var(--ink-faint);
    transition: transform var(--fast) var(--ease), color var(--fast) var(--ease);
}

.event:hover .event__action {
    color: var(--brand-line);
    transform: translateX(0.25rem);
}

@media (max-width: 32rem) {
    .event {
        grid-template-columns: auto 1fr;
        gap: var(--s4);
    }
    .event__action { display: none; }
}

/* --- Kontoaufforderung ------------------------------------------------------
   Steht ganz zuletzt, als Antwort auf eine Frage, welche die Seite vorher
   beantwortet hat. */

.signup {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--s6);
    padding: var(--s7) var(--s6);
    background: var(--paper);
    border: var(--rule-strong);
}

.signup__text { min-width: min(100%, 28rem); flex: 1 1 28rem; }
.signup__text h2 { margin-bottom: var(--s3); }
.signup__text .lead { margin-bottom: 0; }

/* --- Symbol im Text --------------------------------------------------------- */

.icon--inline {
    width: 1em;
    height: 1em;
    vertical-align: -0.125em;
}

/* Ein Symbol in einem Abzeichen: Das Abzeichen setzt 11px Schrift, ein
   Symbol von 1em waere ebenfalls 11px - bei Strichstaerke 1.75 auf einem
   24er-Raster wird daraus ein grauer Fleck. Feste Groesse, kraeftigerer
   Strich. */
.badge .icon {
    width: 0.875rem;
    height: 0.875rem;
    stroke-width: 2.5;
    vertical-align: -0.1em;
}

/* Dasselbe fuer Symbole neben kleiner Schrift. */
.offer-card__when .icon,
.small .icon {
    width: 1rem;
    height: 1rem;
    stroke-width: 2;
}

/* In einer Schaltflaeche darf das Symbol gross bleiben - dort ist die
   Schrift gross genug, dass die Zeichnung traegt. */
.btn .icon {
    width: 1.125em;
    height: 1.125em;
    stroke-width: 2;
}

/* --- Raster, die fehlenden Spannweiten -------------------------------------
   Drei Sorten Spalte, und der Unterschied ist der Umbruchpunkt:

   .col-N     spannt IMMER N von zwoelf. Sie steht auf den dt/dd-Paaren der
              Beschreibungslisten - Beschriftung links, Wert rechts. Genau
              dort darf nichts stapeln, auch nicht am Telefon: Sonst stuende
              "Wann" allein in einer Zeile und der Termin darunter.
   .col-sm-N  ab 36rem, darunter volle Breite.
   .col-lg-N  ab 62rem, darunter volle Breite.

   .col-md-N liegt schon oben und bricht bei 48rem.

   DIE REIHENFOLGE HIER IST DIE GANZE LOGIK, nicht Geschmack: Alle Spalten
   sind gleich stark (eine Klasse), also entscheidet, wer weiter unten steht.
   Deshalb laeuft der Block von schmal nach breit - volle Breite, sm, md, lg.
   Ein <div class="col-sm-6 col-md-5"> soll bei 50rem fuenf Spalten breit
   sein; stuende sm unter md, blieben es sechs. */

/* Zuerst: alles, was einen Umbruchpunkt hat, faengt auf voller Breite an.
   Muss VOR .col-N stehen - sonst schluege es einem <dt class="col-4
   col-sm-3"> am Telefon die vier Spalten wieder aus der Hand. */
.col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6,
.col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12,
.col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6,
.col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 {
    grid-column: span 12;
}

.col-1  { grid-column: span 1; }
.col-2  { grid-column: span 2; }
.col-3  { grid-column: span 3; }
.col-4  { grid-column: span 4; }
.col-5  { grid-column: span 5; }
.col-6  { grid-column: span 6; }
.col-7  { grid-column: span 7; }
.col-8  { grid-column: span 8; }
.col-9  { grid-column: span 9; }
.col-10 { grid-column: span 10; }
.col-11 { grid-column: span 11; }
.col-12 { grid-column: span 12; }

@media (min-width: 36rem) {
    .col-sm-1  { grid-column: span 1; }
    .col-sm-2  { grid-column: span 2; }
    .col-sm-3  { grid-column: span 3; }
    .col-sm-4  { grid-column: span 4; }
    .col-sm-5  { grid-column: span 5; }
    .col-sm-6  { grid-column: span 6; }
    .col-sm-7  { grid-column: span 7; }
    .col-sm-8  { grid-column: span 8; }
    .col-sm-9  { grid-column: span 9; }
    .col-sm-10 { grid-column: span 10; }
    .col-sm-11 { grid-column: span 11; }
    .col-sm-12 { grid-column: span 12; }
}

/* .col-md-N steht schon weit oben in der Datei - also VOR .col-sm-N und damit
   an der falschen Stelle der Reihe. Hier wird es an seinem Umbruchpunkt noch
   einmal gesetzt, damit md gegen sm gewinnt. Nichts oben wird geaendert; es
   kommt nur das nach, was die Reihenfolge verlangt. */
@media (min-width: 48rem) {
    .col-md-1  { grid-column: span 1; }
    .col-md-2  { grid-column: span 2; }
    .col-md-3  { grid-column: span 3; }
    .col-md-4  { grid-column: span 4; }
    .col-md-5  { grid-column: span 5; }
    .col-md-6  { grid-column: span 6; }
    .col-md-7  { grid-column: span 7; }
    .col-md-8  { grid-column: span 8; }
    .col-md-9  { grid-column: span 9; }
    .col-md-10 { grid-column: span 10; }
    .col-md-11 { grid-column: span 11; }
    .col-md-12 { grid-column: span 12; }
}

@media (min-width: 62rem) {
    .col-lg-1  { grid-column: span 1; }
    .col-lg-2  { grid-column: span 2; }
    .col-lg-3  { grid-column: span 3; }
    .col-lg-4  { grid-column: span 4; }
    .col-lg-5  { grid-column: span 5; }
    .col-lg-6  { grid-column: span 6; }
    .col-lg-7  { grid-column: span 7; }
    .col-lg-8  { grid-column: span 8; }
    .col-lg-9  { grid-column: span 9; }
    .col-lg-10 { grid-column: span 10; }
    .col-lg-11 { grid-column: span 11; }
    .col-lg-12 { grid-column: span 12; }
}

/* Die Spalte, die nur so breit ist wie ihr Inhalt - der Knopf neben dem
   Suchfeld. Ein Zwoelferraster kennt keine Inhaltsbreite, deshalb bekommt sie
   drei Spuren zugeteilt und stellt sich darin an den Anfang: Der Knopf behaelt
   seine natuerliche Breite, statt auf eine Rasterzelle gezogen zu werden.
   Unten am Rand, damit er auf der Grundlinie des Feldes daneben sitzt. */
.col-auto { grid-column: span 12; }

@media (min-width: 48rem) {
    .col-auto {
        grid-column: span 3;
        justify-self: start;
        align-self: end;
    }
}


/* Der fehlende Zwischenschritt zwischen .g-2 und .g-4. */
.g-3 { gap: var(--s3); }

/* --- Das Aufklappmenue ------------------------------------------------------
   Der Kontoknopf in der Leiste. Zu ist der Normalfall - das JS haengt .active
   an Knopf und Liste (siehe public/js/custom.js), und erst daran klappt es
   auf. Ohne diese Regeln stand die ganze Liste immer offen unter der Leiste.

   Ueber 48rem faellt sie als Tafel ueber den Inhalt, darunter waechst sie im
   aufgeklappten Menue mit - dort ist kein Platz zum Ueberlagern. */

.nav-dropdown {
    position: relative;
}

/* Der Knopf sieht aus wie ein .nav-link, ist aber ein <button>: Rand,
   Flaeche und Schrift bringt er selbst mit und muss sie abgeben. */
.dropdown-toggle {
    display: flex;
    align-items: center;
    gap: var(--s2);
    min-height: 2.75rem;
    background: none;
    border: 0;
    border-bottom: 2px solid transparent;
    border-radius: var(--radius);
    font: inherit;
    font-weight: 650;
    cursor: pointer;
}

/* Das Dreieck zeigt nach unten, solange zu ist, und dreht sich beim
   Aufklappen. Der Zustand haengt damit an der Form, nicht an einer Farbe -
   und aria-expanded sagt ihn zusaetzlich an. */
.dropdown-toggle::after {
    content: '';
    width: 0;
    height: 0;
    border-inline: 0.3125rem solid transparent;
    border-top: 0.375rem solid currentColor;
    transition: transform var(--fast) var(--ease);
}

.dropdown-toggle.active::after {
    transform: rotate(180deg);
}

.dropdown-menu {
    display: none;
    position: absolute;
    right: 0;
    top: calc(100% + var(--s2));
    z-index: 30;
    min-width: 16rem;
    /* Die Liste wird mit jeder Rolle laenger. Auf einem kurzen Bildschirm
       muss sie in sich rollen, sonst liegt "Abmelden" unter dem Fensterrand.

       `dvh` und nicht nur `vh`: Diese Regel gilt ab 48rem Breite - und dazu
       gehoert ein quer gehaltenes Telefon (ein iPhone 14 Pro Max liegend misst
       932 Punkte). Dort zaehlt `vh` die Adressleiste mit, und der untere Rand
       der Liste laege genau um diese Leiste zu tief. `vh` bleibt als Rueckfall
       fuer aeltere Browser darueber stehen.

       `overscroll-behavior: contain` haelt die Bewegung in der Liste, statt am
       Ende die Seite dahinter weiterzuschieben. */
    max-height: calc(100vh - 6rem);
    max-height: calc(100dvh - 6rem);
    overflow-y: auto;
    overscroll-behavior: contain;
    padding-block: var(--s2);
    list-style: none;
    background: var(--paper);
    border: var(--rule-strong);
    border-radius: var(--radius);
}

.dropdown-menu.active {
    display: block;
}

/* Ein Eintrag ist eine ganze Zeile - 44px hoch, damit ein Daumen ihn trifft.
   Beim Zeigen erscheint eine Linie links; eine Flaeche allein waere in dieser
   Welt zu weich. */
.dropdown-menu a,
.dropdown-item-button {
    display: flex;
    align-items: center;
    gap: var(--s2);
    width: 100%;
    min-height: 2.75rem;
    padding: var(--s2) var(--s4);
    background: none;
    border: 0;
    border-radius: var(--radius);
    color: var(--ink);
    font: inherit;
    font-weight: 650;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
}

/* Die Flaeche hebt hervor - ein Markierungsbalken links daneben waere
   dieselbe Aussage zweimal, und zwar mit dem Mittel, das diese Welt nicht
   benutzt. */
.dropdown-menu a:hover,
.dropdown-item-button:hover {
    color: var(--ink);
    background: var(--paper-sunk);
}

/* Der Verein, in dem man gerade steht. Farbe UND Wort - wer den Blauton nicht
   sieht, liest trotzdem, welcher es ist. */
.dropdown-item-button.is-current {
    color: var(--brand-line);
    border-left-color: var(--brand-line);
}

.dropdown-item-button.is-current::after {
    content: 'aktiv';
    margin-left: auto;
    font-size: 0.6875rem;
    font-weight: 650;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    font-stretch: 88%;
}

/* Die Ueberschrift eines Abschnitts im Menue - dieselbe Stimme wie .label. */
.dropdown-label {
    padding: var(--s3) var(--s4) var(--s1);
    font-size: 0.6875rem;
    font-weight: 650;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ink-faint);
    font-stretch: 88%;
}

/* Abmelden und Vereinswechsel laufen ueber POST. Das Formular ist nur die
   Huelle und darf im Fluss der Liste nicht auffallen. */
.dropdown-form {
    display: block;
}

.dropdown-menu hr {
    height: 0;
    margin-block: var(--s2);
    border: 0;
    border-top: var(--rule);
}

@media (max-width: 47.999rem) {
    /* Im aufgeklappten Telefonmenue gibt es nichts zu ueberlagern: Die Liste
       reiht sich ein und setzt sich nur durch die Flaeche ab.

       Rollen tut hier das ganze Menue (.navbar-menu), nicht diese Liste. Darum
       muessen `overflow` und `overscroll-behavior` von oben zurueckgenommen
       werden: Eine Liste mit `overflow: auto`, die nichts zu rollen hat, aber
       `overscroll-behavior: contain` traegt, schluckt jede Wischbewegung - wer
       auf dem aufgeklappten Benutzermenue wischte, bewegte gar nichts. */
    .dropdown-menu {
        position: static;
        min-width: 0;
        max-height: none;
        overflow: visible;
        overscroll-behavior: auto;
        background: var(--paper-sunk);
        border: 0;
        border-bottom: var(--rule);
    }

    .navbar-nav .dropdown-toggle {
        width: 100%;
        justify-content: space-between;
    }
}

/* --- Schaltflaechen, die fehlenden Spielarten ------------------------------- */

/* Die zweite Wahl neben der Hauptschaltflaeche: Abbrechen, Zurueck. */
.btn-secondary {
    --btn-bg: var(--paper-sunk);
    --btn-fg: var(--ink);
    --btn-edge: var(--paper-edge);
}

.btn-success {
    --btn-bg: var(--signal-good);
    --btn-fg: var(--paper);
    --btn-edge: var(--signal-good);
}

.btn-outline-success {
    --btn-fg: var(--signal-good);
    --btn-edge: var(--signal-good);
}

.btn-outline-success:hover {
    --btn-bg: var(--signal-good);
    --btn-fg: var(--paper);
}

.btn-outline-info {
    --btn-fg: var(--signal-info);
    --btn-edge: var(--signal-info);
}

.btn-outline-info:hover {
    --btn-bg: var(--signal-info);
    --btn-fg: var(--paper);
}

/* Die randlose Nebenhandlung - "Filter zuruecksetzen". Sie behaelt die
   Knopfhoehe, damit sie in einer Reihe mit echten Knoepfen auf derselben
   Linie steht, sieht aber aus wie das, was sie ist: ein Link. */
.btn-link {
    --btn-bg: transparent;
    --btn-fg: var(--brand-line);
    --btn-edge: transparent;
    padding-inline: var(--s2);
    text-decoration: underline;
    text-underline-offset: 0.2em;
}

.btn-link:hover {
    --btn-bg: transparent;
    --btn-fg: var(--ink);
    --btn-edge: transparent;
}

/* AUF DER VEREINSFARBE GILT DEREN SCHRIFTFARBE - fuer JEDEN Knopf, der dort
 * keine eigene Regel hat.
 *
 * Ohne diese Zeilen bringt jede Spielart ihre eigene Farbe mit: Der
 * zurueckhaltende Knopf steht in Grau auf Blau (1,1:1), der randlose in der
 * Linienfarbe auf der eigenen Flaeche (1:1). Sichtbar wird das erst, wenn
 * jemand einen solchen Knopf ins Farbfeld setzt - deshalb steht die Regel
 * hier und nicht in jeder Ansicht neu.
 *
 * Ausgenommen bleiben die drei, die im Farbfeld eine eigene Antwort haben:
 * der helle Knopf, der Knopf auf der Vereinsfarbe und der Hauptknopf. */
.field .btn:not(.btn-light):not(.btn-on-brand):not(.btn-primary) {
    --btn-bg: transparent;
    --btn-fg: var(--brand-ink);
    --btn-edge: var(--brand-ink);
}

.field .btn:not(.btn-light):not(.btn-on-brand):not(.btn-primary):hover {
    --btn-bg: var(--brand-hover);
    --btn-fg: var(--brand-ink);
    --btn-edge: var(--brand-ink);
}

/* Aneinandergereihte Knoepfe. Die 2px-Raender stossen zusammen und werden auf
   einen zusammengezogen - sonst stuende zwischen zwei Knoepfen eine 4px-Naht. */
.btn-group {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: stretch;
}

.btn-group .btn {
    position: relative;
}

.btn-group > * + * {
    margin-left: -2px;
}

/* Der beruehrte Knopf muss mit seinem ganzen Rand vor dem Nachbarn liegen. */
.btn-group .btn:hover,
.btn-group .btn:focus-visible {
    z-index: 1;
}

.btn-group-sm .btn {
    padding: var(--s2) var(--s3);
    min-height: 2.25rem;
    font-size: 0.875rem;
}

/* Das Kreuz, das einen Hinweis oder einen Dialog schliesst. Kein .btn: Es
   traegt keinen Rand und keine Flaeche, nur die Trefferflaeche von 44px. */
.btn-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 2.75rem;
    height: 2.75rem;
    padding: 0;
    background: none;
    border: 0;
    border-radius: var(--radius);
    color: inherit;
    font: inherit;
    cursor: pointer;
}

/* Das Kreuz ist gezeichnet, nicht gesetzt: Es kommt aus Support\Icons wie
   jedes andere Symbol, damit es genau eine Strichstaerke im Haus gibt. */
.btn-close .icon {
    width: 1.125rem;
    height: 1.125rem;
    stroke-width: 2;
    vertical-align: 0;
}

.btn-close:hover {
    background: color-mix(in srgb, var(--ink) 10%, transparent);
}

/* --- Tabellen, die fehlenden Spielarten ------------------------------------- */

/* Die Zeilenhervorhebung steht schon auf .table. Hier steht sie noch einmal,
   damit die Klasse auch dann haelt, was ihr Name verspricht, wenn jemand sie
   ohne .table verwendet. */
.table-hover tbody tr:hover {
    background: var(--paper-sunk);
}

/* Der Kopf einer Tabelle bekommt seine eigene Flaeche. */
.table-light th,
.table-light td {
    background: var(--paper-sunk);
}

.table-sm th,
.table-sm td {
    padding: var(--s2) var(--s3);
}

/* Eine Zeile, die Aufmerksamkeit braucht - etwa eine faellige Pruefung der
   Mitgliedschaft. Die Flaeche allein traegt das nicht: Wer Farben nicht
   unterscheidet, sieht trotzdem den Balken links, den die uebrigen Zeilen
   nicht haben. */
.table-warning > td,
.table-warning > th {
    background: var(--field-warn);
}

/* Die Flaeche allein reicht - ein Akzentbalken an der Kante ist genau das
   Muster, das diese Welt nicht kennt. Damit die Zeile nicht nur ueber Farbe
   spricht, traegt sie in der Ansicht zusaetzlich ein Wort. */
.table-warning > td:first-child {
    font-weight: 650;
}

/* Zeilen mit Abzeichen und Knoepfen lesen sich mittig ausgerichtet besser als
   oben buendig. Zwei Klassen tief, damit die Zellenregel von .table faellt. */
.table.align-middle th,
.table.align-middle td {
    vertical-align: middle;
}

/* --- Hinweise, die fehlenden Teile ------------------------------------------ */

/* Der Streifen unter der Leiste, in dem die Meldungen stehen. */
.flash-area {
    display: flex;
    flex-direction: column;
    gap: var(--s3);
    margin-top: var(--s5);
}

/* Ein Hinweis mit Schliessknopf: Zeichen, Text, Kreuz in einer Zeile. Das
   Kreuz ist 44px hoch, deshalb faellt der Innenabstand oben und unten
   kleiner aus - sonst waere die Meldung doppelt so hoch wie noetig. */
.alert-dismissible {
    display: flex;
    align-items: center;
    gap: var(--s3);
    padding-block: var(--s2);
    padding-right: var(--s2);
}

.alert-dismissible > :not(.alert-icon):not(.btn-close) {
    flex: 1 1 auto;
}

.alert-dismissible .btn-close {
    margin-left: auto;
}

/* Das Zeichen vor der Meldung. Es behaelt die Signalfarbe des Hinweises,
   waehrend der Text auf Tinte steht - und weil je Art ein anderes Zeichen
   darin steht, traegt nicht die Farbe die Aussage, sondern das Zeichen. */
.alert-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 1.5rem;
    height: 1.5rem;
    border: 2px solid currentColor;
    border-radius: var(--radius);
    color: inherit;
    font-size: 0.875rem;
    font-weight: 700;
    line-height: 1;
}

/* Das Symbol im Feld. Feste Groesse statt em, sonst stoesst es an die Kante
   des 1.5rem-Rahmens - und kraeftigerer Strich, weil es klein steht. */
.alert-icon .icon {
    width: 0.875rem;
    height: 0.875rem;
    stroke-width: 2.5;
    vertical-align: 0;
}

/* Ein Link im Hinweis. Er nimmt die Signalfarbe und ist unterstrichen - auf
   der getoenten Flaeche ist das Vereinsblau zu leise. */
.alert .alert-link {
    color: inherit;
    font-weight: 650;
    text-decoration: underline;
    text-underline-offset: 0.2em;
}

/* --- Der Scanner ------------------------------------------------------------
   Einlasskontrolle: im Halbdunkeln, im Stehen, am Telefon. Die Rueckmeldung
   muss aus zwei Metern lesbar sein, deshalb ist sie hier groesser als jede
   Ueberschrift der Seite. Die Zustaende setzt public/js/scanner.js als
   scan-result--ok / --warn / --bad. */

.scanner {
    margin-top: var(--s4);
}

/* Das Kamerabild erscheint erst, wenn die Kamera laeuft (.is-scanning setzt
   scanner.js auf den Wurzelknoten). Vorher stuende hier ein schwarzes
   Rechteck ohne Inhalt. */
.scanner__video {
    display: none;
    width: 100%;
    aspect-ratio: 4 / 3;
    object-fit: cover;
    background: var(--ink);
    border: var(--rule-strong);
    border-radius: var(--radius);
}

.is-scanning .scanner__video {
    display: block;
}

.scan-result {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: var(--s2);
    padding: var(--s5);
    margin-bottom: var(--s4);
    /* Ohne Mindesthoehe springt die Seite bei jedem Scan, weil "Bereit"
       kuerzer ist als "Bereits entwertet". */
    min-height: 8rem;
    background: var(--paper);
    border: var(--rule);
    /* Der Rahmen ringsum in der Signalfarbe, nicht ein Balken oben: aus zwei
       Metern ist eine ganze Kante deutlicher als ein Streifen an einer
       Seite - und es bleibt beim Prinzip Flaeche und Linie. */
    border-color: currentColor;
    border-width: 2px;
    border-radius: var(--radius);
    color: var(--ink-soft);
}

.scan-result__headline {
    display: flex;
    align-items: center;
    gap: var(--s3);
    font-size: clamp(1.75rem, 1.1rem + 3.2vw, 3rem);
    font-weight: 800;
    font-stretch: 108%;
    line-height: 1.05;
    letter-spacing: -0.03em;
    color: inherit;
    text-wrap: balance;
}

.scan-result__detail {
    font-size: clamp(1rem, 0.9rem + 0.6vw, 1.25rem);
    /* Die Zustandsflaechen sind helle Papiertoene, kein farbiger Grund -
       darauf traegt die weiche Tinte mit deutlichem Abstand zur Grenze. */
    color: var(--ink-soft);
}

.scan-result--ok {
    color: var(--signal-good);
    background: var(--field-good);
}

.scan-result--warn {
    color: var(--signal-warn);
    background: var(--field-warn);
}

.scan-result--bad {
    color: var(--signal-bad);
    background: var(--field-bad);
}

/* Je Ausgang ein anderes Zeichen im Kasten. Am Eingang wird der Bildschirm
   nur gestreift - das Zeichen ist schneller erfasst als der Satz, und es
   traegt den Zustand auch dann, wenn die Farbe nicht ankommt. */
.scan-result--ok .scan-result__headline::before,
.scan-result--warn .scan-result__headline::before,
.scan-result--bad .scan-result__headline::before {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 1em;
    height: 1em;
    border: 0.08em solid currentColor;
    border-radius: var(--radius);
    font-size: 0.75em;
    line-height: 1;
}

.scan-result--ok .scan-result__headline::before   { content: '\2713'; }
.scan-result--warn .scan-result__headline::before { content: '!'; }
.scan-result--bad .scan-result__headline::before  { content: '\00d7'; }

/* --- Fachliche Bausteine ---------------------------------------------------- */

/* Kacheln gleicher Hoehe fuer Anmeldungen, Kinder, Angebote. Enger gefasst
   als .tiles, weil hier ganze Karten mit Angaben und Knoepfen stehen. */
.card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 22rem), 1fr));
    gap: var(--s5);
    align-items: stretch;
}

/* Eine ganze Karte als Link - die Verwaltungskacheln auf der Startseite.
   Die Flaeche ist das Ziel, nicht die Ueberschrift. */
.card-link {
    color: var(--ink);
    text-decoration: none;
    transition: border-color var(--fast) var(--ease);
}

.card-link:hover {
    color: var(--ink);
    border-color: var(--ink);
}

/* Kopfzeile einer Anmeldekarte: Titel links, Zustandsabzeichen rechts. */
.booking-card__head {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--s3);
    margin-bottom: var(--s4);
}

/* Vergangenes tritt zurueck - ueber die Flaeche, nicht ueber Transparenz.
   Auch Abgelaufenes muss lesbar bleiben. Dass etwas vergangen ist, sagt
   ausserdem die Ueberschrift des Abschnitts, in dem es steht. */
.booking-card--past {
    background: var(--paper-sunk);
}

/* Die Handlungen am Fuss einer Karte, durch eine Linie abgesetzt. */
.card-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s3);
    margin-top: var(--s4);
    padding-top: var(--s4);
    border-top: var(--rule);
}

/* Eine Frage im Katalog. Die Nummer davor macht aus dem Stapel eine Liste -
   vorher tat das eine dicke Linie links, also der Akzentbalken, den diese
   Welt nicht kennt. Eine Zaehlung sagt ohnehin mehr: Sie nennt die
   Reihenfolge, statt sie nur anzudeuten. */
.question-card {
    counter-increment: frage;
}

/* Der Zaehler muss dort beginnen, wo die Liste beginnt - sonst zaehlt er
   ueber die ganze Seite weiter. */
.stack {
    counter-reset: frage;
}

.question-card::before {
    content: counter(frage);
    display: block;
    margin-bottom: var(--s2);
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    color: var(--ink-faint);
}

/* Eine Frage im ausgefuellten Fragebogen. Jede steht fuer sich, getrennt
   durch eine Linie statt durch Abstand allein. */
.question-field {
    padding-bottom: var(--s5);
    margin-bottom: var(--s5);
    border-bottom: var(--rule);
}

.question-field:last-of-type {
    padding-bottom: 0;
    margin-bottom: 0;
    border-bottom: none;
}

/* Ja/Nein nebeneinander statt untereinander - zwei Wahlmoeglichkeiten
   brauchen keine zwei Zeilen. */
.radio-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s3) var(--s5);
}

/* Mehrfachauswahl: so viele Spalten, wie in die Breite passen. */
.check-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 14rem), 1fr));
    gap: var(--s2) var(--s4);
}

/* "Fuer wen anmelden?" - ein Feld mit Rand, damit die Auswahl als Block
   erkennbar bleibt und nicht mit dem uebrigen Formular verlaeuft. */
.participant-choice {
    padding: var(--s4);
    margin-bottom: var(--s5);
    background: var(--paper-sunk);
    border: var(--rule);
    border-radius: var(--radius);
}

/* Die gegebenen Antworten. Anders als .meta-list ist diese Liste flach
   (dt und dd sind Geschwister), deshalb ordnet das Raster sie selbst:
   Frage links, Antwort rechts, ab 36rem. */
.answer-list {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--s1) var(--s5);
}

.answer-list dt {
    font-size: 0.6875rem;
    font-weight: 650;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ink-faint);
    font-stretch: 88%;
}

.answer-list dd {
    margin-bottom: var(--s3);
}

.answer-list dd:last-of-type {
    margin-bottom: 0;
}

@media (min-width: 36rem) {
    .answer-list {
        grid-template-columns: minmax(8rem, 14rem) 1fr;
    }

    .answer-list dt {
        /* Neben der Antwort braucht die kleine Beschriftung deren
           Grundlinie, nicht die Oberkante der Zelle. */
        padding-top: 0.25em;
    }

    .answer-list dt,
    .answer-list dd {
        padding-bottom: var(--s3);
        margin-bottom: 0;
        border-bottom: var(--rule);
    }

    .answer-list dt:last-of-type,
    .answer-list dd:last-of-type {
        padding-bottom: 0;
        border-bottom: none;
    }
}

/* Der Titel einer Zeile in einer .entry-list. Tinte statt Vereinsfarbe: In
   einer Liste aus zwanzig Zeilen waere zwanzigmal Blau ein Farbfeld. */
.entry__title {
    color: var(--ink);
    font-weight: 700;
    text-decoration: none;
}

.entry__title:hover {
    color: var(--ink);
    text-decoration: underline;
}

/* Der Fangbereich einer Karte, deren Ueberschrift der Link ist: Er deckt die
   ganze Karte ab. Die Karte braucht dafuer einen Bezugspunkt - und bekommt
   ihn nur dann, wenn wirklich so ein Link darin steht. */
.card:has(.stretched-link) {
    position: relative;
}

.stretched-link::after {
    content: '';
    position: absolute;
    inset: 0;
}

/* Dasselbe für eine Tabellenzeile: Die ganze Zeile öffnet den Eintrag
   (Mitgliederliste). Der Link bleibt der Name - für Tastatur und
   Bildschirmleser ändert sich nichts. */
.table tr:has(.stretched-link) {
    position: relative;
    cursor: pointer;
}

/* Die Vorschau einer E-Mail. Feste Laufweite, weil im Text Platzhalter und
   Einrueckungen stehen, die sonst wandern. */
.mail-preview {
    padding: var(--s4);
    background: var(--paper-sunk);
    border: var(--rule);
    border-radius: var(--radius);
    font-family: ui-monospace, 'Cascadia Mono', 'Segoe UI Mono', Menlo, monospace;
    font-size: 0.875rem;
    line-height: 1.5;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

/* Rechtstexte: langer Fliesstext, den jemand wirklich liest. Deshalb eine
   begrenzte Zeilenlaenge und Luft zwischen den Abschnitten - das ist die
   einzige Stelle im Portal, an der ein ganzer Aufsatz steht. */
.legal-text {
    max-width: var(--measure);
}

.legal-text h2,
.legal-text h3 {
    margin-top: var(--s6);
    margin-bottom: var(--s3);
}

.legal-text h2:first-child,
.legal-text h3:first-child {
    margin-top: 0;
}

.legal-text p {
    margin-bottom: var(--s4);
}

.legal-text ul,
.legal-text ol {
    margin-bottom: var(--s4);
    padding-left: var(--s5);
}

.legal-text li {
    margin-bottom: var(--s2);
}

/* Eine Aufzaehlung im Fliesstext. Der Grundreset nimmt jeder Liste ihre
   Einrueckung - hier bekommt sie sie zurueck, samt Punkten. */
.plain-list {
    padding-left: var(--s5);
    max-width: var(--measure);
}

.plain-list li + li {
    margin-top: var(--s2);
}

.list-unstyled {
    list-style: none;
}

/* --- Kleinkram -------------------------------------------------------------- */

.d-grid { display: grid; }
.h-100  { height: 100%; }

.flex-wrap     { flex-wrap: wrap; }
.flex-shrink-0 { flex-shrink: 0; }

/* min-width: 0 gehoert dazu: Ein Flex-Kind waechst sonst mit seinem laengsten
   Wort ueber die Zeile hinaus, statt umzubrechen. */
.flex-grow-1 {
    flex-grow: 1;
    min-width: 0;
}

.gap-2 { gap: var(--s2); }
.gap-3 { gap: var(--s3); }

.align-items-center { align-items: center; }
.align-items-start  { align-items: flex-start; }
.justify-content-center { justify-content: center; }

@media (min-width: 48rem) {
    .d-md-flex { display: flex; }
    .justify-content-md-between { justify-content: space-between; }
    .text-md-end { text-align: right; }
}

.fs-5 { font-size: 1.25rem; }
.fw-normal { font-weight: 400; }

.text-success { color: var(--signal-good); }
.text-danger  { color: var(--signal-bad); }
.text-dark    { color: var(--ink); }

/* In Bootstrap war bg-warning eine gelbe FLAECHE, und text-dark sorgte dort
   fuer lesbare Schrift darauf. Hier faerbt bg-* nur Schrift und Rand -
   text-dark naehme dem Abzeichen sein Signal und machte aus "Warteliste" ein
   beliebiges schwarzes Kaestchen. Im Abzeichen bleibt die Signalfarbe. */
.badge.bg-warning.text-dark {
    color: var(--signal-warn);
}

/* Das neutrale Abzeichen - eine Nummer, keine Aussage. Wie die uebrigen
   bg-* faerbt es Schrift und Rand, nicht die Flaeche. */
.bg-light {
    color: var(--ink-soft);
}

.text-decoration-none { text-decoration: none; }

/* Diese Welt kennt keinen Schatten. .shadow steht noch in den Anmeldeseiten
   und bekommt deshalb, was an seine Stelle tritt: eine Linie. */
.shadow {
    box-shadow: none;
    border: var(--rule-strong);
}

/* Bootstrap dimmte hier die ganze Karte auf 75 Prozent. Auf --ink-faint
   heisst das 2.9:1 und damit unlesbar - die Zusage, dass Fliesstext 4.5:1
   haelt, gilt auch fuer Vergangenes. Zurueckgenommen wird deshalb ueber die
   Flaeche, genau wie bei .booking-card--past. */
.opacity-75 {
    background: var(--paper-sunk);
}

/* Ein farbiger Rand um eine Karte. Beide Stellen nennen den Grund zusaetzlich
   im Text - "aktiv" beim eigenen Verein, "Aus Verein entfernen" ueber dem
   Knopf. Der Rand allein traegt die Aussage also nicht. */
.border-primary { border-color: var(--brand-line); }
.border-danger  { border-color: var(--signal-bad); }

.mt-0 { margin-top: 0; }
.mt-1 { margin-top: var(--s1); }
.my-4 { margin-block: var(--s5); }
.ms-2 { margin-left: var(--s2); }

.p-4  { padding: var(--s5); }
.p-5  { padding: var(--s7); }
.py-2 { padding-block: var(--s2); }
.py-3 { padding-block: var(--s3); }

/* Ein kleineres Feld bleibt 2.75rem hoch - die Trefferflaeche ist keine
   Geschmacksfrage. Kleiner werden nur Schrift und Abstand. */
.form-control-sm {
    padding: var(--s2) var(--s3);
    font-size: 0.875rem;
}

/* --- Das gegliederte Angebotsformular ---------------------------------------
   views/offers/form.php teilt einundzwanzig Felder in drei offene Abschnitte
   und fuenf Klappen. Die Klappen sind <details>, nicht JavaScript - deshalb
   steht hier auch nur Aussehen, kein Zustand.

   DIE ZUSAMMENFASSUNG IN DER KLAPPENZEILE IST DER KERN dieser Seite: Sie
   sagt zugeklappt, was drinsteht. Sie darf deshalb nie so leise werden, dass
   man sie ueberliest - --ink-faint ist die hellste erlaubte Stufe. */

.form-section {
    padding-top: var(--s6);
    margin-bottom: var(--s6);
    border-top: var(--rule-strong);
}

.form-section:first-of-type {
    padding-top: 0;
    border-top: none;
}

/* Ein Abschnittstitel ist keine Seitenueberschrift. Ohne diese Zeile stuende
   "Was kostet es?" in 2.5rem und schriee die Frage, die es beantwortet. */
.form-section__title {
    font-size: 1.25rem;
    font-stretch: 100%;
    margin-bottom: var(--s5);
}

/* Der Pflichtstern. Das Feld traegt zusaetzlich required - der Stern ist der
   sichtbare Hinweis, nicht die Zusage. */
.form-required {
    color: var(--signal-bad);
    font-weight: 700;
}

/* Eine Zahl braucht keine ganze Zeile. */
.form-control-narrow {
    max-width: 10rem;
}

/* Die Einheit steht IM Feld am rechten Rand, nicht darunter: Sonst liest man
   erst den Betrag, dann den Hilfstext, und das Euro-Zeichen dazwischen wird
   fuer eine Beschriftung gehalten. aria-hidden ist es schon in der Ansicht -
   vorgelesen wird die Einheit ueber den Hilfstext. */
.input-affix {
    position: relative;
    display: flex;
    align-items: center;
}

.input-affix .form-control {
    padding-right: var(--s7);
}

.input-affix__unit {
    position: absolute;
    right: var(--s3);
    color: var(--ink-faint);
    font-weight: 650;
    /* Der Klick muss ins Feld gehen, nicht auf die Einheit. */
    pointer-events: none;
}

.form-more__title {
    font-size: 1.25rem;
    font-stretch: 100%;
    margin-top: var(--s7);
    margin-bottom: var(--s2);
}

.form-more__lead {
    color: var(--ink-soft);
    margin-bottom: var(--s4);
}

.form-fold {
    margin-bottom: var(--s3);
    background: var(--paper);
    border: var(--rule);
    border-radius: var(--radius);
}

/* Offen wird der Rand kraeftiger. Der Zustand haengt aber nicht daran: Der
   Inhalt steht sichtbar da, das Dreieck zeigt nach unten, und <details> sagt
   von sich aus "erweitert" beziehungsweise "reduziert". */
.form-fold[open] {
    border-color: var(--ink);
}

.form-fold__summary {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s2) var(--s3);
    min-height: 2.75rem;
    padding: var(--s3) var(--s4);
    cursor: pointer;
    /* Das Standarddreieck des Browsers sieht auf jedem System anders aus.
       Beide Zeilen braucht es: die eine fuer WebKit, die andere fuer den Rest. */
    list-style: none;
}

.form-fold__summary::-webkit-details-marker {
    display: none;
}

.form-fold__summary::before {
    content: '';
    flex: none;
    width: 0;
    height: 0;
    border-block: 0.3125rem solid transparent;
    border-left: 0.375rem solid currentColor;
    transition: transform var(--fast) var(--ease);
}

.form-fold[open] > .form-fold__summary::before {
    transform: rotate(90deg);
}

.form-fold__label {
    font-weight: 700;
}

/* Rechts in derselben Zeile - so weit vom Titel weg, wie die Breite hergibt,
   und bei schmaler Breite darunter. */
.form-fold__state {
    margin-left: auto;
    font-size: 0.875rem;
    color: var(--ink-faint);
    text-align: right;
}

.form-fold__body {
    padding: var(--s5) var(--s4) var(--s4);
    border-top: var(--rule);
}

/* Der Schalter, der ueber Sichtbarkeit entscheidet. Er steht abgesetzt, weil
   er die einzige Angabe im Formular ist, die nach dem Speichern sofort
   jemand anders betrifft. */
.form-publish {
    padding: var(--s4);
    margin-top: var(--s6);
    background: var(--paper-sunk);
    border: var(--rule);
    border-radius: var(--radius);
}

/* --- Fehlerseite -------------------------------------------------------------
   Steht ohne Layout und ohne Datenbank: Sie muss auch dann tragen, wenn die
   Datenbank der Grund fuer den Fehler ist. Deshalb keine Vereinsfarbe. */

.error-code {
    font-size: clamp(4rem, 3rem + 6vw, 8rem);
    font-weight: 800;
    font-stretch: 118%;
    line-height: 0.9;
    letter-spacing: -0.04em;
    color: var(--ink);
    margin-bottom: var(--s4);
}

/* --- Dialog ------------------------------------------------------------------
   Das native <dialog>. Es bringt Fokusfalle, Escape und Hintergrundsperre
   selbst mit - deshalb steht hier nur das Aussehen und kein Verhalten.

   Vorher hingen diese Dialoge an `data-bs-toggle`, einem Merkmal aus einem
   Framework, das dieses Projekt nie eingebunden hat. Sie liessen sich gar
   nicht oeffnen. */

.dialog {
    width: min(100% - 2rem, 34rem);
    padding: 0;
    background: var(--paper);
    color: var(--ink);
    border: var(--rule-strong);
    border-radius: var(--radius);
}

.dialog::backdrop {
    background: rgb(20 22 26 / 0.55);
}

.dialog__head {
    padding: var(--s5);
    border-bottom: var(--rule-strong);
}

.dialog__body {
    padding: var(--s5);
}

.dialog__foot {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s3);
    padding: var(--s4) var(--s5);
    border-top: var(--rule);
    background: var(--paper-sunk);
}

/* Der neue Wert in einem Aenderungsantrag. Er stand auf `var(--success)` -
   eine Marke, die es in dieser Datei nie gab, also erbte er schlicht die
   Textfarbe. Jetzt die Signalfarbe, und fett dazu: Farbe allein traegt die
   Aussage nicht. */
.value-new {
    color: var(--signal-good);
    font-weight: 700;
}

/* --- Trennlinie ---------------------------------------------------------------
   Die Voreinstellung des Browsers ist eine gemeisselte Rille mit Verlauf -
   die gehoert nicht in eine Welt aus geraden Kanten. */

hr {
    height: 0;
    border: 0;
    border-top: var(--rule);
    margin-block: var(--s5);
}


/* --- Ueberschrift in einer Formularkarte ---------------------------------------
   Die Seitenueberschrift skaliert bis 3.75rem - das ist die Groesse fuer eine
   ganze Seitenbreite. In einer Karte von 32rem brach damit schon
   "Kostenlos registrieren" auf zwei Zeilen. */

.card h1 {
    font-size: clamp(1.75rem, 1.4rem + 1.2vw, 2.25rem);
    font-stretch: 106%;
}

/* --- Aufklapper ohne eigene Klasse ---------------------------------------------
   Ein blankes <details> - etwa "Bestaetigungsmail nicht erhalten?" unter dem
   Anmeldefeld - bekam das Dreieck des Browsers und stand nackt im Raum.
   Dieselbe Sprache wie die Klappen im Angebotsformular, nur schlichter. */

details:not(.form-fold, .display-switch) > summary {
    display: flex;
    align-items: center;
    gap: var(--s2);
    padding: var(--s2) 0;
    min-height: 2.75rem;
    cursor: pointer;
    list-style: none;
    font-weight: 650;
    color: var(--ink-soft);
}

details:not(.form-fold, .display-switch) > summary::-webkit-details-marker { display: none; }
details:not(.form-fold, .display-switch) > summary::marker { content: ''; }

details:not(.form-fold, .display-switch) > summary::before {
    content: '';
    width: 0.5rem;
    height: 0.5rem;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: rotate(-45deg);
    transition: transform var(--fast) var(--ease);
    flex: none;
}

details:not(.form-fold, .display-switch)[open] > summary::before {
    transform: rotate(45deg);
}

details:not(.form-fold, .display-switch) > summary:hover {
    color: var(--ink);
}

/* --- Zentrierte Zeilen ---------------------------------------------------------
   `justify-content-center` stammt aus einem Flexbox-Raster: Dort schob es
   eine schmale Spalte in die Mitte. Dieses Raster ist ein CSS-Grid, und eine
   Spalte mit `span 5` beginnt darin immer bei Spalte eins - das Anmeldefeld
   klebte links.

   Hier steht deshalb wieder Flexbox. Ein erster Versuch rechnete die
   Spaltenbreite im Grid nach; das war zu clever, und es funktionierte nicht.
   Fuer "ein Kasten, mittig" ist Flexbox das richtige Werkzeug. */

.row.justify-content-center {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
}

.row.justify-content-center > [class*='col-'] {
    /* Eine Formularspalte hat eine sinnvolle Breite, unabhaengig davon, wie
       viele Zwoelftel jemand einmal notiert hat: Fuenf von zwoelf ergaben
       rund 470 px, und darin brach schon die Ueberschrift des
       Registrierformulars um. */
    flex: 0 1 auto;
    width: min(100%, 34rem);
}

/* Die Ausnahmen: Wo mehr Platz gebraucht wird, steht es auch da. */
.row.justify-content-center > .col-md-8,
.row.justify-content-center > .col-lg-7,
.row.justify-content-center > .col-lg-8 {
    width: min(100%, 46rem);
}

/* --- Hinweis neben einem Kontrollkaestchen -------------------------------------
   `.form-check` ist ein Raster aus zwei Spalten: Kaestchen, dann Beschriftung.
   Steht der erklaerende Satz als GESCHWISTER des Labels statt darin, faellt
   er in die erste Spalte zurueck - und weil er lang ist, zieht er die
   Kaestchenspalte auf mehrere hundert Pixel. Das Ergebnis: ein einsames
   Kaestchen links, die Beschriftung dreizeilig weit rechts.

   Beide Schreibweisen kommen in den Ansichten vor. Statt vier Dateien
   umzubauen, raeumt die Regel den Fall hier ab - und deckt damit auch den
   naechsten ab. */
.form-check > .form-text,
.form-check > small {
    grid-column: 2;
    margin-top: var(--s1);
}

/* --- Farbwahl ------------------------------------------------------------------
   Die Auswahl ist ein Kreis Farbfelder. Ein Radiofeld liegt darunter und ist
   fuer die Maus unsichtbar, fuer die Tastatur aber vollstaendig da - deshalb
   `visually-hidden` und nicht `display: none`. */

.swatches {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 8rem), 1fr));
    gap: var(--s3);
}

.swatch__input {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.swatch__label {
    display: flex;
    align-items: center;
    gap: var(--s3);
    padding: var(--s2) var(--s3);
    min-height: 2.75rem;
    border: 2px solid var(--paper-edge);
    cursor: pointer;
    transition: border-color var(--fast) var(--ease);
}

.swatch__label:hover {
    border-color: var(--ink-faint);
}

/* Die Wahl steht in der Kante UND im Gewicht - Farbe allein traegt sie
   nicht, und das Farbfeld selbst ist ja ohnehin bunt. */
.swatch__input:checked + .swatch__label {
    border-color: var(--ink);
    font-weight: 700;
}

.swatch__input:focus-visible + .swatch__label {
    outline: 3px solid var(--ink);
    outline-offset: 2px;
}

.swatch__chip {
    width: 1.5rem;
    height: 1.5rem;
    flex: none;
    /* Eine Haarlinie, damit ein sehr helles Feld nicht mit dem Papier
       verschwimmt. */
    border: 1px solid rgb(20 22 26 / 0.2);
}

.swatch__name {
    font-size: 0.9375rem;
}

.own-color {
    display: flex;
    gap: var(--s3);
    align-items: stretch;
}

.own-color__picker {
    width: 3.5rem;
    min-height: 2.75rem;
    padding: 0.25rem;
    background: var(--paper);
    border: 1px solid var(--ink-faint);
    border-radius: var(--radius);
    cursor: pointer;
}

.own-color__hex {
    max-width: 10rem;
    font-variant-numeric: tabular-nums;
    text-transform: uppercase;
}

/* --- Vorschau der Vereinsfarbe -------------------------------------------------
   Zwei Gruende, warum sie hier steht: Eine Farbe zu waehlen, ohne zu sehen,
   wie Schaltflaechen und Verweise damit aussehen, waere Raten - und die
   Zahlen darunter sind die eigentliche Zusage, dass die Seite lesbar bleibt. */

.preview {
    border: var(--rule);
}

.preview__field {
    padding: var(--s6);
    /* Farben setzt das Skript - hier stehen nur die Ausgangswerte fuer den
       Fall, dass es nicht laeuft. */
    background: var(--brand);
    color: var(--brand-ink);
}

.preview__title {
    font-size: 1.75rem;
    font-weight: 800;
    font-stretch: 112%;
    line-height: 1.1;
    letter-spacing: -0.02em;
}

.preview__claim {
    margin-top: var(--s2);
    margin-bottom: var(--s5);
    opacity: 0.85;
}

.preview__paper {
    padding: var(--s6);
    background: var(--paper);
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s3);
}

.preview__paper p {
    flex: 1 1 100%;
    margin-bottom: 0;
}

/* Kein echter Knopf, nur sein Aussehen: In einem Formular waere ein zweiter
   Knopf ein zweites Ziel fuer die Eingabetaste. */
.preview__btn {
    display: inline-flex;
    align-items: center;
    padding: 0.6875rem var(--s5);
    min-height: 2.75rem;
    font-weight: 650;
    background: var(--brand-ink);
    color: var(--brand-line);
}

.preview__btn--solid {
    background: var(--brand);
    color: var(--brand-ink);
}

/* --- Fortschritt beim Rundmail-Versand ------------------------------------- */

/* DIE OERTLICHE BOOTSTRAP-FASSUNG BRINGT DIESE KOMPONENTE NICHT MIT. Sie ist
   auf die tatsaechlich benutzten Teile beschnitten, und der Fortschrittsbalken
   war bis zur Rundmail keiner davon.

   Ohne diese Regeln stand er unsichtbar da: null Pixel hoch, ohne Flaeche.
   Sichtbar waren nur die Zahlen darunter - und waehrend zweihundert
   Nachrichten hinausgehen, will man sehen, dass sich etwas bewegt, nicht
   eine Zeile lesen. */
.progress {
    height: 0.5rem;
    background: var(--paper-sunk);
    border: 1px solid var(--paper-edge);
    overflow: hidden;
}

.progress-bar {
    height: 100%;
    background: var(--brand);
    /* Die Breite setzt die Seite: beim Aufbau serverseitig, waehrend des
       Versands aus dem gemeldeten Stand. Der Uebergang macht aus dem Sprung
       je Haeppchen eine Bewegung. */
    transition: width 0.3s ease;
}

/* --- Belegungskalender der Vermietung --------------------------------------

   EIN MONATSRASTER IST EINE TABELLE, und es sieht hier auch so aus: Linien
   statt Karten, Kanten gerade, keine Schatten. Dieselbe Welt wie der Rest.

   FARBE IST NIE DER EINZIGE TRAEGER (Grundsatz 4 im Kopf dieser Datei). Ein
   vergebener Tag ist nicht nur rot, sondern zugleich schraffiert und -
   dort, wo Platz ist - beschriftet. Wer die Farben nicht unterscheidet,
   sieht trotzdem, was los ist.

   @see docs/vermietung.md */

.rental-calendar {
    border: 1px solid var(--paper-edge);
    margin-bottom: var(--s5);
}

.rental-calendar__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s3);
    padding: var(--s3) var(--s4);
    border-bottom: 1px solid var(--paper-edge);
}

.rental-calendar__title {
    margin: 0;
    text-align: center;
}

.rental-calendar__grid {
    width: 100%;
    border-collapse: collapse;
    table-layout: fixed;
}

.rental-calendar__grid th {
    padding: var(--s2);
    font-size: 0.75rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-faint);
    border-bottom: 1px solid var(--paper-edge);
}

.rental-calendar__grid th abbr {
    text-decoration: none;
    border: 0;
}

.cal-day {
    /* Quadratisch genug, dass der Monat als Flaeche lesbar bleibt - aber mit
       Luft nach unten, wo Zeitfenster daruntersitzen. */
    height: 4.5rem;
    padding: var(--s1);
    vertical-align: top;
    border: 1px solid var(--paper-edge);
}

.cal-day--outside {
    background: var(--paper-sunk);
    color: var(--ink-faint);
}

.cal-day--today {
    /* Zwei Linien statt einer Farbe: Der heutige Tag ist eine Position, keine
       Aussage ueber die Belegung. */
    outline: 2px solid var(--ink);
    outline-offset: -2px;
}

.cal-day__number {
    display: block;
    font-size: 0.875rem;
    font-weight: 700;
    line-height: 1.2;
}

.cal-day__button {
    display: block;
    width: 100%;
    min-height: 3.5rem;
    padding: var(--s1);
    text-align: left;
    background: none;
    border: 0;
    color: inherit;
    font: inherit;
    cursor: pointer;
}

.cal-day__button:hover {
    background: var(--paper-sunk);
}

.cal-day__button:focus-visible {
    outline: 2px solid var(--brand-line);
    outline-offset: -2px;
}

.cal-day__who,
.cal-day__state {
    display: block;
    margin-top: var(--s1);
    font-size: 0.6875rem;
    line-height: 1.25;
    overflow: hidden;
}

.cal-day__state {
    color: var(--ink-faint);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

/* Die drei Zustaende. Die Schraffur traegt die Aussage mit - siehe oben. */
.cal-day--pending {
    background: var(--field-warn);
    background-image: repeating-linear-gradient(
        45deg,
        transparent,
        transparent 6px,
        rgba(138, 80, 0, 0.14) 6px,
        rgba(138, 80, 0, 0.14) 12px
    );
}

.cal-day--taken {
    background: var(--field-bad);
    background-image: repeating-linear-gradient(
        45deg,
        transparent,
        transparent 4px,
        rgba(172, 31, 33, 0.18) 4px,
        rgba(172, 31, 33, 0.18) 8px
    );
}

/* Frei, aber ausserhalb von Vorlauf und Horizont: kein Angebot, aber auch
   keine Belegung. Deshalb blass und ohne Schraffur. */
.cal-day--closed {
    background: var(--paper-sunk);
    color: var(--ink-faint);
}

/* Die Zeitfenster eines Tages. */
.cal-slot {
    display: block;
    width: 100%;
    margin-top: 2px;
    padding: 2px var(--s1);
    font-size: 0.6875rem;
    font-weight: 600;
    line-height: 1.3;
    text-align: center;
    text-decoration: none;
    border: 1px solid currentColor;
    background: none;
    font-family: inherit;
    cursor: default;
}

.cal-slot--free {
    color: var(--signal-good);
    cursor: pointer;
}

.cal-slot--free:hover {
    background: var(--field-good);
}

.cal-slot--pending {
    color: var(--signal-warn);
    background: var(--field-warn);
}

.cal-slot--taken {
    color: var(--signal-bad);
    background: var(--field-bad);
    /* Durchgestrichen, damit die Aussage nicht allein an der Farbe haengt. */
    text-decoration: line-through;
}

.cal-slot:focus-visible {
    outline: 2px solid var(--brand-line);
    outline-offset: 1px;
}

/* Was zuletzt angeklickt wurde. Eine Flaeche in der Vereinsfarbe - der
   einzige Ort im Kalender, an dem sie auftritt. */
.is-picked,
.cal-day__button.is-picked {
    background: var(--brand);
    color: var(--brand-ink);
}

.rental-calendar__legend {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s2);
    margin: 0;
    padding: var(--s3) var(--s4);
    border-top: 1px solid var(--paper-edge);
}

.legend-key {
    display: inline-block;
    width: 1rem;
    height: 1rem;
    border: 1px solid var(--paper-edge);
    vertical-align: middle;
}

.legend-key--free {
    background: var(--paper);
}

.legend-key--pending {
    background: var(--field-warn);
    background-image: repeating-linear-gradient(
        45deg,
        transparent,
        transparent 3px,
        rgba(138, 80, 0, 0.2) 3px,
        rgba(138, 80, 0, 0.2) 6px
    );
}

.legend-key--taken {
    background: var(--field-bad);
    background-image: repeating-linear-gradient(
        45deg,
        transparent,
        transparent 2px,
        rgba(172, 31, 33, 0.25) 2px,
        rgba(172, 31, 33, 0.25) 4px
    );
}

/* Auf dem Handy wird aus dem Raster eine engere Fassung: Die Zahl bleibt,
   die Beschriftung weicht. Sieben Spalten muessen sieben Spalten bleiben -
   ein Monat, der umbricht, ist kein Monat mehr. */
@media (max-width: 640px) {
    .cal-day {
        height: 3.25rem;
        padding: 0;
    }

    .cal-day__who {
        display: none;
    }

    .cal-slot {
        font-size: 0.5625rem;
        padding: 1px;
    }
}

/* Ein freies Fenster an einem Tag, den niemand mehr buchen kann: weder
   Angebot noch Belegung. Blass und ohne Rahmenfarbe - es steht nur noch da,
   damit die Woche ihre Form behaelt. */
.cal-slot--closed {
    color: var(--ink-faint);
    border-color: var(--paper-edge);
}

/* --- Der Kalender als Werkzeug ---------------------------------------------

   Zwei Zustände, die es vorher nicht gab: Der Kalender holt sich einen
   anderen Monat, und eine einzelne Zelle wartet auf ihre Antwort. Beide
   sollen SICHTBAR, aber nicht laut sein - wer acht Termine hintereinander
   zumacht, will nicht achtmal etwas blinken sehen. */

.rental-calendar__quick {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--s3);
    padding: var(--s3) var(--s4);
    border-bottom: 1px solid var(--paper-edge);
    background: var(--paper-sunk);
}

/* Waehrend ein Monat geholt wird: blass und unklickbar. Kein Ersetzen durch
   einen Platzhalter - das Raster soll stehenbleiben, damit die Seite nicht
   springt. */
.rental-calendar.is-loading {
    opacity: 0.55;
    pointer-events: none;
}

/* Eine einzelne Zelle, die auf ihre Antwort wartet. */
.is-busy {
    opacity: 0.5;
    pointer-events: none;
}

/* Im Schnellmodus ist auch die belegte Zelle ein Ziel - das soll man sehen. */
.rental-calendar [data-block-id] {
    cursor: pointer;
}

/* --- Vermietung: Bestätigen und Verlauf -------------------------------------

   Die Bestätigungsseite soll sich lesen wie ein Beleg: was frei wird ganz
   oben, darunter die Fakten als Liste, dann Grund und Häkchen. Kein Kasten
   um alles - die Seite IST der Dialog. */

.rental-confirm__lead {
    margin-bottom: var(--s4);
}

.rental-confirm__facts {
    display: grid;
    grid-template-columns: minmax(8rem, max-content) 1fr;
    gap: var(--s2) var(--s4);
    margin: 0;
    padding: var(--s4) 0;
    border-top: var(--rule);
    border-bottom: var(--rule);
}

.rental-confirm__facts dt {
    font-weight: 600;
}

.rental-confirm__facts dd {
    margin: 0;
    overflow-wrap: anywhere;
}

/* Auf dem Handy untereinander - zwei Spalten mit einer E-Mail-Adresse
   passen nicht auf 375 Pixel. */
@media (max-width: 575.98px) {
    .rental-confirm__facts {
        grid-template-columns: 1fr;
        gap: 0 var(--s4);
    }

    .rental-confirm__facts dd {
        margin-bottom: var(--s2);
    }
}

.rental-confirm__check {
    padding: var(--s3) var(--s4);
    background: var(--field-warn);
    border-left: 3px solid var(--signal-warn);
}

.rental-confirm__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s3);
}

/* Der Verlauf: eine schlichte Liste, jüngstes oben. Der Knopf steht rechts
   daneben und bricht auf schmalen Schirmen darunter. */
.rental-history {
    list-style: none;
    margin: 0;
    padding: 0;
    border-top: var(--rule);
}

.rental-history__item {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--s2) var(--s4);
    padding: var(--s3) 0;
    border-bottom: var(--rule);
}

.rental-history__main {
    display: flex;
    flex-direction: column;
    gap: var(--s1);
    min-width: 0;
    flex: 1 1 16rem;
}

.rental-history__text {
    font-weight: 600;
    overflow-wrap: anywhere;
}

.rental-history__meta,
.rental-history__reason {
    color: var(--ink-soft);
    font-size: 0.875rem;
    overflow-wrap: anywhere;
}

/* --- Nichts ragt über den Schirm hinaus -------------------------------------

   DAS SYMPTOM: Auf dem Handy liess sich die Seite plötzlich nach links und
   rechts schieben. Die Navigation stand dann halb draussen, und wer zurück
   wollte, wischte erst einmal quer.

   DIE URSACHEN WAREN IMMER DIESELBEN ZWEI:

   1. EIN LANGES WORT. "Vereinsmeisterschaften", "Mehrzweckveranstaltungs-
      halle", eine E-Mail-Adresse - ein Wort, das breiter ist als der
      Bildschirm, schiebt alles mit sich. Bei einem Titel von 66 Zeichen
      wurde die Seite 1445 Pixel breit statt 375.
   2. EINE REIHE, DIE NICHT UMBRICHT. Zwei Knöpfe nebeneinander brauchen 341
      Pixel; der Bildschirm gibt 327 her. Ohne Umbruch steht der zweite
      draussen.

   WARUM NICHT `overflow-x: hidden` AM BODY: Das ist das Pflaster über der
   Anzeige. Der Inhalt bleibt zu breit, nur sieht man ihn nicht mehr - und
   ein Knopf, der ausserhalb liegt, ist nicht anklickbar, sondern weg.
   Repariert wird der Umbruch, nicht die Sichtbarkeit.

   Die Regeln gelten AUF JEDER BREITE. Ein Wort, das nicht passt, passt auf
   dem grossen Schirm genauso wenig - dort fällt es nur seltener auf. */

/* Überschriften dürfen notfalls mitten im Wort brechen. `hyphens` setzt
   dabei einen Trennstrich, wo die Sprache ihn erlaubt - `<html lang="de">`
   steht im Seitenkopf, der Browser weiss also, wie deutsche Wörter
   getrennt werden. */
h1, h2, h3, h4, h5, h6,
.card-title,
.entry__title,
.home-title,
.navbar-tenant {
    overflow-wrap: anywhere;
    hyphens: auto;
}

/* Fliesstext und alles, was Namen, Adressen oder Verweise trägt. `break-word`
   statt `anywhere`: Hier wird erst dann im Wort gebrochen, wenn die Zeile es
   wirklich verlangt - der Zeilenfall bleibt ruhiger. */
p, li, dd, dt, td, th, a, label, summary, figcaption,
.entry, .entry__sub, .badge, .form-text, .card-text, .notice__body, .alert {
    overflow-wrap: break-word;
}

/* AN TABELLENZELLEN AUSDRÜCKLICH NUR `break-word` (siehe Liste oben), nicht
   `anywhere`. Der Unterschied entscheidet, was bei Platzmangel passiert:
   `anywhere` rechnet den Wortbruch schon in die Mindestbreite ein, die
   Tabelle quetscht sich so schmal wie möglich - und aus "01.10.2026" werden
   zwei Zeilen. `break-word` bricht erst, wenn ein Wort allein nicht passt;
   bis dahin bleibt die Tabelle breit und ihr Rahmen rollt seitlich. In einer
   Tabelle ist Rollen die bessere Antwort als zerrissene Datumsangaben. */

/* Bilder und eingebettete Inhalte bleiben in ihrem Kasten. */
img, svg, video, iframe, canvas {
    max-width: 100%;
}

@media (max-width: 47.999rem) {
    /* JEDE REIHE BRICHT UM. `.d-flex` ist die Hilfsklasse für "nebeneinander",
       und auf einem Bildschirm von 360 Pixeln ist "nebeneinander" oft schon
       zu zweit zu viel. Wer nebeneinander braucht, hat es auf dem grossen
       Schirm - hier zählt, dass nichts hinausragt. */
    .d-flex {
        flex-wrap: wrap;
    }

    /* UND JEDES FLEX-KIND DARF SCHRUMPFEN. Das ist die zweite Hälfte der
       Sache: Eine Gruppe mit `flex-shrink-0` bleibt so breit, wie ihr Inhalt
       nebeneinander braucht - 388 Pixel bei zwei Knöpfen - und ihre Kinder
       kommen gar nicht erst in die Lage, umbrechen zu müssen. Auf dem
       grossen Schirm ist `flex-shrink-0` richtig: Dort soll der Knopf neben
       dem Text stehen bleiben und nicht zerquetscht werden. Hier ist kein
       Platz, neben dem etwas stehen könnte.

       `min-width: 0` dazu, weil ein Flex-Kind sonst nicht unter die Breite
       seines längsten Wortes geht - die Voreinstellung `min-width: auto` ist
       der häufigste Grund, warum Umbruchregeln wirkungslos aussehen. */
    .flex-shrink-0 {
        flex-shrink: 1;
    }

    .d-flex > * {
        min-width: 0;
    }

    /* Ein Knopf, der nicht schrumpfen darf, darf wenigstens nicht breiter
       sein als der Schirm. */
    .btn,
    .badge {
        max-width: 100%;
    }
}

/* --- Umschalter zwischen zwei Sichten desselben Bereichs -------------------
   "Zu prüfen" und "Abgabestand" sind dieselbe Arbeit der Abteilungsleitung
   aus zwei Richtungen. Kein Menüpunkt mehr, sondern zwei Reiter über der
   Seite - wer den einen sieht, soll den anderen finden. */

.view-switch {
    display: flex;
    flex-wrap: wrap;
    gap: 0;
    border-bottom: var(--rule);
}

.view-switch__item {
    padding: var(--s2) var(--s4);
    color: var(--ink-soft);
    text-decoration: none;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
}

.view-switch__item:hover { color: var(--ink); }

.view-switch__item.is-active {
    color: var(--ink);
    font-weight: 650;
    border-bottom-color: var(--brand);
}

/* --- Aufklappbares unter einer Listenzeile --------------------------------
   Die Ausnahme von der Abgabefrist braucht ein kleines Formular. In der
   Zeile selbst wäre es Lärm bei siebzig Trainern; es klappt deshalb unter
   ihr auf und nimmt dann die volle Breite. */

.entry__aside {
    flex-wrap: wrap;
}

.entry__more {
    flex-basis: 100%;
    font-size: 0.9375rem;
}

.entry__form {
    margin-top: var(--s3);
    padding: var(--s4);
    background: var(--paper-sunk);
    border: var(--rule);
}

/* --- Gruppenkopf mit Zwischensumme ------------------------------------------
   Die Buchhaltung schliesst Abteilung für Abteilung ab. Die Summe steht
   deshalb am Kopf jeder Gruppe, nicht erst ganz unten. */

.group-head {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--s2);
}

/* Die Summe bleibt rechts, auch wenn ein langer Abteilungsname sie in die
   zweite Zeile schiebt - dort stünde sie sonst links unter dem Namen. */
.group-head__sum {
    margin-left: auto;
    text-align: right;
}

/* Der Kopf über den Karten (der Zeitraum) steht ausserhalb ihres Rahmens.
   Er rückt um Innenabstand und Rahmen der Karte ein, damit seine Summe
   bündig über der Betragsspalte darunter steht. */
.group-head--outside {
    padding-right: calc(var(--s5) + 1px);
}

/* --- Beträge ganz rechts ----------------------------------------------------
   In Trainerabrechnung und Auslagen steht der Betrag jeder Zeile als eigenes
   Element am rechten Rand: rechtsbündig, tabellare Ziffern, feste
   Mindestbreite. Untereinander bilden die Beträge so eine Spalte, die man
   von oben nach unten liest. Vorher stand er mitten im Satz unter dem Namen
   („2,00 Std. · 28,00 €“) und musste in jeder Zeile gesucht werden.

   Die Zeile wird dafür ein Raster: Inhalt | Zustand und Knöpfe | Betrag.
   Die Mitte als `fit-content(50%)` und nicht `auto`: Eine `auto`-Spalte
   wächst erst auf ihre volle Breite, bevor der Inhalt etwas bekommt - drei
   Abzeichen hätten den Namen sonst auf null gedrückt. */

.entry--amount {
    display: grid;
    grid-template-columns: minmax(0, 1fr) fit-content(50%) auto;
}

/* Was unter der Zeile aufklappt, nimmt die volle Breite. */
.entry--amount > .entry__more {
    grid-column: 1 / -1;
}

.entry__amount,
.amount {
    font-variant-numeric: tabular-nums;
    font-weight: 650;
    color: var(--ink);
    text-align: right;
    white-space: nowrap;
}

/* Immer die letzte Spalte - auch in einer Zeile ohne Zustand und Knöpfe. */
.entry__amount {
    grid-column: -2;
    min-width: 6.5rem;
}

@media (max-width: 35.999rem) {
    /* Auf dem Telefon bleibt der Betrag oben rechts neben dem Namen; Zustand
       und Knöpfe rücken in eine eigene Zeile darunter. */
    .entry--amount {
        grid-template-columns: minmax(0, 1fr) auto;
    }

    .entry--amount > .entry__amount {
        grid-column: 2;
        grid-row: 1;
        align-self: start;
    }

    .entry--amount > .entry__aside {
        grid-column: 1 / -1;
        grid-row: 2;
    }

    /* Eine Zeile ohne Betrag (Abgabestand: noch nichts eingereicht) gibt
       ihren Platz dem Namen. */
    .entry--amount > .entry__amount:empty {
        display: none;
    }
}

/* Häkchen und Name auf einer Linie - das Häkchen gehört zur Person. */
.entry__pick {
    display: flex;
    align-items: center;
    gap: var(--s3);
}

.entry__pick .form-check-input {
    margin: 0;
    flex-shrink: 0;
}

/* Der Teilbetrag steht unter seiner Auswahl, eingerueckt auf die Hoehe der
   Beschriftung (Kaestchen 1,375rem + Abstand). */
.donation-amount {
    display: flex;
    align-items: center;
    gap: var(--s2);
    margin: var(--s1) 0 var(--s2) calc(1.375rem + var(--s3));
}

.donation-amount .form-control {
    width: 8rem;
}

/* --- Ferien, Feiertage, Schliesszeiten --------------------------------------
   DEZENT: ein Hauch Farbe und ein Wort, kein Alarm. Viele Gruppen pausieren
   in den Ferien, manche trainieren gerade dann - die Markierung sagt nur,
   dass man hinsehen sollte. Nicht auf dem Blatt (PDF). */

.holiday-row,
tr.holiday-row > td {
    background: color-mix(in srgb, var(--signal-info) 7%, transparent);
}

.holiday-note {
    display: inline-block;
    font-size: 0.8125rem;
    color: var(--signal-info);
    white-space: nowrap;
}

.holiday-note::before {
    content: "";
    display: inline-block;
    width: 0.5rem;
    height: 0.5rem;
    margin-right: var(--s1);
    border-radius: var(--radius-round);
    background: var(--signal-info);
    opacity: 0.55;
    vertical-align: 0.05em;
}

/* Überschneidung: gebaut wie der Ferien-Hinweis, aber rot - eine Person kann
   nicht zwei Gruppen zugleich trainieren. Der Text darf umbrechen: Er nennt
   die andere Stunde samt Abteilung. Steht eine Stunde in den Ferien UND
   überschneidet sich, gewinnt der rote Grund. */
.overlap-row,
tr.overlap-row > td {
    background: color-mix(in srgb, var(--signal-bad) 6%, transparent);
}

.overlap-note {
    display: inline-block;
    font-size: 0.8125rem;
    color: var(--signal-bad);
}

.overlap-note::before {
    content: "";
    display: inline-block;
    width: 0.5rem;
    height: 0.5rem;
    margin-right: var(--s1);
    border-radius: var(--radius-round);
    background: var(--signal-bad);
    vertical-align: 0.05em;
}

/* --- Einstellungsseite: Gruppen, Karten, Sprungleiste ----------------------
   Dreizehn Einstellungen auf einer Seite. Damit sie lesbar bleibt: je
   Einstellung eine Karte (Titel, Erklärung, Felder, Knopf im Kartenfuss),
   die Karten in vier Gruppen mit Plakatzeile, oben eine Sprungleiste.
   Vorher trennte `.form-section` - und `:first-of-type` nahm die Linie
   jedem Abschnitt weg, der als erster in seinem Formular stand. */

.settings-nav {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--s2) var(--s5);
    padding: 0 0 var(--s3);
    margin-bottom: var(--s7);
    border-bottom: var(--rule);
}

.settings-nav__label {
    font-size: 0.6875rem;
    font-weight: 650;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ink-faint);
}

.settings-nav a {
    color: var(--ink);
    font-weight: 600;
    text-decoration: none;
    border-bottom: 2px solid transparent;
}

.settings-nav a:hover,
.settings-nav a:focus-visible {
    border-bottom-color: var(--brand);
}

.settings-group {
    scroll-margin-top: var(--s5);
}

.settings-group + .settings-group {
    margin-top: var(--s8);
}

/* Die Plakatzeile: dicke Linie, darunter klein und versal - wie die
   Beschriftung der Kennzahlen. Gruppe, nicht Überschrift zum Lesen. */
.settings-group__title {
    font-size: 0.75rem;
    font-weight: 700;
    font-stretch: 88%;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    padding-top: var(--s3);
    margin-bottom: var(--s5);
    border-top: var(--rule-strong);
}

.settings-card {
    scroll-margin-top: var(--s5);
}

.settings-card + .settings-card {
    margin-top: var(--s5);
}

.settings-card > .card-body {
    padding: var(--s6);
}

.settings-card > .card-footer {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s3);
    padding: var(--s4) var(--s6);
}

@media (max-width: 47.999rem) {
    .settings-card > .card-body { padding: var(--s5); }
    .settings-card > .card-footer { padding: var(--s4) var(--s5); }
}

.settings-card__title {
    font-size: 1.25rem;
    font-stretch: 100%;
    margin-bottom: var(--s2);
}

.settings-card__lead {
    color: var(--ink-soft);
    max-width: 65ch;
    margin-bottom: var(--s5);
}

/* Ein zweiter Teil derselben Karte - etwa der Zeitraum unter den Sätzen. */
.settings-card__part {
    margin-top: var(--s6);
    padding-top: var(--s5);
    border-top: var(--rule);
}

.settings-card__part-title {
    font-size: 1rem;
    font-weight: 700;
    margin-bottom: var(--s3);
}

/* Felder nebeneinander, UNTEN BÜNDIG: Eine zweizeilige Beschriftung schiebt
   sonst ihr Feld tiefer als das daneben. */
.field-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 11rem), 1fr));
    gap: var(--s4) var(--s5);
    align-items: end;
}

.field-grid--account {
    grid-template-columns: 1fr;
}

@media (min-width: 48rem) {
    /* Inhaber und IBAN brauchen Platz, die BIC hat höchstens elf Zeichen. */
    .field-grid--account {
        grid-template-columns: 5fr 5fr 3fr;
    }
}

/* Eine Abfolge von Werten untereinander - die drei Erinnerungen. */
.offset-list {
    display: grid;
    gap: var(--s3);
}

.offset-list__row {
    display: grid;
    grid-template-columns: minmax(12rem, 16rem) auto;
    justify-content: start;
    align-items: center;
    gap: var(--s2) var(--s5);
}

.offset-list__tone {
    display: block;
    font-weight: 400;
    color: var(--ink-faint);
    font-size: 0.875rem;
}

@media (max-width: 47.999rem) {
    .offset-list__row {
        grid-template-columns: 1fr;
    }
}

/* Zahl mit Einheit dahinter: "7 Tage", "12,00 €". Das Feld so breit wie
   die Zahl, die Einheit liest sich mit. */
.input-suffix {
    display: flex;
    align-items: center;
    gap: var(--s2);
    color: var(--ink-soft);
}

.input-suffix .form-control {
    flex: 0 0 auto;
    width: 6.5rem;
}

.settings-select {
    max-width: 22rem;
}

.settings-logo img {
    display: block;
    max-height: 80px;
    max-width: 100%;
    margin-bottom: var(--s2);
}

.settings-list {
    border: var(--rule);
}

/* Eine Zeile zum Hinzufügen im Kartenfuss - Felder und Knopf bündig.
   Doppelt benannt, weil der Kartenfuss sonst als Flexzeile gewinnt. */
.settings-card > .settings-add {
    display: grid;
    grid-template-columns: minmax(12rem, 2fr) repeat(2, minmax(9rem, 1fr)) auto;
    gap: var(--s3) var(--s4);
    align-items: end;
}

.settings-add .form-label {
    margin-bottom: var(--s1);
}

/* Datumsfelder bringen eine eigene Mindestbreite mit; ohne diese Zeilen
   laufen sie auf dem Telefon über den Kartenrand. */
.settings-add > * {
    min-width: 0;
}

.settings-add .form-control {
    width: 100%;
    min-width: 0;
}

@media (max-width: 47.999rem) {
    .settings-card > .settings-add {
        grid-template-columns: 1fr 1fr;
    }

    .settings-add__name {
        grid-column: 1 / -1;
    }
}

/* --- Termine abhaken (Trainerabrechnung) -------------------------------------
   Das Telefon ist der Regelfall. Deshalb: ganze Zeile antippbar, Datum
   vorn in fester Spalte, nach Kalenderwochen gegliedert, und EIN Knopf, der
   unten am Bildschirmrand haftet - egal, wie lang die Liste ist. */

.pick {
    background: var(--paper);
    border: var(--rule);
}

.pick__tools {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s2);
    padding: var(--s3) var(--s4);
    border-bottom: var(--rule);
}

.pick__tools[hidden] {
    display: none;
}

.pick__tools-label {
    font-size: 0.6875rem;
    font-weight: 650;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ink-faint);
    margin-right: var(--s1);
}

.pick__week {
    border: 0;
    margin: 0;
    padding: 0;
    min-width: 0;
}

.pick__week-title {
    display: block;
    width: 100%;
    float: left; /* sonst sitzt die Legende im Rahmen statt darin */
    padding: var(--s2) var(--s4);
    background: var(--paper-sunk);
    border-bottom: var(--rule);
    font-size: 0.75rem;
    font-weight: 650;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-soft);
}

.pick__week-title + * {
    clear: both;
}

.pick__row {
    display: grid;
    grid-template-columns: auto 4.75rem minmax(0, 1fr);
    align-items: start;
    gap: var(--s3);
    min-height: 3.25rem;
    padding: var(--s3) var(--s4);
    border-bottom: var(--rule);
    cursor: pointer;
    transition: background-color var(--fast) var(--ease);
}

.pick__row:hover {
    background: var(--paper-sunk);
}

.pick__row:has(.pick__box:checked) {
    background: color-mix(in srgb, var(--brand) 7%, var(--paper));
}

.pick__row:has(.pick__box:focus-visible) {
    outline: 2px solid var(--brand);
    outline-offset: -2px;
}

.pick__row.is-taken {
    cursor: default;
    color: var(--ink-faint);
}

.pick__row.is-taken:hover {
    background: transparent;
}

.pick__box {
    margin-top: 0.0625rem;
}

.pick__done {
    display: inline-flex;
    width: 1.375rem;
    justify-content: center;
    color: var(--signal-good);
}

.pick__date {
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    padding-top: 0.0625rem;
}

.pick__body {
    display: flex;
    flex-direction: column;
    gap: 0.125rem;
    min-width: 0;
}

.pick__title {
    font-weight: 600;
    overflow-wrap: anywhere;
}

.pick__meta {
    font-size: 0.875rem;
    color: var(--ink-faint);
}

.pick__row.is-taken .pick__title {
    font-weight: 400;
}

/* Haftet unten: Der Knopf bleibt in Reichweite des Daumens. Trennung durch
   die starke Linie, nicht durch Schatten - wie überall. */
.pick__bar {
    position: sticky;
    bottom: 0;
    z-index: 5;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--s3);
    padding: var(--s3) var(--s4);
    background: var(--paper);
    border-top: var(--rule-strong);
}

.pick__count {
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

@media (max-width: 47.999rem) {
    .pick__bar .btn {
        flex: 1 1 auto;
    }
}

/* --- Buchhaltung: Unterlagen für die Kassenprüfung --------------------------
   Eine Leiste über der Liste „Ausgezahlt“: links, was drin ist, rechts die
   Downloads. Auf dem Telefon untereinander. */

.export-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--s3);
    padding: var(--s3) var(--s4);
    background: var(--paper-sunk);
    border: var(--rule);
    border-radius: var(--radius);
}

.export-bar__label {
    display: flex;
    flex-direction: column;
}

.export-bar__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s2);
}

.export-links {
    color: var(--ink-faint);
    white-space: nowrap;
}

/* --- Eingetragene Stunden als Liste ----------------------------------------- */

/* Aufklappbar wie "Termine abhaken": Die Liste trägt keinen eigenen Rahmen,
   den gibt der Aufklappbereich; die Summe steht als Fußzeile darunter. */
.hours-fold .entry-list {
    border-top: var(--rule);
}

/* Seitlich wie die Zeilen darüber (.entry), damit die Summe bündig unter
   der Betragsspalte steht. */
.hours-fold .hours__total {
    padding: var(--s3) var(--s5);
    border-top: var(--rule-strong);
}

.hours-fold .form-fold__state {
    font-variant-numeric: tabular-nums;
}

.hours__aside {
    flex-wrap: nowrap;
}

.hours__total {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--s3);
    font-variant-numeric: tabular-nums;
}

.hours__total > span:last-child {
    display: flex;
    gap: var(--s3);
    align-items: baseline;
}

@media (max-width: 47.999rem) {
    /* Auf dem Telefon: Stunden und Knopf in einer Zeile unter dem Termin,
       der Betrag bleibt oben rechts (.entry--amount). */
    .hours__row .entry__aside {
        width: 100%;
        justify-content: space-between;
    }
}

/* Der Ferienhinweis bricht in der Abhakliste um - sonst schiebt er auf dem
   Telefon die ganze Liste über den Rand. */
.pick__body .holiday-note {
    white-space: normal;
}

@media (max-width: 47.999rem) {
    .pick__tools-label {
        display: none;
    }

    .pick__tools .btn {
        flex: 1 1 0;
        white-space: nowrap;
        padding-inline: var(--s2);
    }

    /* Der Knopf sagt schon, wie viele es sind - die Zeile daneben bleibt
       nur für Vorleseprogramme (aria-live). */
    .pick__count {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip: rect(0 0 0 0);
        white-space: nowrap;
    }
}

/* --- Termine abhaken als Aufklappbereich ------------------------------------
   Derselbe Baustein wie "Stunde von Hand eintragen". Die Liste selbst trägt
   keinen eigenen Rahmen mehr - den gibt der Aufklappbereich. */

.pick-fold__title {
    margin: 0;
    font-size: 1.0625rem;
    font-stretch: 100%;
}

.pick-fold__intro {
    padding: 0 var(--s4) var(--s3);
}

.pick-fold .pick {
    border: 0;
    border-top: var(--rule);
}

/* Zugeklappt braucht der Bereich Abstand zur Stundenliste darunter - die
   Randabstandsregel des Aufklappbausteins ist für gestapelte Bereiche gedacht. */
.form-fold.pick-fold {
    margin-bottom: var(--s6);
}

/* --- Anfragen (Anmeldung mit Zusage) ----------------------------------------
   Das Häkchen gehört zur Person - wie in der Abhakliste der Abrechnung.
   Gesammelt zugesagt wird im Kartenfuss, einzeln in der Zeile. */

.request-row__pick {
    display: flex;
    align-items: center;
    gap: var(--s3);
    cursor: pointer;
}

.request-row__pick .form-check-input {
    margin: 0;
    flex-shrink: 0;
}

.request-group__bulk {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--s3);
}

/* Hinweis über dem Anmeldeknopf eines Angebots mit Zusage. */
.approval-note {
    padding: var(--s3) var(--s4);
    margin: var(--s3) 0;
    background: var(--field-warn);
    border-left: 3px solid var(--signal-warn);
    font-size: 0.9375rem;
}

/* --- Auslagen: das Belegfoto -------------------------------------------------
   Klein im Formular (nur zur Kontrolle, ob es das richtige ist), gross in der
   Ansicht - dort wird der Betrag darauf gelesen. Nie breiter als die Seite. */

.receipt-thumb {
    display: block;
    max-width: 100%;
    max-height: 12rem;
    border: 1px solid var(--paper-edge);
    background: var(--paper-sunk);
}

.receipt-photo {
    display: block;
    max-width: 100%;
    max-height: 40rem;
    border: 1px solid var(--paper-edge);
    background: var(--paper-sunk);
}

/* Mehrere Positionen auf dem Telefon: Ohne Trennlinie liest sich Zeile 2
   wie die Fortsetzung von Zeile 1. */
.expense-item + .expense-item {
    border-top: 1px solid var(--paper-edge);
    padding-top: var(--s3);
    margin-top: var(--s3);
}

/* Buchhaltung Auslagen: je Person eine Zwischenzeile mit der Summe - so wird
   überwiesen, und so liest die Buchhaltung die Liste. */
tr.table-group-row > th,
tr.table-group-row > td {
    background: var(--paper-sunk);
    border-top: 2px solid var(--paper-edge);
    /* Ein Name, keine Spaltenüberschrift. */
    text-transform: none;
    letter-spacing: normal;
    font-size: 1rem;
    font-weight: 700;
    color: var(--ink);
}

/* --- Dokumente: das Symbol für den Dateityp ---------------------------------
   PDF rot, Word blau, Excel grün, PowerPoint orange - die Farben aus dem Büro.
   Das Kürzel steht zusätzlich im Band: Farbe allein genügt nicht. Die Farben
   sind feste Dateityp-Farben und folgen bewusst NICHT der Vereinsfarbe - sonst
   sähe beim roten Verein jedes Dokument wie ein PDF aus. */

.file-icon {
    display: block;
    width: 2.25rem;
    height: 2.75rem;
    flex: none;
}

.file-icon__sheet { fill: var(--paper); stroke: var(--ink-faint); stroke-width: 1.2; }
.file-icon__fold { fill: none; stroke: var(--ink-faint); stroke-width: 1.2; }
.file-icon__band { fill: #5b6470; }
.file-icon__text { fill: #fff; font-family: Helvetica, Arial, sans-serif; font-weight: 700; letter-spacing: 0.02em; }

.file-icon--pdf .file-icon__band { fill: #c62828; }
.file-icon--word .file-icon__band { fill: #1f5fbf; }
.file-icon--excel .file-icon__band { fill: #1e7a45; }
.file-icon--powerpoint .file-icon__band { fill: #c4501a; }
.file-icon--image .file-icon__band { fill: #7b4bb7; }
.file-icon--archive .file-icon__band { fill: #7a6420; }

/* In der Liste: Symbol links, Titel wächst, die Marke bleibt rechts. */
.entry__icon { flex: none; display: flex; }
.entry__icon + .entry__main { flex: 1 1 12rem; }

/* Als Karte: Symbol links neben dem Text. */
.document-card { display: flex; gap: var(--s4); align-items: flex-start; }
.document-card__icon { flex: none; padding-top: 0.125rem; }
.document-card__body { flex: 1 1 auto; min-width: 0; }

/* ==========================================================================
   DARSTELLUNG - Dunkel, Textgröße, hoher Kontrast, unterstrichene Links
   ==========================================================================

   Gesteuert über Attribute am <html> (Support\DisplayPrefs), gesetzt vom
   Server und beim Umschalten von public/js/display.js:

     data-theme="auto|light|dark"   auto folgt dem Gerät
     data-text="m|l|xl"             100 / 112,5 / 125 %
     data-contrast="high"           7:1, Linien in voller Tinte
     data-links="underline"         Verweise immer unterstrichen

   DER DUNKLE MODUS IST KEIN NEUER ENTWURF, sondern dieselben Token mit
   anderen Werten - das Plakat bei Nacht: Tiefschwarz als Grund, Papierweiss
   als Schrift, die Vereinsfarbe als Fläche unverändert (ihre Schrift ist
   ohnehin danach gerechnet), als Linie aufgehellt. Keine Schatten, Radius 0. */

/* -- Dunkel ------------------------------------------------------------------
   Die Werte stehen ZWEIMAL: für "dunkel" und für "wie das Gerät" bei
   dunklem Gerät. CSS kann einen Block nicht unter zwei Bedingungen setzen,
   von denen eine eine Medienabfrage ist. */
:root[data-theme='dark'] {
    color-scheme: dark;
    --paper: #1b1e23;
    --paper-sunk: #121417;
    --paper-edge: #30353d;
    --ink: #eceef2;
    --ink-soft: #c0c5cd;
    --ink-faint: #9aa1ab;
    --ink-placeholder: #6c727c;
    --brand-line: var(--brand-line-dark);
    --signal-good: #6fcf97;
    --signal-warn: #f0b45c;
    --signal-bad: #f28b82;
    --signal-info: #8ab4f8;
    --field-good: #14281c;
    --field-warn: #2e2412;
    --field-bad: #341a19;
    --field-info: #16243a;
}

@media (prefers-color-scheme: dark) {
    :root[data-theme='auto'] {
        color-scheme: dark;
        --paper: #1b1e23;
        --paper-sunk: #121417;
        --paper-edge: #30353d;
        --ink: #eceef2;
        --ink-soft: #c0c5cd;
        --ink-faint: #9aa1ab;
        --ink-placeholder: #6c727c;
        --brand-line: var(--brand-line-dark);
        --signal-good: #6fcf97;
        --signal-warn: #f0b45c;
        --signal-bad: #f28b82;
        --signal-info: #8ab4f8;
        --field-good: #14281c;
        --field-warn: #2e2412;
        --field-bad: #341a19;
        --field-info: #16243a;
    }
}

/* DAS FARBFELD DES VEREINS BLEIBT, WIE ES IST. Es ist die stärkste Präsenz
   der Vereinsfarbe und im Dunkeln dasselbe Plakat - mit demselben weissen
   Knopf darauf. Innerhalb des Feldes gelten deshalb die hellen Werte. */
:root[data-theme="dark"] .field {
    --paper: #ffffff;
    --paper-sunk: #f2f3f5;
    --paper-edge: #e3e5e9;
    --ink: #14161a;
    --ink-soft: #4a4f57;
    --ink-faint: #646a74;
    --ink-placeholder: #8b9099;
}

@media (prefers-color-scheme: dark) {
    :root[data-theme="auto"] .field {
        --paper: #ffffff;
        --paper-sunk: #f2f3f5;
        --paper-edge: #e3e5e9;
        --ink: #14161a;
        --ink-soft: #4a4f57;
        --ink-faint: #646a74;
        --ink-placeholder: #8b9099;
    }
}

/* Ein Logo mit dunkler Schrift auf durchsichtigem Grund verschwände im
   Dunkeln. Ein schmaler heller Rahmen hält es lesbar - wie ein Aufkleber
   auf dem Plakat. */
:root[data-theme='dark'] .navbar-logo__mark {
    background: #ffffff;
    padding: 2px;
}

@media (prefers-color-scheme: dark) {
    :root[data-theme='auto'] .navbar-logo__mark {
        background: #ffffff;
        padding: 2px;
    }
}

/* -- Textgröße -----------------------------------------------------------------
   Alles im Layout rechnet in rem - die Wurzel zu vergrössern lässt Schrift,
   Abstände und Knöpfe gemeinsam wachsen, ohne dass etwas überläuft. */
:root[data-text='l'] {
    font-size: 112.5%;
}

:root[data-text='xl'] {
    font-size: 125%;
}

/* -- Hoher Kontrast ------------------------------------------------------------
   Gedämpfte Schrift wird volle Tinte, Haarlinien werden Tintenlinien, die
   Vereinsfarbe als Linie geht auf 7:1. Unterstrichen wird NICHT
   automatisch - dafür gibt es den eigenen Haken "Links unterstreichen";
   jeder Haken tut genau, was an ihm steht. */
:root[data-contrast='high'] {
    --ink-soft: var(--ink);
    --ink-faint: var(--ink);
    /* Der Platzhalter wird NICHT volle Tinte - sonst gliche er dem Eintrag
       aufs Haar. Er bekommt das kräftige Grau (5.5:1). */
    --ink-placeholder: #646a74;
    --paper-edge: var(--ink);
    --brand-line: var(--brand-line-hc);
}

:root[data-contrast='high'][data-theme='dark'] {
    --brand-line: var(--brand-line-hc-dark);
    --ink-placeholder: #9aa1ab;
}

@media (prefers-color-scheme: dark) {
    :root[data-contrast='high'][data-theme='auto'] {
        --brand-line: var(--brand-line-hc-dark);
        --ink-placeholder: #9aa1ab;
    }
}

:root[data-contrast='high'] :focus-visible {
    outline-width: 3px;
    outline-offset: 3px;
}

/* -- Unterstrichene Verweise ---------------------------------------------------
   Nur im Inhalt und in der Fusszeile: Menüpunkte und Knöpfe sind schon als
   solche erkennbar, und Kacheln tragen ihren Titel als ganze Fläche. */
:root[data-links='underline'] :is(main, footer, .alert) a:not(.btn):not(.stretched):not(.nav-link) {
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: 0.18em;
}

/* -- Der Schnellschalter "Ansicht" ---------------------------------------------
   Schwebt unten rechts und wandert mit - von jeder Stelle der Seite aus
   erreichbar, auf dem Telefon in Reichweite des Daumens. Ein <details> -
   klappt ohne Skript auf, und zwar nach OBEN. Gestaltet wie die
   Aufklappmenüs: Papier, eine Tintenlinie, kein Schatten.

   Unter Kopfleiste (20) und Dialogen (40): Ein offener Dialog deckt ihn zu. */
.display-switch {
    position: fixed;
    right: var(--s4);
    bottom: var(--s4);
    z-index: 15;
}

.display-switch__toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 3rem;
    height: 3rem;
    padding: 0;
    border: 2px solid var(--ink);
    border-radius: 50%;
    background: var(--paper);
    color: var(--ink);
    cursor: pointer;
    list-style: none;
    user-select: none;
}

.display-switch__toggle::-webkit-details-marker {
    display: none;
}

.display-switch__toggle::marker {
    content: "";
}

.display-switch__toggle .icon {
    width: 1.375rem;
    height: 1.375rem;
}

/* Das Wort bleibt für Bildschirmleser - sichtbar ist nur das Auge. */
.display-switch__label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
}

.display-switch[open] .display-switch__toggle,
.display-switch__toggle:hover {
    background: var(--ink);
    color: var(--paper);
}

.display-switch__panel {
    position: absolute;
    right: 0;
    bottom: calc(100% + var(--s2));
    /* Bei grosser Schrift auf kleinem Schirm: lieber scrollen als oben
       abgeschnitten werden. */
    max-height: calc(100vh - 6rem);
    overflow-y: auto;
    z-index: 60;
    width: min(18rem, calc(100vw - 2rem));
    padding: var(--s4);
    background: var(--paper);
    border: 2px solid var(--ink);
    display: grid;
    gap: var(--s4);
}

.display-switch__group {
    border: 0;
    margin: 0;
    padding: 0;
    display: grid;
    gap: var(--s1);
}

.display-switch__group legend {
    font-size: 0.75rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--ink-soft);
    margin-bottom: var(--s1);
}

.display-switch__option {
    display: flex;
    align-items: center;
    gap: var(--s2);
    min-height: 2rem;
    cursor: pointer;
}

.display-switch__option input {
    accent-color: var(--brand);
    width: 1.05rem;
    height: 1.05rem;
}

/* Die Textgrößen zeigen sich in ihrer eigenen Grösse. */
.display-switch__option--l { font-size: 1.125rem; }
.display-switch__option--xl { font-size: 1.25rem; }

.display-switch__note {
    margin: 0;
    font-size: 0.8125rem;
    color: var(--ink-faint);
}

/* Der Knopf deckt die rechte untere Ecke. Ganz unten bekommt die Fusszeile
   deshalb Luft, und die haftende Leiste der Abrechnung lässt ihm Platz,
   damit ihr Knopf nicht darunter liegt. */
.footer {
    padding-bottom: calc(var(--s6) + 3.5rem);
}

.pick__bar {
    padding-right: calc(var(--s4) + 3.75rem);
}

/* -- Zu eng für eine Zeile -------------------------------------------------------
   Grosse Schrift, ein langer Vereinsname oder viele Menüpunkte: Passt die
   Leiste nicht mehr in eine Zeile, setzt public/js/display.js die Klasse
   `nav-compact`, und es gilt das Telefonmenü - auch auf breiterem Schirm.
   Die Breitenabfragen in rem helfen hier nicht: Sie rechnen mit der
   Grundgrösse des Browsers, nicht mit der gewählten Textgrösse.
   Dieselben Regeln wie unter "max-width: 47.999rem" weiter oben. */
:root.nav-compact .hamburger { display: flex; }
:root.nav-compact .navbar-container { position: relative; }

:root.nav-compact .navbar-menu {
    display: none;
    position: absolute;
    left: 0;
    right: 0;
    top: 100%;
    max-height: calc(100vh - 5rem);
    max-height: calc(100dvh - 5rem);
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: var(--s4) var(--s5) var(--s5);
    background: var(--paper);
    border-bottom: var(--rule-strong);
}

:root.nav-compact .navbar-menu.active { display: block; }

:root.nav-compact .navbar-nav {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
}

:root.nav-compact .navbar-nav .nav-link {
    padding: var(--s3) 0;
    border-bottom: var(--rule);
    border-left: none;
}

:root.nav-compact .navbar-nav .nav-link-btn {
    margin-top: var(--s3);
    border: 2px solid var(--brand-line);
    text-align: center;
}

:root.nav-compact .dropdown-menu {
    position: static;
    min-width: 0;
    max-height: none;
    overflow: visible;
    overscroll-behavior: auto;
    background: var(--paper-sunk);
    border: 0;
    border-bottom: var(--rule);
}

:root.nav-compact .navbar-nav .dropdown-toggle {
    width: 100%;
    justify-content: space-between;
}

/* -- Schmuck: die Akzentfarbe ----------------------------------------------------
   Nur, wenn der Verein eine Akzentfarbe gewählt hat (data-accent am <html>).
   Ohne sie gibt es keinen Schmuck - die Vereinsfarbe wird NICHT zur Zierde
   (siehe docs/gestaltung.md, "Band zurückgenommen").

   Zwei Stellen, beide ohne Aussage:
     - ein Punkt vor den Abschnittstiteln ("Angebote", "Vermietung" ...)
     - eine Kante oben an jeder Kachel, auch beim Überfahren - der Rahmen
       färbt sich dann in der Vereinsfarbe, die Kante bleibt.

   Die Farbe steht, wie gewählt: Schmuck trägt keine Schrift und braucht
   deshalb keinen Kontrast nach WCAG. */
:root[data-accent] .section-head h2::before {
    content: '';
    display: inline-block;
    width: 0.5em;
    height: 0.5em;
    margin-right: 0.35em;
    border-radius: var(--radius-round);
    background: var(--accent);
    vertical-align: 0.08em;
}

:root[data-accent] .card--sheet,
:root[data-accent] .card--sheet:hover,
:root[data-accent] .card--sheet:focus-within,
:root[data-accent] .card--sheet:focus-visible {
    border-top: 4px solid var(--accent);
}
