:root {
    --ms-accent: #4fc3f7;

    /* -----------------------------------------------------------------------
       W-457: die Bedienfarbe des Webs — eine feste Hausfarbe
       -----------------------------------------------------------------------
       Die Trennung: **feste Farbe = was ich anklicken kann**, Vereinsfarbe =
       wo ich bin und wem das gehört (aktiver Reiter, Tabellenkopf — das ist
       W-123 und bleibt unangetastet). Der Aktionston der Skins gehört nach
       Konvention N7 der App; das Web liest ihn nicht, sonst müsste die Palette
       dem Web hinterherziehen.

       Warum nicht der alte `--ms-accent` (#4fc3f7): der erreicht gegen eine
       weiße Seite nur 2,00:1 — als gefüllte Fläche im Hellmodus kaum vom Grund
       zu unterscheiden. Warum nicht das Dunkelblau der alten Version (#184063):
       gegen den dunklen Grund nur 1,62:1, es bräuchte einen Rahmen, und Rahmen
       als UI-Mittel verbietet N9. #37718E trägt in beiden Modi (5,36:1 gegen
       Weiß als Beschriftung, 3,24:1 gegen die dunkle Seite) ohne Rahmen.

       Wer eine neue Bedienfläche baut, nimmt diese zwei Variablen — nicht
       `--ms-accent`, der bleibt den Nicht-Bedienelementen (Status-Kacheln,
       Zeitleiste). Rot und Grün bleiben ausgenommen: Löschen, Anlegen,
       gewonnen/verloren tragen Bedeutung. */
    --ms-action: #37718E;
    --ms-action-text: #FFFFFF;

    /* W-462: die beiden Grundflächen des Dunkelmodus — Seite und das, was darauf liegt.
       Sie waren bis hierher exakt neutral (#1a1a1a / #2a2a2a, 0 % Sättigung). Solange
       Bootstrap die Karte malte, fiel das nicht auf; seit W-454 ist die hellere der
       beiden die Kartenfläche, und daneben steht Schrift in #dee2e6 — Farbton 210, wie
       jedes Grau dieser Bibliothek. Neben blaugrauer Schrift liest das Auge ein neutrales
       Grau als WARM, gemeldet als „ins Braun gerutscht". Der Hellmodus war nie betroffen:
       seine Kartenfläche #f1f3f5 trägt denselben Farbton 210 schon immer.

       Die Werte sind nicht erfunden, sondern Bootstraps eigene Leiter: #2B3035 ist dort
       `--bs-tertiary-bg`, die erhobene Fläche. Sie ist rund 2 % heller als das alte
       Neutralgrau — die Karte hebt sich damit etwas deutlicher ab, was W-454 wollte.
       Der Seitengrund geht im selben Farbton mit, sonst sähe die Karte auf ihm nicht
       heller, sondern eingefärbt aus. */
    --ms-surface: #1A1D20;
    --ms-surface-variant: #2B3035;
    --ms-online: #4caf50;
    --ms-offline: #f44336;
    /* W-271: Breitester Zustand einer Kachel (Tisch · Stream · Historien-Spiel) —
       950 px = halbe HD-Breite. Regel + Spaltenstaffelung bei `.live-tables`. */
    --ms-tile-max: 950px;

    /* -----------------------------------------------------------------------
       W-454: die Form-Leiter der Website
       -----------------------------------------------------------------------
       `overlay_base.css` hat so eine Leiter seit W-179, `stats.css` eine für
       Farben seit V20 — die Website hatte keine. Ergebnis waren 14 Radien,
       ~40 Schriftgrößen und 22 Deckkraft-Stufen für eine Handvoll Rollen; jede
       Stelle entschied für sich, und wo auch das nicht geschah, entschied
       Bootstrap. Genau das war der Eindruck „nicht aus einem Guss".

       Die Stufen sind NICHT frei gewählt, sondern die jeweils häufigsten
       Bestandswerte ihrer Rolle. Wer eine neue Stelle baut, nimmt eine Stufe —
       ein neuer Zwischenwert ist ein Rückschritt, kein Feinschliff.
       ----------------------------------------------------------------------- */

    /* Radius — drei Rollen, sichtbar auseinander (4-px-Schritt). Kreise bleiben
       `50%`, das ist eine Form und keine Stufe. */
    --ms-r-sm: 6px;     /* Bedienelement, Chip, Icon */
    --ms-r-md: 10px;    /* Block innerhalb einer Fläche */
    --ms-r-lg: 14px;    /* Fläche selbst: Kachel, Karte, Panel */
    --ms-r-pill: 999px; /* Pille */

    /* Deckkraft — „wie weit tritt es zurück". Vier Stufen statt 22.

       W-474: Die Leiter hat eine Grenze, und die liegt zwischen Stufe 3 und 4.
       Text braucht 4,5:1 zum Grund, und das HELLE Layout ist das knappere:
       .5 steht dort auf 3,1:1, .6 auf 4,2:1 — beide zu schwach. .65 steht auf
       4,9:1. Deshalb ist `--ms-dim-3` die LEISESTE Stufe, die Text tragen darf,
       und `--ms-dim-4` trägt gar keinen: nur Symbole und inaktive
       Bedienelemente, für die 3:1 genügt. Der Wert .65 ist derselbe, den
       `--stats-faint` in W-473 bekommen hat — eine Schwelle, nicht zwei. */
    --ms-dim-1: .85;    /* kaum zurück — liest noch voll mit */
    --ms-dim-2: .7;     /* leise — Beschriftung, Nebenangabe */
    --ms-dim-3: .65;    /* leiser — Meta, Zähler, Trennwort; letzte Text-Stufe */
    --ms-dim-4: .5;     /* NUR Symbol/inaktives Bedienelement — kein Text */

    /* Schrift — nach ROLLE benannt, nicht nach Größe. Das Vorbild ist die
       4-Rollen-Skala der App (Konvention 2026-07-15, Titel > Button > Body >
       Label): eine Stelle, an der die Hierarchie feinjustiert wird.
       Die Stufen liegen bewusst DICHT an den Bestandswerten — das Problem waren
       fünf nicht unterscheidbare „klein"-Stufen (.78/.8/.82/.85/.875), nicht die
       Größen selbst. Nichts bewegt sich um mehr als ~3 %.

       ACHTUNG, hier endet die Leiter: Alles ab 1.1rem ist Anzeige-Größe der
       Live-Seite (Titel, Gesamtscore, Teamname) und steht dort in PAAREN je
       Umbruchpunkt — `.total-score` ist 3rem und im @media 2.4rem. Das sind
       keine doppelten Werte, das ist Responsivität; wer sie auf eine Stufe
       zieht, nimmt der Seite ihre Umbrüche. Ebenso ausgenommen: `em` (relativ
       zum Elternelement), `pt` (das druckbare Ein-Blatt) und `clamp()`. */
    --ms-fs-meta:  .7rem;    /* kleinste Nebenangabe: Tabellenkopf, Marke */
    --ms-fs-small: .8rem;    /* Nebentext, Meta-Zeile */
    --ms-fs-label: .875rem;  /* Label, Sekundärtext */
    --ms-fs-body:  .95rem;   /* Fließtext in Kachel und Tabelle */
    --ms-fs-base:  1rem;     /* Grundschrift (= Bootstrap-Body) */
    --ms-fs-emph:  1.05rem;  /* Hervorhebung: Spielername, Break, Score */

    /* Schatten — drei Rollen statt 19 Einzelwerten. Fokus- und Zustandsringe
       (`0 0 0 …`) sind KEINE Schatten, sondern Bedienhinweise; sie bleiben
       unangetastet. Die beiden gestuften Werte standen schon je zweimal im
       Bestand — sie sind gewählt, weil sie sich bewährt haben. */
    --ms-shadow-1: 0 1px 2px rgba(0, 0, 0, .28);                              /* liegt auf */
    --ms-shadow-2: 0 1px 2px rgba(0, 0, 0, .3), 0 4px 12px rgba(0, 0, 0, .22); /* Karte, Kachel */
    --ms-shadow-3: 0 6px 18px rgba(0, 0, 0, .22);                             /* schwebt darüber */
}

/* ---------------------------------------------------------------------------
   W-454: Fremdvorgaben, die site-weit abgestellt werden
   ---------------------------------------------------------------------------
   Trennlinien sind bei uns kein UI-Mittel — abgegrenzt wird über Fläche und
   Abstand. Bootstrap zieht sie an Kopf und Fuß jedes Dialogs von sich aus, und
   zwar an jeder Stelle neu. W-419 hat sie dem Anmelde-Popup einzeln abgenommen;
   die übrigen elf Dialoge trugen sie weiter. Hier steht die Regel einmal.
   --------------------------------------------------------------------------- */
.modal-header,
.modal-footer { border: 0; }

/* Dialoge stehen mittig im Bild. Bootstrap stellt sie von Haus aus oben an und
   überlässt die Mitte einer Zusatzklasse — die stand an fünf von zwölf Stellen,
   also sprang ein Dialog je nach Herkunft. Das hier ist genau das, was
   `.modal-dialog-centered` tut, nur eben überall. Die Klasse darf im Markup
   stehen bleiben, sie schadet nicht; nötig ist sie nicht mehr.
   ACHTUNG: gilt, solange es keine `modal-fullscreen`-Dialoge gibt (Stand W-454:
   keine, auch keine Größenvarianten). Käme eine, braucht sie eine Ausnahme. */
.modal-dialog {
    display: flex;
    align-items: center;
    min-height: calc(100% - var(--bs-modal-margin) * 2);
}

/* ---------------------------------------------------------------------------
   W-454: die Karte
   ---------------------------------------------------------------------------
   Rund 55 Karten stehen im Markup, und bis hierher hat sie keine einzige von uns
   gestaltet: es gab genau eine eigene Regel — ein Rahmen-Aufhellen bei Hover in
   admin.css, mit fest verdrahtetem Weiß und `!important`, und nur dort geladen,
   wo admin.css geladen wird. Alles Übrige entschied Bootstrap.

   Dessen Karte ist ein 1-px-Rahmen um eine Fläche in EXAKT der Seitenfarbe
   (`--bs-card-bg: var(--bs-body-bg)`). Sie hebt sich also gar nicht ab — sie ist
   nur umrandet. Das ist zweimal gegen unsere Linie: Trennlinien sind kein
   UI-Mittel, und eine Fläche soll sich als Fläche zeigen.

   Erfunden wird hier nichts. Die Machart steht schon dreifach im Haus —
   `.activity-tile` (Zeile ~395), die Navigationsleiste und die Fußzeile heben
   sich über einen helleren Grund ab: dunkel `--ms-surface-variant` auf
   `--ms-surface`, hell #f1f3f5 auf #fff. Die Karte tritt dieser Familie bei, statt eine vierte Machart zu
   sein. Radius, Schatten und Kopf-Tönung kommen aus der Leiter bzw. aus dem
   `color-mix`-Muster, das `.ms-login-card` schon benutzt.
   --------------------------------------------------------------------------- */
.card {
    --bs-card-border-width: 0;
    --bs-card-border-radius: var(--ms-r-lg);
    --bs-card-inner-border-radius: var(--ms-r-lg);
    --bs-card-cap-bg: color-mix(in srgb, var(--bs-body-color) 5%, transparent);
    box-shadow: var(--ms-shadow-2);
}
body.dark-mode  .card { --bs-card-bg: var(--ms-surface-variant); }
body.light-mode .card { --bs-card-bg: #f1f3f5; }

/* Der Kopf trennt sich über seine eigene Tönung und das Gewicht. Der Strich, den
   Bootstrap unter ihn zieht, hängt an `--bs-card-border-width` und ist damit
   oben schon miterledigt. */
.card-header { font-weight: 600; }

/* Fünf Karten haben eine randlose Tabelle im Rumpf (`card-body p-0`). Solange die
   Ecke 6 px war, fiel das nicht auf; bei 14 px ragt eine Tabelle, die selbst einen
   Grund malt (`table-dark`, Streifen, Hover), sichtbar aus der Rundung.
   ACHTUNG, die Ausnahme ist kein Schönheitsfehler: `overflow: hidden` macht den
   Rumpf zum eigenen Scroll-Bereich, und ein `position: sticky` darin verliert
   dadurch die Seite als Bezug. Der Log-Viewer hat genau so einen mitlaufenden
   Tabellenkopf (logs.php:955) — der bleibt deshalb ungeklammert. */
.card > .card-body.p-0:last-child:not(:has(.sticky-top)) {
    overflow: hidden;
    border-bottom-left-radius: var(--ms-r-lg);
    border-bottom-right-radius: var(--ms-r-lg);
}

body.dark-mode {
    background-color: var(--ms-surface);
}

/* W-457: die Bedienfarbe als Schrift. `--ms-action` ist als Fläche gewählt; als dünner
   Linktext ist derselbe Ton auf einer hellen Seite zu blass und auf einer dunklen zu
   satt. Ein Schritt Richtung Schwarz bzw. Weiß löst das, ohne den Farbton zu verlassen.
   `--ms-action-link-on` ist die Beschriftung, wenn diese Farbe selbst zur Fläche wird
   (aktiver Zeitfilter, Datums-Chip). Die Statistik zieht ihr `--stats-link` von hier,
   damit es nur eine Stelle gibt, an der das gerechnet wird. */
body.light-mode {
    --ms-action-link: color-mix(in srgb, var(--ms-action) 60%, #000);
    --ms-action-link-on: #fff;
}
body.dark-mode {
    --ms-action-link: color-mix(in srgb, var(--ms-action) 70%, #fff);
    --ms-action-link-on: #000;
}

/* -----------------------------------------------------------------------
   W-459: Schaltflächen liegen leicht erhaben auf der Seite.

   Die Hausfarbe allein sagt nicht mehr verlässlich „hier kann ich klicken":
   derselbe Ton steht als Tönung auch unter Kacheln, Tabellenbändern und
   Nummernkreisen, mit denen man nicht interagiert. Dazu gilt seit W-459 die
   Stärke-Regel — volle Fläche = bedienbar, Tönung (höchstens ~20 %) = passiv.
   Der Schatten ist das zweite Merkmal, das keine passive Fläche je trägt:
   die liegt in der Seite, der Knopf liegt darauf.

   Bewusst weich und knapp — kein Relief. Gedrückt wird die Fläche flach, das
   ist der Weg nach unten. Im Fokus tritt der Ring an die Stelle der Erhebung,
   sonst stünden zwei Schatten am selben Knopf (`box-shadow` kennt nur einen
   Gewinner je Regel). Im Dunkel-Layout braucht der Schatten mehr Deckung,
   weil er dort gegen eine dunkle Fläche arbeitet.
   ----------------------------------------------------------------------- */
:root {
    --ms-btn-lift: 0 1px 2px rgba(0, 0, 0, .16);
    --ms-btn-lift-hover: 0 2px 5px rgba(0, 0, 0, .22);
}
body.dark-mode {
    --ms-btn-lift: 0 1px 3px rgba(0, 0, 0, .5);
    --ms-btn-lift-hover: 0 3px 7px rgba(0, 0, 0, .55);
}

.btn:not(.btn-link):not(:focus-visible) { box-shadow: var(--ms-btn-lift); }
.btn:not(.btn-link):not(:focus-visible):hover { box-shadow: var(--ms-btn-lift-hover); }
.btn:not(.btn-link):not(:focus-visible):active,
.btn:not(.btn-link):not(:focus-visible).active,
.btn:not(.btn-link):disabled,
.btn-check:checked + .btn:not(:focus-visible) { box-shadow: none; }

/* W-60/W-457: Der Primär-/Speichern-Button. Er lag bis W-457 in `admin.css` und ein zweites
   Mal als Inline-Style im Kopf der Anmeldeseiten, die `admin.css` nicht laden — zwei Kopien
   derselben Regel, die auseinanderlaufen konnten. Hier steht sie einmal, für jede Seite.
   Farbe und Beschriftung stehen einmal oben in `:root`, nicht hier — wer den Ton ändern
   will, ändert ihn dort und trifft damit jede Bedienfläche des Webs. */
.btn-primary {
    --bs-btn-bg: var(--ms-action);
    --bs-btn-border-color: var(--ms-action);
    --bs-btn-color: var(--ms-action-text);
    --bs-btn-hover-bg: color-mix(in srgb, var(--ms-action) 86%, #000);
    --bs-btn-hover-border-color: color-mix(in srgb, var(--ms-action) 86%, #000);
    --bs-btn-hover-color: var(--ms-action-text);
    --bs-btn-active-bg: color-mix(in srgb, var(--ms-action) 78%, #000);
    --bs-btn-active-border-color: color-mix(in srgb, var(--ms-action) 78%, #000);
    --bs-btn-active-color: var(--ms-action-text);
    --bs-btn-disabled-bg: var(--ms-action);
    --bs-btn-disabled-border-color: var(--ms-action);
    --bs-btn-disabled-color: var(--ms-action-text);
}
/* Bootstrap baut seinen Fokusring aus einem RGB-Tripel (`--bs-btn-focus-shadow-rgb`); ein
   Hex-Wert passt da nicht hinein. Deshalb der Ring hier direkt. */
.btn-primary:focus-visible {
    box-shadow: 0 0 0 0.25rem color-mix(in srgb, var(--ms-action) 40%, transparent);
}

/* W-457: Häkchen, Auswahlpunkte und Schalter. Bootstrap füllt sie im gesetzten Zustand
   mit seinem eigenen Blau (`#0d6efd`) — dasselbe „andere Blau", das schon beim Button
   auffiel. Sie sind Bedienelemente, also tragen sie die Hausfarbe.

   Die halbe Auswahl (`:indeterminate`, der Strich statt des Hakens) steht mit dabei:
   sie ist derselbe Schalter in einem dritten Zustand, und bliebe sie blau, hätte man
   im selben Kasten zwei Farben nebeneinander. */
.form-check-input:checked,
.form-check-input[type="checkbox"]:indeterminate {
    background-color: var(--ms-action);
    border-color: var(--ms-action);
}

/* Der Fokusring folgt derselben Farbe wie beim Button — er sagt „hier bist du gerade". */
.form-check-input:focus {
    border-color: var(--ms-action);
    box-shadow: 0 0 0 0.25rem color-mix(in srgb, var(--ms-action) 30%, transparent);
}

/* Der Knopf im Schalter färbt sich bei Bootstrap im Fokus hellblau ein. Solange der
   Schalter aus ist, bleibt er hier grau — der Ring zeigt den Fokus schon an, und ein
   zweiter Farbwechsel sähe wie „eingeschaltet" aus. Die zwei Werte sind Bootstraps
   eigene Ruhe-Zustände, keine zweite Kopie der Hausfarbe. */
.form-switch .form-check-input:focus:not(:checked) {
    --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");
}
[data-bs-theme="dark"] .form-switch .form-check-input:focus:not(:checked) {
    --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28255, 255, 255, 0.25%29'/%3e%3c/svg%3e");
}

/* W-457: Die Umschalter-Gruppen („Wettkampf/Training", „15/6" am Live-Tisch). Sie sind
   Auswahlpunkte in Knopfform; gewählt trugen sie bisher das Grau von
   `.btn-outline-secondary` und hoben sich kaum von der ungewählten Seite ab. Jetzt
   dieselbe Hausfarbe wie jede andere getroffene Wahl. */
.btn-check:checked + .btn {
    --bs-btn-active-bg: var(--ms-action);
    --bs-btn-active-border-color: var(--ms-action);
    --bs-btn-active-color: var(--ms-action-text);
}
.btn-check:focus-visible + .btn {
    box-shadow: 0 0 0 0.25rem color-mix(in srgb, var(--ms-action) 40%, transparent);
}

/* W-457: Der Fokusring der Eingabefelder. Er stand in `admin.css` und galt damit nur im
   Verwaltungsbereich — auf den Anmeldeseiten, die `admin.css` nicht laden, blieb das
   Bootstrap-Blau stehen, ausgerechnet im Passwortfeld. Hier gilt er für jede Seite. */
.form-control:focus,
.form-select:focus {
    border-color: var(--ms-action);
    box-shadow: 0 0 0 0.2rem color-mix(in srgb, var(--ms-action) 30%, transparent);
}

/* W-454: keine Trennlinie unter der Leiste — sie setzt sich über ihre Fläche ab,
   nicht über einen Strich (dunkel `--ms-surface-variant` auf `--ms-surface`,
   hell #f1f3f5 auf #fff).
   Auf Seiten mit Vereins-Skin war die Linie ohnehin schon abgeschaltet. */
body.dark-mode .navbar {
    background-color: var(--ms-surface-variant) !important;
}
body.light-mode .navbar {
    background-color: #f1f3f5 !important;
}

/* 3-Zonen-Header: Menü links, Logo mittig, Aktionen rechts.
   W-489: Die Zeile bricht nie um — nachgibt das Logo, nicht die Zeilenzahl. Und das Logo
   steht **immer** mittig: beide Seitenzonen bekommen dieselbe Basisbreite
   (`--ms-navbar-side`, von navbar-height.js auf die breitere der beiden gemessen) und
   wachsen von dort gleich weit. Ohne das Maß stünde das Logo so weit aus der Mitte, wie
   die Zonen sich unterscheiden — links „Über uns" plus Zurück-Pfeil gegen rechts Theme
   plus Profil ist nie symmetrisch. Reines CSS kann zwei inhaltsbreite Spalten nicht
   gleichziehen (weder über Flex-Basen noch über `1fr`), deshalb die gemessene Variable. */
/* Zwei Klassen, weil Bootstraps `.navbar > .container-fluid` mit `flex-wrap: wrap` sonst
   gewinnt — und genau dieser Umbruch ist das, was hier abgeschafft wird. */
.ms-navbar .ms-navbar-grid {
    display: flex;
    align-items: center;
    flex-wrap: nowrap;
}
.ms-navbar-zone {
    /* `shrink: 0` ist der Kern: eine schrumpfbare Zone würde die Knöpfe zusammenschieben,
       statt dem Logo das Nachgeben zu überlassen. */
    flex: 1 0 var(--ms-navbar-side, 0px);
    display: flex;
    align-items: center;
    gap: .5rem;
    /* Untergrenze Inhalt: ohne sie stünde bei fehlender Messung (kein `ResizeObserver`,
       oder eine Zone ist gerade breiter geworden) ein Knopf halb unter dem Logo. Solange
       gemessen wird, greift sie nie — die Variable ist ja das Maß der breiteren Zone. */
    min-width: min-content;
}
.ms-navbar-zone > * { flex-shrink: 0; }
.ms-navbar-left { justify-content: flex-start; gap: 1rem; }
.ms-navbar-right { justify-content: flex-end; gap: 1rem; }
/* W-31a: Trefferzone der Header-Controls auf ~44px anheben (Icons optisch
   unverändert; min-width/height fassen die Klickfläche, ohne mit `p-0` zu kollidieren).
   Die Zeichen selbst eine Stufe kleiner seit 2026-10-03 (1,5 → 1,25 rem, User-Intake:
   „wirkt zu groß") — die Trefferzone bleibt. */
.ms-navbar-left .ms-navbar-back {
    font-size: 1.25rem;
    line-height: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;
    min-height: 44px;
}
.ms-navbar-right #themeToggle,
.ms-navbar-right > a.nav-link,
.ms-navbar-right > .dropdown > .nav-link {
    font-size: 1.25rem;
    line-height: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;
    min-height: 44px;
}
/* Der Hamburger hatte auf dem Desktop keine eigene Regel und stand mit Bootstraps Maßen
   da: Strichbild 1,5 em auf 1,25 rem Schrift (≈ 30 px) plus .75 rem Polster je Seite, in
   einem sichtbaren Rahmen — deutlich größer als die Symbole daneben (2026-10-03). Jetzt
   ist das Strichbild so groß wie die anderen Zeichen (.85 rem × 1,5 ≈ 1,25 rem) und der
   Rahmen eng darum. Die 44-px-Trefferzone aus W-31a liegt unsichtbar darüber
   (`::after`), damit der Rahmen nicht wieder auf 44 px aufgeht. */
.ms-navbar-left .navbar-toggler {
    position: relative;
    padding: .25rem .4rem;
    font-size: .85rem;
}
.ms-navbar-left .navbar-toggler::after {
    content: "";
    position: absolute;
    inset: -6px;
}
.ms-navbar-logo {
    /* Das Logo ist das Einzige in der Zeile, das nachgeben darf (W-489). */
    flex: 0 1 auto;
    min-width: 0;
    text-align: center;
    padding: 0;
}
.ms-header-logo {
    /* `height: auto` statt fester Höhe: schrumpft die Breite, soll die Höhe mitgehen
       (bei fester Höhe ließe `object-fit: contain` stattdessen Leerraum stehen). */
    height: auto;
    max-height: 44px;
    max-width: min(220px, 100%);
    object-fit: contain;
}
.ms-navbar-menus {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
}
/* Hamburger-Menü links, Profil-Menü rechts – beide mit gleicher Oberkante. */
.ms-navbar-menus > #navMain {
    margin-right: auto;
}
.ms-navbar-menus > #navProfile {
    margin-left: auto;
}
.ms-navbar-menu {
    flex-basis: auto;
    flex-grow: 0;
}
.ms-navbar-menu .navbar-nav {
    padding-top: .5rem;
}
/* Profil-Menü rechts unter dem zugehörigen Button ausrichten (nicht linksbündig). */
#navProfile .navbar-nav {
    align-items: flex-end;
    text-align: right;
}

/* ===== W-489: Schmale Screens — die Kopfzeile bleibt EINE Zeile =====
   Bis W-489 lag das Logo oben und die beiden Zonen darunter (`order: -1`, je 50 % Basis,
   damit die Zeilensumme über 100 % geht und echt umbricht). Das kostete eine ganze Zeile
   Höhe für drei Dinge, die nebeneinander Platz haben — und die Leiste ist `sticky`, jede
   Zeile fehlt dem Inhalt darunter dauerhaft. Die Regeln dafür stehen oben (nowrap,
   gemessene Seitenbreite); hier bleibt, was nur auf dem Telefon gilt.
   Zum Maß: das breiteste Logo im Haus ist 960 × 200 (4,8 : 1) und damit bei 36 px Höhe
   173 px breit. Auf 360 px Gerätebreite bleiben nach Hamburger und den beiden rechten
   Symbolen knapp 200 px — es steht also gerade unverkleinert, und der Rest ist Reserve
   für noch breitere Logos. */
@media (max-width: 575.98px) {
    .ms-header-logo { max-height: 36px; }

    /* Die Symbole werden kleiner, ihre **Trefferzone** nicht: die 44 px aus W-31a bleiben
       als `min-width`/`min-height` stehen, nur das Zeichen darin wird kleiner. Das ist,
       was der Platz für das Logo hergibt, ohne an der Bedienbarkeit zu sparen. */
    .ms-navbar-left { gap: .5rem; }
    .ms-navbar-right { gap: .5rem; }
    .ms-navbar-left .ms-navbar-back,
    .ms-navbar-right #themeToggle,
    .ms-navbar-right > a.nav-link,
    .ms-navbar-right > .dropdown > .nav-link {
        font-size: 1rem;
    }
    /* Bootstraps Hamburger-Strichbild ist 1,5 em — es folgt dem Schriftgrad hier, sonst
       bliebe genau dieses eine Symbol in alter Größe stehen (.7 rem × 1,5 ≈ 1 rem wie die
       Zeichen daneben). Die Trefferzone trägt das `::after` aus der Desktop-Regel. */
    .ms-navbar-left .navbar-toggler {
        padding: .2rem .35rem;
        font-size: .7rem;
    }
    body.ms-skinned .ms-navbar .navbar-toggler-icon::before,
    body.ms-skinned-frame .ms-navbar .navbar-toggler-icon::before { font-size: 1.05rem; }
}

/* Unter 360 px: zurück auf zwei Zeilen (User-Entscheid 2026-09-28).
   Die einzeilige Leiste lebt davon, dass das Logo nachgibt. Bis 360 px ist das ein
   Zurechtrücken — darunter würde aus dem Nachgeben ein Verstümmeln, und ein Vereinslogo,
   das man nicht mehr erkennt, ist schlechter als eine zweite Zeile. Also fällt hier die
   alte Aufteilung ein: Logo oben in voller Breite und unverkleinert, die beiden Zonen
   darunter. Zwei Zeilen kosten Höhe, aber Geräte unter 360 px sind selten — die Ausnahme
   trägt den Preis, nicht der Normalfall. */
@media (max-width: 359.98px) {
    .ms-navbar .ms-navbar-grid { flex-wrap: wrap; }
    .ms-navbar-logo {
        order: -1;            /* eigene Zeile **über** den Zonen */
        flex: 0 0 100%;
        /* `max-width: 100%` bleibt als letzter Halt stehen: ein Logo, das breiter ist als
           das Gerät, muss weichen — sonst schöbe es die Seite seitlich auf. */
    }
    /* Ohne Logo in der Zeile teilen sich die Zonen die zweite: die gemessene Gleichbreite
       hat hier nichts mehr auszurichten (nichts steht zwischen ihnen), links beginnt,
       rechts endet. */
    .ms-navbar-zone { flex: 1 1 auto; }
}

/* W-271: Tischkacheln und Streams wachsen nicht unbegrenzt mit. `main` ist ein
   `container-fluid` — ohne Deckel zieht sich eine Kachel auf einem breiten Schirm über
   einen halben Meter. Eine Kachel wird deshalb nie breiter als `--ms-tile-max` (950 px =
   halbe HD-Breite): auf 1920 px füllen zwei Tische das Bild formatfüllend aus, breitere
   Schirme lassen den Block **zentriert** stehen, statt ihn zu dehnen.

   Wie viele Kacheln nebeneinander stehen dürfen, hängt an ihrer **Anzahl** — sie steht als
   `data-cols` am Container (2 · 3). Gesetzt wird sie dort, wo die Kacheln entstehen:
   serverseitig `ms_grid_cols()` in includes/live_state.php, im Browser `render()` in
   assets/js/live-tables.js. Die Spaltenzahl ist immer der kleinere der beiden Werte
   „so viele erlaubt die Anzahl" und „so viele passen auf den Schirm".

   W-484: Die Spuren sind `minmax(0, 1fr)`, nicht `1fr`. `1fr` heißt `minmax(auto, 1fr)` —
   die Spur wächst dann bis zur Mindestbreite ihres Inhalts, statt den Inhalt zu kürzen, und
   eine Kachel mit nicht umbrechendem Inhalt (Ankündigung der nächsten Partie) schiebt die
   Seite seitwärts aus dem Fenster. Mit der Null als Untergrenze greifen `text-overflow` und
   die Vereins-Kürzung aus W-437 — gekürzt wird, nicht gescrollt. */
.live-tables,
.ms-stream-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 10px;
    align-items: start;
    max-width: var(--ms-tile-max);
    margin-inline: auto;
}

@media (min-width: 992px) {
    .live-tables,
    .ms-stream-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        max-width: calc(2 * var(--ms-tile-max) + 10px);
    }
    /* Alleiniger Tisch in der letzten Zeile: gleiche Breite wie die anderen, zentriert.
       Gilt für beide Grid-Item-Typen: Live-Block (JS) und Preview-Zeile (SSR). */
    .live-tables > *:last-child:nth-child(odd),
    .ms-stream-grid > *:last-child:nth-child(odd) {
        grid-column: 1 / -1;
        justify-self: center;
        width: calc(50% - 5px);
    }
}

/* W-299: Die dritte Spalte gibt es erst ab neun Kacheln UND erst jenseits von 2500 px —
   dort bleiben je Spalte noch gut 800 px. Auf Full-HD (1920 px) sind es deshalb immer zwei
   Spalten, auch bei zwölf Tischen: drei Spalten ließen je Kachel gut 630 px, und der Name
   mit Verein in Klammern passt dann nicht mehr in eine Zeile. Eine vierte Spalte gibt es
   nicht mehr — auf einem Schirm, der sie tragen könnte, gibt es sie noch nicht. */
@media (min-width: 2500px) {
    .live-tables[data-cols="3"],
    .ms-stream-grid[data-cols="3"] {
        grid-template-columns: repeat(3, minmax(0, 1fr));
        max-width: calc(3 * var(--ms-tile-max) + 20px);
    }
    /* Jenseits von zwei Spalten gibt es keinen „alleinigen" Rest mehr, der sich halbbreit
       zentrieren ließe — die Sonderregel der Zweispalten-Ansicht wird zurückgenommen. */
    .live-tables[data-cols="3"] > *:last-child:nth-child(odd),
    .ms-stream-grid[data-cols="3"] > *:last-child:nth-child(odd) {
        grid-column: auto;
        justify-self: stretch;
        width: auto;
    }
}

.live-row {
    background: var(--sk-row-bg, #1a1a1a);
    color: var(--sk-text, #fff);
    border: 1px solid rgba(0,0,0,.28);
    border-radius: var(--ms-r-lg);
    overflow: hidden;
    box-shadow: var(--ms-shadow-2);
    font-family: 'Roboto', system-ui, -apple-system, sans-serif;
}

/* W-22: Idle-/Ankündigungs-Zustand. Die Demo-Tische bilden immer die Grundschicht
   (die Seite wirkt nie „tot"); dezent gedimmt + eine unaufdringliche Beispiel-Notiz. */
.live-preview-wrap { position: relative; }
.live-tables--preview {
    opacity: var(--ms-dim-3);
    filter: grayscale(.25);
    pointer-events: none;
    user-select: none;
}
/* W-47: prominenter Nicht-Live-Hinweis, der wie in der Alt-Software mittig ÜBER den
   gedimmten Demo-Tischen schwebt — sofort erkennbar, dass keine Live-Ergebnisse laufen. */
.live-preview-banner {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 2;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: .6rem;
    max-width: min(90%, 640px);
    padding: 1rem 2rem;
    border-radius: var(--ms-r-lg);
    background: rgba(20, 17, 10, .82);
    box-shadow: var(--ms-shadow-3);
    backdrop-filter: blur(3px);
    text-align: center;
    /* Banner ist in beiden Themes dunkel → Text immer hell halten (Light-Mode-Kontrast). */
    color: #f5f2ec;
}
.live-preview-banner .bi {
    align-self: flex-start;
    margin-top: .12em;
    color: #e0a020;
    font-size: 1.5rem;
}
.live-preview-banner .lpb-main {
    font-size: clamp(1.3rem, 2.2vw, 1.8rem);
    font-weight: 700;
    letter-spacing: .01em;
}

/* Idle-Kopf: freier Titel bzw. Event-Ankündigung + Countdown. */
.live-idle-head { margin-top: .5rem; }
.live-idle-title {
    font-size: 2.4rem;
    font-weight: 700;
    line-height: 1.15;
}
.live-idle-badge {
    display: inline-block;
    margin-bottom: .35rem;
    font-size: var(--ms-fs-small);
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
    opacity: var(--ms-dim-2);
}
.live-countdown {
    display: inline-flex;
    align-items: baseline;
    gap: .6rem;
    margin-top: .9rem;
    padding: .5rem 1.1rem;
    border-radius: var(--ms-r-md);
    background: rgba(127, 127, 127, .12);
}
.live-countdown .lc-label {
    font-size: var(--ms-fs-label);
    font-weight: 500;
    opacity: var(--ms-dim-2);
}
.live-countdown .lc-clock {
    font-size: 1.7rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    letter-spacing: .02em;
}

/* V1 (REQ-SW-059): reduziertes Tisch-Aktivitaets-Raster fuer eingeloggte Mitglieder
   bei nicht-oeffentlichem Event. Nur Zustand pro Tisch (aktiv/pausiert/inaktiv),
   keine Scores/Namen. Farb-Konvention wie die alte Status-Ansicht. */
.live-activity {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 10px;
    align-items: flex-start;
}
/* W-470: Der Zustand stand als 4-px-Kante links an der Kachel — dieselbe Kante, die
   zwei andere Stellen für „Warnung" und „das bist du" benutzten. Er steht jetzt im
   Schildchen, also in dem Vokabular, mit dem der Super-Admin denselben Zustand schon
   nennt („gerade aktiv", superadmin/pages/activity.php). Die Kachel selbst trägt nur
   noch eine sehr leichte Tönung derselben Farbe, damit sie aus der Entfernung lesbar
   bleibt; der 1-px-Rahmen fällt ersatzlos weg — getragen wird die Kachel vom Schatten. */
.activity-tile {
    display: flex;
    flex-direction: column;
    align-items: center;
    width: 160px;
    gap: 6px;
    padding: 16px 12px;
    border-radius: var(--ms-r-lg);
    box-shadow: var(--ms-shadow-2);
    font-family: 'Roboto', system-ui, -apple-system, sans-serif;
}
body.dark-mode .activity-tile {
    background: color-mix(in srgb, var(--at-color, #9e9e9e) 14%, var(--ms-surface-variant));
    color: #fff;
}
body.light-mode .activity-tile {
    background: color-mix(in srgb, var(--at-color, #9e9e9e) 8%, #f1f3f5);
    color: #212529;
}
.activity-tile .at-table { font-weight: 600; font-size: var(--ms-fs-emph); }
.activity-tile .at-status {
    font-size: var(--ms-fs-body);
    font-weight: 600;
    background: var(--at-color, #9e9e9e);
    color: var(--at-ink, #fff);
    padding: .1rem .6rem;
    border-radius: 999px;
}
/* Vor der ersten Antwort steht die Kachel ohne Zustand da; ohne diese Zeile bliebe ein
   leeres graues Schildchen stehen, wo bisher gar nichts stand. */
.activity-tile .at-status:empty { display: none; }
/* W-474: „Inaktiv" trug bis hierher Alarmrot (#ef2b2b) — und ist dabei der
   NORMALZUSTAND: kein Gerät, kein offenes Spiel, oder eine halbe Stunde nichts
   gewertet (`live_state.js`). In einem Verein mit neun Tischen standen an einem
   ruhigen Tag acht rote Schildchen nebeneinander; das ist keine Nachricht, das
   ist eine Wand. Farbe tragen jetzt nur die Zustände, die auch eine sind:
   grün = läuft, orange = Pause. Inaktiv bleibt neutral grau — weiß darauf steht
   auf 4,7:1 und damit über den 4,5:1, die Text braucht.
   Das Rot der Live-Seite (`--ms-offline`, Statuspunkt) ist davon unberührt. */
.activity-tile.is-active   { --at-color: var(--ms-online, #4caf50); --at-ink: #fff; }
.activity-tile.is-paused   { --at-color: #ff9800;                   --at-ink: #212529; }
.activity-tile.is-inactive { --at-color: #6c757d;                   --at-ink: #fff; }

/* W-299: Der Abstand entsteht über den Titel (`margin-right: auto`), NICHT über
   `justify-content: space-between`. Mit dem Stift hat die Kopfzeile ein viertes Kind;
   `space-between` hätte den Status-Punkt in die Mitte gezogen, statt ihn — wie seit
   je — am rechten Rand zu lassen. So bleibt die Reihenfolge stabil, egal wie viele
   Kinder tatsächlich da sind: Titel links, alles Übrige rechts aneinander. */
.lr-head {
    display: flex;
    align-items: center;
    padding: 3px 12px;
    font-size: var(--ms-fs-meta);
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
    background: rgba(0,0,0,.16);
}

/* W-299: Der Titel trägt seit der Beschreibung („Tisch 3: Viertelfinale") einen frei
   getippten Text und darf deshalb — als zweites Kind neben dem Shootout-Hinweis —
   kürzen statt Punkt und Stift aus der Zeile zu drängen. Nie umbrechen: die Kopfzeile
   ist einzeilig. */
.lr-head .lr-title {
    margin-right: auto;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Der Tischname bleibt das Erste, was man liest; die Beschreibung hängt sich
   zurückgenommen daran. */
.lr-desc { opacity: var(--ms-dim-1); }

.lr-head .lr-pause {
    color: #ffca28;
    font-weight: 700;
}

/* W-242: Shootout-Hinweis in der Kopfzeile. Solange die Zeit läuft, ist er bewusst
   unauffällig — gleiche Schrift wie der Rest der Kopfzeile, nur zurückgenommen; er
   ist eine Randnotiz, keine Meldung. Erst mit Ablauf der Shootout-Zeit wechselt er
   in einen gedeckten Warnton (kein zweites Gelb neben `.lr-pause`, kein Rahmen).
   Er steht direkt links vom Status-Punkt, nicht mittig zwischen Titel und Status —
   dafür sorgt seit W-299 das `margin-right: auto` des Titels (siehe `.lr-head`).
   `min-width: 0` + Ellipsis: in schmalen Zeilen darf er kürzen, aber nie umbrechen
   und nie Status oder Stift verdrängen. Schrumpfen dürfen nur er und der Titel
   (W-299, dessen Beschreibung ebenfalls beliebig lang sein kann) — Status-Punkt,
   Pause-Hinweis und Stift bleiben immer vollständig stehen. Das zählt seit dem
   längeren Text der Übergangsphase („Nächster Frame: Shootout") doppelt. */
.lr-head > :not(.lr-shootout):not(.lr-title) {
    flex-shrink: 0;
}

.lr-head .lr-shootout {
    min-width: 0;
    padding-right: 10px;
    text-align: right;
    opacity: var(--ms-dim-3);
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.lr-head .lr-shootout.is-due {
    opacity: 1;
    color: #d98939;
    font-weight: 700;
}

.lr-body {
    display: grid;
    grid-template-columns: minmax(0,1fr) auto auto auto minmax(0,1fr);
    align-items: center;
    gap: 8px;
    padding: 6px 12px;
    min-height: 46px;
}

.lr-player {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    padding: 2px 6px;
    border-radius: var(--ms-r-md);
}
/* W-235: Die Spielerfläche füllt ihre Spalte, damit der Break-Slot per `margin: auto`
   bis an die Punkte-Pille rücken kann — der Name bleibt außen stehen. Genau so löst es
   das Score-Overlay seit W-216 (`.player-left/.break-display`); der Break gehört zum
   Score, auf den er einzahlt, nicht zum Namen. */
.lr-p1 { grid-column: 1; justify-content: flex-start; justify-self: stretch; max-width: 100%; }
.lr-p2 { grid-column: 5; justify-content: flex-end; justify-self: stretch; max-width: 100%; }
.lr-p1 .lr-break { margin-left: auto; }
.lr-p2 .lr-break { margin-right: auto; }
.lr-score1 { grid-column: 2; }
.lr-center { grid-column: 3; }
.lr-score2 { grid-column: 4; }
.lr-p2 .lr-name { text-align: right; }

/* W-181: Der aktive Spieler wird NICHT mehr farblich hinterlegt — die Markierung ist der
   Punkt im Break-Slot (.lr-break.active-dot), wie im Score-Overlay. W-237: Damit ist in der
   Live-Ansicht überhaupt keine Spielerfläche mehr getönt; die zuletzt verbliebene Tönung
   (Sieger der Tageshistorie) ist entfallen, `--sk-active-bg` ersatzlos ausgebaut. */

.lr-name {
    font-size: var(--ms-fs-emph);
    font-weight: 400;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}

.lr-icon {
    width: 24px;
    height: 24px;
    border-radius: var(--ms-r-sm);
    object-fit: cover;
    flex: 0 0 auto;
}
.lr-icon[hidden] { display: none; }
.lr-player.is-active .lr-name { font-weight: 700; }

.lr-break {
    font-size: var(--ms-fs-emph);
    font-weight: 700;
    color: var(--sk-break, #ffd600);
    font-variant-numeric: tabular-nums;
    min-width: 1.6ch;
    /* W-235: Zahl und Pfeil sitzen als Flex-Elemente auf einer Mittellinie — sonst hängt
       das Zeichen an der Grundlinie der Zahl und steht damit sichtbar zu tief. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    white-space: nowrap;
    flex: none;
}
/* W-227: Der Break trug hier eine getoente Flaeche PLUS einen 1-px-Rahmen — die
   Signalfarbe stand damit dreifach uebereinander (Text, Flaeche, Rahmen). Im
   Score-Overlay ist das mit W-217 gefallen: die alte Software zeigte den Break als
   reine Textfarbe, ohne Flaeche und ohne Rahmen. Weil W-216 das Overlay ausdruecklich
   an dieser Tisch-Anzeige ausrichtet, muessen beide dasselbe sagen — und ein duenner
   Rahmen ist ohnehin kein Gestaltungsmittel dieses Projekts. Der Abstand zum Namen
   kommt aus dem `gap` der Zeile, nicht aus einer Pille. */

/* W-235: Richtungszeichen zwischen Break und Punktestand — sonst stehen zwei Zahlen
   unvermittelt nebeneinander. Es zeigt zugleich, worauf der Break einzahlt, und ist
   deshalb je Seite gespiegelt. Die alte Software hatte an genau dieser Stelle dieselbe
   Lösung (`pfeil()` in `overlay/table.php`, `&#8680;`/`&#8678;`).
   W-336: Es bleibt beim schlanken Winkelzeichen; der volle Konturpfeil der Alt-Software
   stand hier eine Runde lang (W-273) und trägt zu dick auf. Tisch-Anzeige und Bauchbinde
   (`.break-display`) müssen dabei dieselbe Marke tragen, sonst driften sie auseinander.
   Als Pseudo-Element, damit `:empty` weiter greift: ohne Break bleibt der Slot leer und
   trägt den Aktiv-Punkt — dann gibt es nichts zu trennen und kein Pfeil erscheint. */
.lr-p1 .lr-break:not(:empty)::after  { content: "\203A"; margin-left: .28em; line-height: 1; }
.lr-p2 .lr-break:not(:empty)::before { content: "\2039"; margin-right: .28em; line-height: 1; }

/* W-336: Bei einem Foul steht auf der Seite des Verursachers das Wort „Foul“ statt einer
   Zahl. Der Pfeil zeigt, worauf ein Wert einzahlt — ein Wort zahlt auf nichts ein, und die
   Punkte stehen ohnehin drüben. Dort also kein Zeichen.
   Die Seitenklasse muss mitgeschrieben werden: `.lr-break.is-label` allein wiegt weniger
   als `.lr-p1 .lr-break:not(:empty)` (`:not()` zählt wie eine Klasse) — die Regel stünde
   sonst zwar hier, verlöre aber gegen die darüber und der Pfeil bliebe stehen. */
.lr-p1 .lr-break.is-label::after,
.lr-p2 .lr-break.is-label::before { content: none; }

/* W-181: Aktiv-Punkt — nur bei leerem Slot gesetzt, daher ohne Chip-Fläche.
   `::before` zählt für `:empty` nicht mit, die Chip-Regel oben greift also nicht. */
.lr-break.active-dot {
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.lr-break.active-dot::before {
    content: "";
    width: 11px;
    height: 11px;
    border-radius: 50%;
    background: var(--sk-break, #ffd600);
}

.lr-score {
    background: var(--sk-score-bg, #fff);
    color: var(--sk-score-text, #000);
    font-size: 1.55rem;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
    line-height: 1.1;
    box-sizing: border-box;
    width: 3.4rem;
    text-align: center;
    padding: 1px 4px;
    border-radius: var(--ms-r-md);
    border: 1px solid rgba(0,0,0,.15);
}

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

/* Sieger-Markierung, web-weit einheitlich: die Ergebniszahl des Siegers (Frame-Punkte,
   Frame-Stand) steht fett auf einer abgesetzten Pille — Gegenstück zur hinterlegten Zahl der
   App. Die Fläche mischt sich aus der Textfarbe, trägt also auf heller Seite wie auf der
   dunklen Spielstand-Pille und in jedem Skin. Bewusst keine Farbe: Gelb gehört den Breaks,
   Grün/Rot der Bilanz in Kennzahlen und Diagrammen. Gleichstand → keine Pille (W-467). */
.ms-win-box {
    display: inline-block;
    min-width: 2.1em;
    padding: .1rem .45rem;
    border-radius: var(--ms-r-pill);
    line-height: 1.4;
    text-align: center;
}
.ms-win-box.is-win {
    font-weight: 700;
    background: rgba(127, 127, 127, .18);
    background: color-mix(in srgb, currentColor 18%, transparent);
}

.lr-frames {
    background: var(--sk-frames-bg, #333);
    color: var(--sk-frames-text, #fff);
    font-size: var(--ms-fs-base);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    padding: 2px 10px;
    border-radius: var(--ms-r-md);
    white-space: nowrap;
}
/* Tageshistorie: Pille des Siegers innerhalb der Spielstand-Pille — ohne Mehrhöhe. Der
   Spielstand ist dort ohnehin fett, die Fläche allein trägt die Aussage. */
.lr-frames .ms-win-box { min-width: 0; padding: 0 .35em; line-height: inherit; }

/* W-286: Ankündigung „Nächste Partie". Sie tritt an die Stelle des Spielstands, statt
   neben ihm zu stehen — die Kachel behält ihre Höhe, und dass hier gerade nicht gespielt
   wird, ist auf einen Blick klar. Gezeigt wird sie nur, wenn der Server `next_match`
   liefert; `.has-next` sitzt auf dem Block, den auch `applySkin()` trägt. */
.live-block.has-next .lr-body,
.live-block.has-next .lr-head .lr-shootout { display: none; }

/* Die Statistik-Klappe gehört zur beendeten Partie — sie zeigt deren Zahlen. Sobald die
   nächste angekündigt wird, gehört sie weg (dieselbe Regel wie bei den Overlays `stats`
   und `fullstats`); die Auswertung bleibt über die Tageshistorie erreichbar. */
.live-block.has-next .lr-statsbar,
.live-block.has-next .lr-stats { display: none; }

.lr-next {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    padding: 8px 12px;
    min-height: 46px;
}
.lr-next[hidden] { display: none; }

.lr-next-label {
    font-size: var(--ms-fs-meta);
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
    opacity: var(--ms-dim-3);
    /* W-293: Die Beschreibung des angekündigten Spiels hängt mit dran und darf 60 Zeichen
       haben — abschneiden statt umbrechen, sonst wächst die Kachel aus der Reihe. */
    max-width: 100%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.lr-next-pair {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    max-width: 100%;
    min-width: 0;
}

.lr-next-side {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}

.lr-next-name {
    font-size: var(--ms-fs-emph);
    font-weight: 400;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}

.lr-next-icon {
    width: 24px;
    height: 24px;
    border-radius: var(--ms-r-sm);
    object-fit: cover;
    flex: 0 0 auto;
}
.lr-next-icon[hidden] { display: none; }

/* Der Modus steht in denselben Farben wie der Framestand im Spiel — gleiche Stelle,
   gleiche Schreibweise, damit die Kachel beim Umschalten ihre Marke behält. */
.lr-next-mode {
    background: var(--sk-frames-bg, #333);
    color: var(--sk-frames-text, #fff);
    font-size: var(--ms-fs-base);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    padding: 2px 10px;
    border-radius: var(--ms-r-md);
    white-space: nowrap;
    flex: 0 0 auto;
}

.lr-next-meta {
    font-size: var(--ms-fs-small);
    opacity: var(--ms-dim-3);
}
.lr-next-meta[hidden] { display: none; }

/* V51: Die laufende Übung — dieselbe Stelle und dieselbe Bauart wie die Ankündigung, damit
   die Kachel ihre Höhe behält. Sie verdrängt zusätzlich die Ankündigung selbst: an einem
   Tisch, an dem gerade geübt wird, ist das Übernächste nicht die Nachricht. */
.live-block.has-training .lr-body,
.live-block.has-training .lr-next,
.live-block.has-training .lr-head .lr-shootout,
.live-block.has-training .lr-statsbar,
.live-block.has-training .lr-stats { display: none; }

.lr-training {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    padding: 8px 12px;
    min-height: 46px;
}
.lr-training[hidden] { display: none; }

/* Erste Zeile: „Übung: Pink endlos". Sie trägt jetzt den Namen der Übung und ist damit
   Inhalt, keine Beschriftung mehr — deshalb weder Versalien noch gedämpft. */
.lr-training-label {
    font-size: var(--ms-fs-body);
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
    min-width: 0;
}

.lr-training-player {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    max-width: 100%;
    min-width: 0;
}

.lr-training-name {
    font-size: var(--ms-fs-body);
    opacity: var(--ms-dim-1);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}

.lr-training-icon {
    width: 24px;
    height: 24px;
    border-radius: var(--ms-r-sm);
    object-fit: cover;
    flex: 0 0 auto;
}
.lr-training-icon[hidden] { display: none; }

/* Der Fortschritt steht dort, wo sonst der Punktstand steht: die Aussage, die den
   Zuschauer angeht. Er kürzt nie. */
.lr-training-attempt {
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    flex: 0 0 auto;
    background: var(--sk-frames-bg, #333);
    color: var(--sk-frames-text, #fff);
    font-weight: 700;
    padding: 2px 10px;
    border-radius: var(--ms-r-md);
}

.active-indicator {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    display: inline-block;
    animation: pulse 1.5s infinite;
}

.active-indicator.active {
    background: var(--ms-online);
}

.active-indicator.paused {
    background: #ff9800;
    animation: none;
}

.active-indicator.inactive {
    background: var(--ms-offline);
    animation: none;
}

@keyframes pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.4; }
}

.live-event-title {
    font-size: 2.6rem;
    font-weight: 700;
    line-height: 1.15;
}

.live-event-title .live-vs {
    font-size: 1.3rem;
    font-weight: 400;
    opacity: var(--ms-dim-2);
    vertical-align: middle;
}

.live-event-subtitle {
    font-size: 1.2rem;
    font-weight: 400;
    opacity: var(--ms-dim-2);
}

/* ---------------------------------------------------------------------------
   W-137/W-477: Livebild und Terminkalender an derselben Stelle
   ---------------------------------------------------------------------------
   DIE FLÄCHE IST EIN `ms-panel`, kein nachgebauter Rahmen. Radius, Innenabstand
   und Kopfzeile stehen dort und werden hier nicht ein zweites Mal erfunden — der
   erste Anlauf hatte genau das getan und sich damit eigene Abstände und Radien
   eingehandelt. Der Umschalter sitzt im vorgesehenen Slot `ms-panel-action` in
   der Kopfzeile, statt auf dem Bild zu schweben.

   DIE HÖHE KOMMT AUS DER TERMINLISTE, NICHT AUS DEM BILD. Fünf Zeilen und zwei
   Monats-Überschriften ergeben ein Maß, das feststeht, bevor irgendetwas geladen
   ist; das Standbild wird in dieses Maß gestellt. Deshalb muss das Seitenver-
   hältnis der Vereinsbilder nicht bekannt sein, und es springt weder beim Ein-
   treffen des Bildes noch beim Wechsel. Fallen die fünf Termine in mehr als zwei
   Monate, scrollt die Liste innen — die Fläche wächst nicht.

   GETRENNT WIRD DURCH FLÄCHE UND ABSTAND, NICHT DURCH LINIEN (N9): drei Töne der
   bestehenden Familie ineinander — Panel, darin die Zeilen-Kachel, darin das
   Datums-Kästchen.
   --------------------------------------------------------------------------- */
.live-media {
    max-width: min(100%, 640px);
    margin-left: auto;
    margin-right: auto;
}

/* Zeilenhöhe, Zeilenabstand und Überschriftshöhe stehen hier einmal — die Höhe
   der Fläche darunter rechnet mit genau diesen Werten, statt sie zu schätzen. */
.live-media-stage {
    --lk-row: 4.15rem;
    --lk-gap: .4rem;
    --lk-month: 1.7rem;
    --lk-blende: .4rem;
    --lk-hoehe: calc(5 * var(--lk-row) + 4 * var(--lk-gap) + 2 * var(--lk-month));
    position: relative;
    height: var(--lk-hoehe);
}

.live-media-pane {
    position: absolute;
    inset: 0;
}

/* W-450: IST DAS LIVEBILD DER GEZEIGTE REITER, KOMMT DIE HÖHE VOM BILD.
   Die Terminlisten-Höhe oben ist das Maß für die Termine — für ein Querformat war sie
   schlicht zu viel: auf dem Handy ist die Fläche rund 330 px breit, ein 16:9-Motiv darin
   186 px hoch, und die übrigen 226 px standen als leerer Panel-Grund darunter. Gemeldet
   wurde genau das.

   Die Fläche darf deshalb mitgehen, und dafür muss das Bild-Pane aus der Überlagerung
   heraus: nur ein Kind im normalen Fluss trägt eine Höhe nach oben. Das Termine-Pane
   bleibt absolut liegen und behält sein festes Maß — der Umschalter ändert die Höhe damit
   sichtbar, und das ist der Preis, den diese Meldung bewusst zahlt.

   `--lk-hoehe` bleibt die Obergrenze: ein Hochformat soll die Seite nicht auseinander-
   schieben. Diese Grenze steckt schon in der Breitenformel des Bildes unten, weshalb hier
   kein zweiter Deckel nötig ist. */
.live-media-stage:has(> .live-media-pane[data-ms-pane="bild"]:not(.d-none)) {
    height: auto;
}
.live-media-stage:has(> .live-media-pane[data-ms-pane="bild"]:not(.d-none))
    > .live-media-pane[data-ms-pane="bild"] {
    position: static;
}

/* Das Bild steht ohne Unterlage auf dem Panel — wie der Livestream (`.stream-embed`),
   wo das Medium selbst die Fläche ist. Hier lag zuerst eine eigene Fläche im Ton der
   Terminzeilen, gedacht als Blende für abweichende Bildformate. Sie wurde zum Rahmen:
   die Höhe kommt aus der Terminliste (≈ 412 px), ein querformatiges Bild ist bei voller
   Breite rund 342 px hoch — die übrigen 70 px standen in einem zweiten Ton oben und
   unten. Ohne die Fläche ist der Rest Panel-Grund und liest sich als Luft. */
.live-media-pane[data-ms-pane="bild"] {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--lk-blende);
}

/* Auch ein kleines Kamerabild soll die Fläche ausfüllen, ohne sich zu verziehen.
   Die Breite ist deshalb die kleinere von zwei Größen: der vollen Fläche und der
   Breite, die das Motiv bei voller Höhe hätte. Die Höhe folgt daraus von selbst und
   überschreitet die Fläche nie — ein bloßes `max-height` täte es nicht, denn bei
   gesetzter Breite staucht der Browser das Bild, statt die Breite nachzurechnen.
   `--lk-ar` ist das Seitenverhältnis des geladenen Motivs; es kommt aus `index.php`,
   weil nur der Browser es kennt.

   Die Box ist damit so groß wie das Bild darin — nur so rundet `border-radius` das
   Motiv selbst. `object-fit` und `max-height` greifen erst, wenn das Verhältnis
   fehlt (kein JavaScript): dann bleibt wenigstens das Motiv unverzerrt.

   W-450: `aspect-ratio` hält den Platz schon frei, bevor das Motiv geladen ist. Seit die
   Bühne dem Bild folgt statt eine feste Höhe zu halten, wäre der Aufbau sonst kurz flach
   und würde beim Eintreffen des Bildes nach unten springen. Der Rückfall 16:9 ist die
   übliche Kameraform; sobald `--lk-ar` steht, korrigiert sich die Höhe auf das echte
   Verhältnis. `height: auto` bleibt, damit ein Motiv mit anderem Verhältnis nicht
   beschnitten in die reservierte Form gezwungen wird. */
.live-webcam-img {
    display: block;
    width: min(100%, calc((var(--lk-hoehe) - 2 * var(--lk-blende)) * var(--lk-ar, 1.7778)));
    aspect-ratio: var(--lk-ar, 1.7778);
    height: auto;
    max-height: 100%;
    object-fit: contain;
    border-radius: var(--ms-r-md);
}

/* Füllen die Zeilen die Fläche nicht aus, sitzen sie mittig. `safe` verhindert
   den bekannten Fall, dass zentrierter Inhalt bei Überlänge oben abgeschnitten
   wird und nicht mehr zu erreichen ist. */
.live-kalender {
    height: 100%;
    overflow: auto;
    display: flex;
    flex-direction: column;
    justify-content: center;
    justify-content: safe center;
}

.lk-month {
    display: block;
    padding: 0 .2rem .35rem;
    font-size: var(--ms-fs-meta);
    font-weight: 700;
    letter-spacing: .09em;
    text-transform: uppercase;
    opacity: var(--ms-dim-3);
}

.live-kalender-list + .lk-month {
    padding-top: .8rem;
}

.live-kalender-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--lk-gap);
}

/* Links das Datums-Kästchen in fester Breite, rechts der Rest. `stretch` gibt
   dem Kästchen die volle Zeilenhöhe, damit es ein Blatt ist und kein Aufkleber. */
.lk-row {
    display: grid;
    grid-template-columns: 3.6rem 1fr;
    align-items: stretch;
    gap: .7rem;
    min-height: var(--lk-row);
    padding: .35rem .6rem .35rem .35rem;
    border-radius: var(--ms-r-md);
    background: var(--ms-surface);
}

body.light-mode .lk-row {
    background: #fff;
}

.lk-date {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: .2rem .15rem;
    border-radius: var(--ms-r-sm);
    background: var(--ms-surface-variant);
}

body.light-mode .lk-date {
    background: #f1f3f5;
}

.lk-day {
    font-size: 1.15rem;
    font-weight: 700;
    line-height: 1.1;
    font-variant-numeric: tabular-nums;
}

.lk-wd {
    font-size: var(--ms-fs-meta);
    font-weight: 600;
    line-height: 1.2;
    letter-spacing: .08em;
    text-transform: uppercase;
    opacity: var(--ms-dim-2);
}

/* „ganztägig" steht an derselben Stelle wie eine Uhrzeit und ist dreimal so
   lang — im Kästchen wird gekürzt, nicht umgebrochen. */
.lk-time {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: var(--ms-fs-meta);
    font-weight: 600;
    line-height: 1.2;
    font-variant-numeric: tabular-nums;
    opacity: var(--ms-dim-3);
}

.lk-body {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: .1rem;
    min-width: 0;
}

/* Titel und Ort bleiben je eine Zeile: bei Platzmangel wird gekürzt, nicht
   umgebrochen (N-Regel „kürzen statt umbrechen"). */
.lk-title {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: var(--ms-fs-body);
    font-weight: 600;
    line-height: 1.25;
}

.lk-meta {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: var(--ms-fs-small);
    line-height: 1.25;
    opacity: var(--ms-dim-3);
}

.live-kalender-note {
    margin: 0;
    padding: .6rem;
    text-align: center;
    font-size: var(--ms-fs-small);
    opacity: var(--ms-dim-3);
}

/* Die beiden Reiter tragen dieselbe Machart wie die Reiter in Stats+
   (`stats.css`, `.stats-tabs`): der nicht gewählte steht als Chip auf einer
   dauerhaft sichtbaren, gedeckten Bedienfläche (W-66), der gewählte legt den Chip
   ab und trägt stattdessen den Markenton als Unterstrich. Die Live-Seite lädt
   `stats.css` nicht — deshalb stehen hier dieselben Ableitungen aus denselben
   Marken, nicht eigene Werte.

   Der Helligkeitsunterschied steckt wie dort in einer Marke, nicht in einer
   zweiten `background`-Regel: `body.light-mode .live-media-tab` wäre spezifischer
   als `.live-media-tab.active` — der gewählte Reiter behielte im Hellmodus seinen
   Chip. */
.live-media-tab {
    --lmt-chip: color-mix(in srgb, var(--ms-action) 15%, transparent);
    --lmt-chip-hover: color-mix(in srgb, var(--ms-action) 24%, transparent);
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    padding: .25rem .6rem;
    border: 0;
    border-radius: var(--ms-r-sm);
    background: var(--lmt-chip);
    color: var(--ms-action-link);
    font-size: var(--ms-fs-label);
    font-weight: 600;
    transition: background-color .12s ease;
}

body.light-mode .live-media-tab {
    --lmt-chip: color-mix(in srgb, var(--ms-action) 10%, transparent);
    --lmt-chip-hover: color-mix(in srgb, var(--ms-action) 20%, transparent);
}

.live-media-tab:hover {
    background: var(--lmt-chip-hover);
}

/* Links das Bild, rechts die Termine — die Kopfzeile hält sie auseinander. */
.live-media-tab-rechts {
    margin-left: auto;
}

.live-media-tab.active,
.live-media-tab.active:hover {
    background: none;
    color: var(--bs-body-color);
    border-radius: var(--ms-r-sm) var(--ms-r-sm) 0 0;
    border-bottom: 2px solid var(--ms-action);
}

body.ms-skinned .live-media-tab.active {
    border-bottom-color: var(--skin-marked);
}

.live-media-tab .bi {
    font-size: var(--ms-fs-small);
    opacity: var(--ms-dim-2);
}

.live-doc-link {
    font-size: var(--ms-fs-emph);
}

.live-doc-link a {
    text-decoration: underline;
}

.live-total {
    display: inline-block;
}

.live-total-row {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 1rem;
}

.live-score-adm {
    display: flex;
    flex-direction: column;
    gap: .25rem;
}

.live-score-adm .btn {
    min-width: 2.6rem;
    font-weight: 700;
    line-height: 1;
}

.total-score {
    font-size: 3rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.live-total-sep {
    padding: 0 .35rem;
    opacity: var(--ms-dim-2);
}

/* ---------------------------------------------------------------------------
   W-297: „Wettkampf einstellen" — Stift im Live-Kopf und das Formular dahinter.

   Der Stift ist die einzige Bedienspur in einem sonst rein zuschauenden Kopf und
   soll ihn nicht stören: gedeckt, ohne Rahmen, erst beim Zeigen deutlich. Dieselbe
   Klasse ist für weitere Stellen gedacht (Tisch-Zeile, W-299) — deshalb `ms-`.
   --------------------------------------------------------------------------- */
.ms-inline-edit {
    margin-left: .5rem;
    padding: .15em .35em;
    border: 0;
    background: none;
    color: inherit;
    opacity: var(--ms-dim-4);
    font-size: .55em;
    line-height: 1;
    vertical-align: middle;
    border-radius: var(--ms-r-sm);
    cursor: pointer;
}

.ms-inline-edit:hover,
.ms-inline-edit:focus-visible { opacity: 1; }

body.dark-mode .ms-inline-edit:hover { background: rgba(255, 255, 255, .1); }
body.light-mode .ms-inline-edit:hover { background: rgba(0, 0, 0, .07); }

/* Ohne eingerichteten Titel hat die Seite keine Überschrift — Admins bleibt an dieser
   Stelle allein der Stift (Besucher sehen dort nichts). Allein im leeren Kopf ginge ein
   gedeckter Stift unter, deshalb steht er hier auf einer gedeckten Fläche und ist von
   Anfang an voll sichtbar: keine Umrandung, nur Fläche und Größe (N9). */
.live-head-empty {
    font-size: 1.4rem;
    font-weight: 600;
    line-height: 1;
}

.live-head-empty .ms-inline-edit {
    margin-left: 0;
    padding: .5rem .75rem;
    border-radius: var(--ms-r-md);
    opacity: 1;
    font-size: .85em;
}

body.dark-mode .live-head-empty .ms-inline-edit { background: rgba(255, 255, 255, .12); }
body.light-mode .live-head-empty .ms-inline-edit { background: rgba(0, 0, 0, .08); }

body.dark-mode .live-head-empty .ms-inline-edit:hover,
body.dark-mode .live-head-empty .ms-inline-edit:focus-visible { background: rgba(255, 255, 255, .22); }
body.light-mode .live-head-empty .ms-inline-edit:hover,
body.light-mode .live-head-empty .ms-inline-edit:focus-visible { background: rgba(0, 0, 0, .15); }

.live-head-form {
    max-width: 900px;
    margin: 0 auto 1.5rem;
    padding: 1.25rem;
    border-radius: var(--ms-r-lg);
    /* W-330: „Einschalten" im Band springt hierher — und der Schalter, um den es dabei
       geht, sitzt ganz oben im Formular. Ohne diesen Abstand endet der Sprung unter der
       klebenden Kopfleiste, und man landet vor einem Formular, dessen entscheidende Zeile
       verdeckt ist. `--ms-navbar-h` wird in includes/header.php gemessen. */
    scroll-margin-top: calc(var(--ms-navbar-h, 4rem) + .75rem);
}

body.dark-mode .live-head-form { background: var(--ms-surface-variant); color: #fff; }
body.light-mode .live-head-form { background: #f1f3f5; color: #212529; }

/* Der Sichtbarkeits-Schalter (W-297). Er entscheidet als einziges Feld im Formular
   darüber, ob draußen jemand mitliest — deshalb eigene Fläche über allem anderen,
   größerer Schalter, Zustand im Klartext. Abgesetzt wird über die Fläche, nicht über
   einen Rahmen. */
.lhf-public {
    padding: .85rem 1rem;
    margin-bottom: 1.25rem;
    border-radius: var(--ms-r-md);
}

body.dark-mode  .lhf-public { background: rgba(255, 255, 255, .06); }
body.light-mode .lhf-public { background: rgba(0, 0, 0, .05); }
body.dark-mode  .lhf-public.is-on { background: rgba(93, 150, 106, .24); }
body.light-mode .lhf-public.is-on { background: rgba(93, 150, 106, .18); }

.lhf-public-switch .form-check-input {
    width: 2.75em;
    height: 1.4em;
    margin-top: .08em;
}

.lhf-public-switch .form-check-label {
    font-size: var(--ms-fs-emph);
    font-weight: 600;
    padding-left: .35rem;
}

.lhf-public-state {
    margin-top: .35rem;
    font-size: var(--ms-fs-label);
    opacity: var(--ms-dim-2);
}

/* W-310: Der Satz, der für alle Felder gilt, steht über dem Raster statt siebenmal an
   den Labels. Dieselbe zurückgenommene Schrift wie der Hinweis am Fuß des Formulars. */
.lhf-lead {
    margin-top: 1rem;
    margin-bottom: .75rem;
    font-size: var(--ms-fs-label);
    opacity: var(--ms-dim-2);
}

.lhf-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1rem 1.25rem;
}

/* Die Schalter tragen ganze Sätze und brauchen die volle Breite. */
.lhf-switches {
    grid-column: 1 / -1;
    display: flex;
    flex-direction: column;
    gap: .5rem;
}

.lhf-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .75rem;
    margin-top: 1.25rem;
}

/* „Wettkampf beenden" ist kein Nachbar von „Speichern" — es rutscht ans andere Ende. */
.lhf-end { margin-left: auto; }

.lhf-hint {
    margin-top: .75rem;
    font-size: var(--ms-fs-label);
    opacity: var(--ms-dim-2);
}

@media (max-width: 640px) {
    .lhf-grid { grid-template-columns: minmax(0, 1fr); }
    .lhf-actions { flex-direction: column; align-items: stretch; }
    .lhf-actions .btn { width: 100%; }
    .lhf-end { margin-left: 0; margin-top: .5rem; }
}

@media (max-width: 768px) {
    .lr-body { gap: 2px; padding: 5px 6px; }
    .lr-player { gap: 3px; padding: 2px 1px; }
    .lr-score { font-size: var(--ms-fs-emph); width: 1.85rem; padding: 1px 1px; }
    .lr-name { font-size: var(--ms-fs-small); }
    .lr-icon { width: 16px; height: 16px; }
    .lr-break { font-size: var(--ms-fs-small); min-width: 1.1ch; }
    /* Leeres Break belegt sonst ~11px pro Name; auf Mobil ausblenden, damit die
       Namen mind. 16 Zeichen zeigen. Bei laufendem Break erscheint der Chip wieder. */
    .lr-break:empty { display: none; }
    .lr-frames { font-size: var(--ms-fs-small); padding: 2px 4px; }
    .lr-next { padding: 6px 6px; }
    .lr-next-pair { gap: 5px; }
    .lr-next-side { gap: 3px; }
    .lr-next-name { font-size: var(--ms-fs-small); }
    .lr-next-icon { width: 16px; height: 16px; }
    .lr-next-mode { font-size: var(--ms-fs-small); padding: 2px 4px; }
    .lr-next-meta { font-size: var(--ms-fs-meta); }
    .live-event-title {
        font-size: 1.9rem;
    }
    .live-event-title .live-team,
    .live-event-title .live-vs {
        display: block;
    }
    .live-event-title .live-vs {
        font-size: var(--ms-fs-base);
        margin: .1rem 0;
    }
    .live-event-subtitle {
        font-size: var(--ms-fs-emph);
    }
    .total-score {
        font-size: 2.4rem;
    }
}

.visitor-count {
    font-size: var(--ms-fs-label);
    opacity: var(--ms-dim-3);
}

.stream-embed {
    position: relative;
    padding-bottom: 56.25%;
    height: 0;
    overflow: hidden;
    border-radius: var(--ms-r-md);
}

.stream-embed iframe {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    border: 0;
}

/* DS-2: Klick-zum-Laden-Gate für Drittanbieter-Streams. Kein iframe/Netzwerk, bis der
   Besucher zustimmt. Gedeckte Flächen, keine Trennlinien (Konvention). */
/* W-409: Die Fläche „Aktuell läuft kein Livestream" liegt an derselben Stelle und in
   derselben Größe wie das Gate, teilt sich also dessen Aufbau — nur nicht seinen Ton
   (siehe unten bei --ms-surface). */
.stream-consent__gate,
.stream-offline {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: .5rem;
    padding: 1rem;
    text-align: center;
}
body.dark-mode .stream-consent__gate { background: var(--ms-surface-variant); color: #dfe3e8; }
body.light-mode .stream-consent__gate { background: #e9ecef; color: #212529; }
.stream-consent__icon,
.stream-offline__icon { font-size: 2rem; opacity: var(--ms-dim-2); }
.stream-consent__text { font-size: var(--ms-fs-label); margin: 0; max-width: 34ch; }

/* Wo kein Stream läuft, sitzt die Fläche IM Panel „Livestream" — trüge sie dessen Ton,
   verschwämme sie darin zu einem großen leeren Feld. Eine Stufe dunkler (dieselbe Farbe,
   die auch der Seitengrund hat) setzt sie ab, ohne dass es ein Rahmen tun müsste. */
body.dark-mode .stream-offline { background: var(--ms-surface); color: #dfe3e8; }
body.light-mode .stream-offline { background: #dee2e6; color: #212529; }
.stream-offline__text { margin: 0; max-width: 34ch; }
/* Derselbe gedeckte Linkton wie in der Stream-Liste (.club-streams .stream-link) —
   hier ausgeschrieben, weil die Fläche auch in Tisch-Kacheln steht, also ausserhalb
   jenes Zweigs. Ohne das erbte der Link das grelle Browser-Blau. */
.stream-offline__link { color: var(--ms-action-link); }
.stream-consent__btn {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    padding: .4rem .9rem;
    border: 0;
    border-radius: var(--ms-r-sm);
    background: var(--ms-action);
    color: var(--ms-action-text);
    cursor: pointer;
    font-size: var(--ms-fs-body);
}
.stream-consent__btn:hover { opacity: var(--ms-dim-1); }
.stream-consent__note { font-size: var(--ms-fs-meta); opacity: var(--ms-dim-3); margin: 0; max-width: 34ch; }

/* Kurze Seiten sollen den Footer nicht in der Luft hängen lassen: die Hülle ist so hoch
   wie das Fenster, `main` dehnt sich, der Footer landet am unteren Rand. Bewusst NICHT
   fixiert — bei viel Inhalt scrollt er ganz normal aus dem Bild. */
.ms-shell {
    min-height: 100vh;
    min-height: 100dvh; /* Handy: die sichtbare Höhe, nicht die mit ausgefahrener Adresszeile. */
    display: flex;
    flex-direction: column;
}
.ms-shell > main { flex: 1 0 auto; }

/* W-504: Der Fuß ist EINE zentrierte Zeile — Impressum · Datenschutz · Sprache ·
   MyScoreboard. Bis W-504 ein 3-Spalten-Grid mit Medien-Widerruf, App-Link, Version und
   Changelog-Symbol; die stehen jetzt an ihren eigenen Orten (Streams, Menü, Tooltip).
   Auf dem Handy bricht die Zeile höchstens zwischen zwei Links um, nie in einem. */
.site-footer {
    display: flex;
    justify-content: center;
}
/* W-454: Rückfallfläche ohne Vereins-Skin — dasselbe Paar wie bei der
   Navigationsleiste. Auf Seiten MIT Skin überschreibt `body.ms-skinned
   .site-footer` das hier mit `--skin-surface`; sichtbar wird es also nur, wenn
   kein Verein ermittelbar ist. Bis W-454 trug diese Lage der `border-top`, den
   die Trennlinien-Konvention entfernt hat — ohne Ersatz stünde die Fußzeile dort
   als einziger Rahmenteil ohne eigene Fläche da. */
body.dark-mode  .site-footer { background-color: var(--ms-surface-variant); }
body.light-mode .site-footer { background-color: #f1f3f5; }

.site-footer .footer-line {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: .25rem 1.25rem;
}
.site-footer .footer-line a {
    color: inherit;
    text-decoration: none;
    white-space: nowrap;
    opacity: var(--ms-dim-2);
}
.site-footer .footer-line a:hover { opacity: 1; text-decoration: underline; }
/* W-31d: auf Touch-Geräten etwas Tap-Padding (Optik unverändert, nur die Klickfläche wächst).
   Seitlich nur 2 px: mit den engeren Abständen unten passt die deutsche Zeile so auf
   360 px Breite (gemessen 336 von 336 px); die englische ist länger und darf dort
   zwischen zwei Links umbrechen — Rechtliches wird nicht gekürzt. */
@media (pointer: coarse) {
    .site-footer .footer-line a {
        display: inline-block;
        padding: 6px 2px;
    }
}
@media (max-width: 575.98px) {
    .site-footer .footer-line { gap: .25rem .75rem; }
}

/* W-504: Widerruf der Medien-Einwilligung — seit W-504 unter den Streams statt im Fuß:
   dort, wo eingewilligt wurde. Leise wie der Hinweis im Einwilligungs-Feld. */
.media-consent-revoke {
    display: block;
    margin: .75rem 0 0;
    text-align: center;
}
.media-consent-revoke a {
    font-size: var(--ms-fs-small);
    opacity: var(--ms-dim-3);
    color: inherit;
    text-decoration: underline;
}
.media-consent-revoke a:hover { opacity: var(--ms-dim-1); }

/* W-419: Die Versionsnummer ist Betriebsinfo. Sie steht dort, wo man sie sucht, und
   holt sonst keinen Blick — derselbe Wert wie beim Medien-Widerruf daneben, damit im
   Fuß nicht zwei verschiedene „leise" Stufen nebeneinanderstehen. Gilt auch im Fuß des
   Login-Popups und der Access-Seiten (`ms_access_footer()`). */
.ms-version { font-size: var(--ms-fs-small); opacity: var(--ms-dim-3); }

/* W-419: Das Login-Popup bekommt Kopf und Fuß mit MyScoreboard-Bezug. Rechtliches
   steht bewusst NICHT hier, sondern im Seitenfuß (Entscheid 2026-09-05).
   Die Trennlinien fallen seit W-454 site-weit weg (Regel ganz oben), nicht mehr nur
   hier; die Abschnitte trennt der Abstand. */
.ms-login-modal .modal-header { align-items: flex-start; padding-bottom: .5rem; }
.ms-login-head { display: flex; flex-direction: column; min-width: 0; }
/* Kürzen, nie umbrechen: der Vereinsname bleibt einzeilig, der volle steht im `title`. */
.ms-login-club {
    font-size: var(--ms-fs-label);
    opacity: var(--ms-dim-2);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Der Fuß bleibt EINE Reihe: die MyScoreboard-Zeile füllt die freie linke Hälfte,
   die Knöpfe stehen rechts. Darunter noch eine Zeile zu setzen liest sich wie ein
   Nachtrag — der Dialog ist mit der Knopfreihe zu Ende. */
.ms-login-modal .modal-footer { justify-content: space-between; flex-wrap: nowrap; gap: 1rem; }
.ms-login-note { min-width: 0; }
.ms-login-note > p { margin: 0; text-align: left; white-space: nowrap; }
.ms-login-actions { display: flex; justify-content: flex-end; gap: .5rem; flex: 0 0 auto; }
/* Schmal reicht eine Reihe nicht: die Knöpfe brauchen ihre volle Breite, und die
   Fußzeile abzuschneiden („© 2026 My…") wäre schlechter als sie umzusetzen. Dann
   steht sie unter den Knöpfen — auf dieser Breite ist das die übliche Auflösung. */
@media (max-width: 575.98px) {
    .ms-login-modal .modal-footer { flex-direction: column-reverse; align-items: stretch; gap: .5rem; }
    .ms-login-note > p { text-align: center; }
}

/* Stream in der Tisch-Box (IT-B4) */
.lr-stream {
    /* W-79: kleiner Abstand zur Tisch-Karte darüber (analog zum Statistik-Button). */
    margin-top: 6px;
    padding: 0 12px 12px;
}
/* Bei zugeordnetem Video den Abstand Video→Statistik-Chip an den Abstand Tisch→Video
   (6px margin-top) angleichen: die transparente Stream-Box addierte sonst 12px Bodenrand
   + 7px Statsbar-Kopfabstand (=19px), was zu groß wirkte. Nur wenn ein Statsbar folgt —
   ein Stream als letztes Element (Statistik aus) behält seinen Bodenrand. */
.lr-stream:has(+ .lr-statsbar) { padding-bottom: 0; }
.lr-stream + .lr-statsbar { padding-top: 6px; }

/* V12: Titel eines vereinsweiten Streams — eine Zeile über der Kachel, nie umbrochen. */
.ms-stream-title {
    font-size: var(--ms-fs-label);
    font-weight: 600;
    margin-bottom: 4px;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* W-83: Ausschließlich für Gäste bei nicht-öffentlichem Event (Idle-/Vorschau-Zustand)
   werden konfigurierte Streams dezent gedimmt/entsättigt gezeigt – als Zuschauer-Hinweis,
   dass das Event noch nicht öffentlich läuft. Alle anderen Zustände (öffentlich, angemeldet)
   bleiben unberührt. Hover hebt die Dämpfung fürs Bedienen leicht an. */
.club-streams--dimmed .ms-stream-item {
    opacity: var(--ms-dim-3);
    filter: grayscale(.35);
    transition: opacity .15s ease, filter .15s ease;
}
.club-streams--dimmed .ms-stream-item:hover {
    opacity: var(--ms-dim-1);
    filter: grayscale(.1);
}

/* Was sich nicht einbetten lässt — ein Kanal ohne Video-ID, eine fremde Plattform —, sieht
   jetzt aus wie die Offline-Fläche: gedeckter Grund eine Stufe unter dem Panel, Text mittig.
   Vorher stand dort ein nackter Link linksbündig auf dem Panel-Grund, und dieselbe Kachel
   sah je nach Anbieter grundverschieden aus.
   Die Schriftgröße teilen sich beide Flächen: dieselbe Sache, dieselbe Größe. */
.stream-link,
.stream-offline__text,
.stream-offline__link { font-size: var(--ms-fs-body); }

.stream-link {
    display: block;
    padding: 1rem;
    border-radius: var(--ms-r-md);
    text-align: center;
    word-break: break-all;
}
body.dark-mode .stream-link { background: var(--ms-surface); }
body.light-mode .stream-link { background: #dee2e6; }

/* Einheitliche, aufklappbare Panels für Inhalte ohne Tischbezug (Club-Livestreams,
   Tageshistorie, Livebild/Termine): gleiche Box, gleicher Klapp-Header.

   GETRAGEN WIRD DAS PANEL VOM SCHATTEN, NICHT ALLEIN VOM TON. Vorher hatte es keinen
   Schatten, die Fläche machte die ganze Arbeit — und im Dunkelmodus war ihr Schritt zum
   Seitengrund mit 1,27 gut doppelt so groß wie im Hellen (1,11 von #fff auf #f1f3f5).
   Das las sich als plumper Kasten. Die Tisch-Kacheln machen es umgekehrt: `.activity-tile`
   trägt `--ms-shadow-2` und darf deshalb sehr leicht getönt sein (W-470 hat dort den
   Rahmen ersatzlos gestrichen). Dasselbe hier.

   Der dunkle Ton geht dafür eine Sprosse tiefer auf #212529 — Bootstraps `gray-900`,
   direkt unter `--bs-tertiary-bg` (#2B3035), auf dessen Leiter sich schon die Tokens oben
   berufen. Damit stimmt der Schritt zum Seitengrund mit dem hellen überein.

   Im Hellmodus bleibt der Ton, nur der Schatten kommt dazu — und zwar NICHT aus der
   Leiter: deren Stufen sind auf dunklen Grund gerechnet, .28 Schwarz auf Hellgrau ist ein
   grauer Fleck, kein Schatten. Es sind dieselben zwei Lagen wie bei `--ms-shadow-2`
   (Kontakt + Hof), nur auf hellen Grund gerechnet. */
.ms-panel {
    border-radius: var(--ms-r-lg);
    padding: 6px 16px 16px;
}
body.dark-mode .ms-panel { background: #212529; color: #fff; box-shadow: var(--ms-shadow-2); }
body.light-mode .ms-panel { background: #f1f3f5; color: #212529; box-shadow: 0 1px 2px rgba(0, 0, 0, .08), 0 4px 12px rgba(0, 0, 0, .06); }

.ms-panel-head {
    display: flex;
    align-items: center;
    gap: 12px;
    min-height: 44px;
}
.ms-panel-toggle {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    padding: 8px 6px;
    color: inherit;
    text-decoration: none;
    border-radius: var(--ms-r-md);
}
.ms-panel-toggle:hover { opacity: var(--ms-dim-1); }
.ms-panel-icon { font-size: var(--ms-fs-base); opacity: var(--ms-dim-2); }
.ms-panel-title { font-size: 1.2rem; font-weight: 600; line-height: 1.2; }
.ms-panel-caret { font-size: var(--ms-fs-small); opacity: var(--ms-dim-3); transition: transform .2s ease; }
.ms-panel-toggle[aria-expanded="true"] .ms-panel-caret { transform: rotate(180deg); }
.ms-panel-action { margin-left: auto; }
.ms-panel-body { margin-top: 12px; }

/* Unscheinbarer Verweis auf die stream-freie Monitor-Übersicht. W-457: Hier stand
   „dezent in Skin-Akzentfarbe" — gemeint war `--ms-accent`, und der war nie skinbar. */
.live-overview-link a {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    padding: .3rem .7rem;
    border-radius: var(--ms-r-sm);
    font-size: var(--ms-fs-small);
    color: var(--ms-action-link);
    text-decoration: none;
    background: color-mix(in srgb, var(--ms-action) 9%, transparent);
    opacity: var(--ms-dim-2);
    transition: opacity .15s ease, background .15s ease;
}
.live-overview-link a:hover {
    opacity: 1;
    background: color-mix(in srgb, var(--ms-action) 16%, transparent);
}

/* W-504: App-Hinweis unter den Tischen — dieselbe leise Form wie der Übersicht-Verweis
   darüber, zentriert, mit kleinem ✕. Nur auf Android sichtbar (app-hint.js). */
.ms-app-hint {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: .25rem;
    margin: .75rem 0 0;
    min-width: 0;
}
/* Nie umbrechen (Konvention): reicht der Platz nicht, kürzt die Zeile mit „…“. */
.ms-app-hint a > span { overflow: hidden; text-overflow: ellipsis; }
.ms-app-hint[hidden] { display: none; }
.ms-app-hint a {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    padding: .3rem .7rem;
    border-radius: var(--ms-r-sm);
    font-size: var(--ms-fs-small);
    color: var(--ms-action-link);
    text-decoration: none;
    background: color-mix(in srgb, var(--ms-action) 9%, transparent);
    opacity: var(--ms-dim-2);
}
.ms-app-hint a:hover { opacity: 1; }
.ms-app-hint-close {
    border: 0;
    background: none;
    color: inherit;
    opacity: var(--ms-dim-3);
    min-width: 44px;
    min-height: 44px;
    font-size: 1.1rem;
}
.ms-app-hint-close:hover { opacity: var(--ms-dim-1); }

.club-streams .stream-link { color: var(--ms-action-link); }

/* Streams ohne Tischbezug kacheln wie die Tischanzeige darüber — Raster, Kachel-Deckel
   und Spaltenzahl teilen sie sich seit W-271 mit `.live-tables` (Regel dort). */

/* "Score im Bild": Punkte, Break + Frames ausblenden, nur Namen bleiben */
.score-hidden .lr-score,
.score-hidden .lr-break,
.score-hidden .lr-frames {
    display: none;
}

/* ── Tageshistorie: abgeschlossene Spiele als Tisch-Balken (IT-B6) ── */
/* Kachel-Deckel wie bei den Live-Tischen (W-271) — ohne ihn stünde ein abgeschlossenes
   Spiel auf einem breiten Schirm breiter da als der laufende Tisch darüber. Die
   Spaltenstaffelung nach Anzahl gilt hier bewusst nicht: die Historie ist eine Liste,
   die nach unten wächst, kein Nebeneinander mehrerer Tische. Die Null als Untergrenze der
   Spur gilt hier aus demselben Grund wie bei den Live-Kacheln (W-484): eine Zeile mit nicht
   umbrechendem Inhalt soll kürzen, nicht die Seite seitwärts schieben. */
.history-games {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 10px;
    max-width: var(--ms-tile-max);
    margin-inline: auto;
}
@media (min-width: 992px) {
    .history-games {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        max-width: calc(2 * var(--ms-tile-max) + 10px);
    }
}

/* Verlaufszeilen folgen dem Dark/Light-Theme (keine Skin-Farbe je Spiel). Die
   neutralen Skin-Variablen sitzen am .history-block, damit auch der außerhalb der
   Score-Karte liegende Statistik-Button und das Dropdown sie erben (statt der bunten
   Skin-Farben der Live-Seite → dezenter Hintergrund in der Tageshistorie). */
body.dark-mode .history-block {
    --sk-row-bg: #242424;
    --sk-text: #f1f1f1;
    --sk-frames-bg: #111;
    --sk-frames-text: #fff;
    --sk-break: var(--ms-online, #22c55e);
}
body.light-mode .history-block {
    --sk-row-bg: #eef1f4;
    --sk-text: #212529;
    --sk-frames-bg: #ced4da;
    --sk-frames-text: #212529;
    --sk-break: var(--ms-online, #22c55e);
}
body.light-mode .history-row {
    border-color: rgba(0,0,0,.12);
    /* W-454: bleibt bewusst neben der Schatten-Leiter. Die drei Stufen sind auf
       den dunklen Grund gerechnet; .28 Schwarz auf Hellgrau ist ein Balken, kein
       Schatten. Das hier ist die Hell-Entsprechung von --ms-shadow-1. */
    box-shadow: 0 1px 2px rgba(0,0,0,.08);
}

.history-row .lr-meta { font-variant-numeric: tabular-nums; opacity: var(--ms-dim-1); }
/* W-233: Ein abgeschlossenes Spiel hat keinen laufenden Punktestand — deshalb trägt die
   Historie keine Punkte-Pillen, sondern zeigt den Framestand mittig in derselben Form wie
   die laufende Zeile (`frames1 (Bo) frames2`). Vorher standen die Frames IN den Pillen und
   damit an dem Platz, der auf derselben Seite eine Zeile höher die Punkte meint. */
.history-row .lr-body { grid-template-columns: minmax(0,1fr) auto minmax(0,1fr); }
.history-row .lr-center { grid-column: 2; }
/* W-235: Die laufende Zeile lässt die Spielerfläche ihre Spalte füllen, damit der Break
   an die Punkte-Pille rücken kann. Die Historie hat keinen Break — dort bleibt die Fläche
   am Inhalt, sonst zöge sich die Sieger-Hinterlegung über die halbe Zeile. */
.history-row .lr-p1 { justify-self: start; }
.history-row .lr-p2 { grid-column: 3; justify-self: end; }
/* W-237: Nicht der Sieger wird hervorgehoben, sondern der Verlierer leicht zurückgenommen.
   Vorher trug der Sieger eine getönte Fläche plus Fettschrift — die Fläche sah aus wie die
   Frames-Pille und der Statistik-Knopf daneben, las sich also als Bedienelement oder
   Statusfeld und nicht als „hat gewonnen". Zurücknehmen kommt ohne Fläche, ohne Farbe und
   ohne zweites Schriftgewicht aus; wer gewonnen hat, steht ohnehin im Spielstand daneben.
   Die Zurücknahme gilt der ganzen Spielerfläche, damit ein Vereinswappen nicht in voller
   Deckkraft neben einem gedimmten Namen steht. */
.history-row .lr-player.lr-loser { opacity: var(--ms-dim-3); }

/* Tisch-Block = Score-Karte + (darunter, außerhalb der Karte) Stats-Button/Dropdown. */
.live-block { display: flex; flex-direction: column; }

.lr-statsbar {
    padding: 7px 12px 2px;
    text-align: center;
}
/* Ausklapp-Button als kleiner Chip in den Skin-Farben des Tisches: sitzt UNTER der
   Score-Karte auf dem Seitenhintergrund → klar dem Tisch zugehörig, aber nicht Teil
   der Scoring-Zeile. Vorbild: „Statistik"-Button der Alt-SW. */
.lr-statstoggle {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 4px 16px;
    font-size: var(--ms-fs-meta);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .05em;
    text-decoration: none;
    cursor: pointer;
    color: var(--sk-text, #fff);
    background: var(--sk-row-bg, #1a1a1a);
    border: 1px solid color-mix(in srgb, var(--sk-text, #fff) 22%, transparent);
    border-radius: var(--ms-r-md);
    box-shadow: var(--ms-shadow-1);
    opacity: var(--ms-dim-1);
    transition: opacity .15s ease, filter .15s ease;
}
.lr-statstoggle:hover { opacity: 1; filter: brightness(1.08); }
.lr-stats-caret { font-size: var(--ms-fs-meta); opacity: var(--ms-dim-1); transition: transform .2s ease; }
.lr-statstoggle[aria-expanded="true"] .lr-stats-caret { transform: rotate(180deg); }
/* W-31d: auf Touch-Geräten den bewusst kleinen „Statistik"-Chip behutsam auf eine
   komfortablere Tap-Höhe (~38px) bringen — Desktop-Chip bleibt kompakt. */
@media (pointer: coarse) {
    .lr-statstoggle { padding-block: 8px; }
}

/* Dropdown: eigene, bewusst schmalere Karte unter der Tisch-Zeile, zentriert
   (Alt-SW: ~80%). Runde Ecken passend zur Score-Karte.

   W-270: Die Karte trug den vollen Markenton der Zeile. Auf einer Fläche dieser Größe
   wird daraus bei satten Skins eine Leuchtfläche — Dresdens Gelb füllte den halben
   Bildschirm. Sie geht deshalb denselben Weg wie Kopf und Fuß der Seite (Konvention
   A-13, Markenton vs. Flächenton): derselbe Farbton, aber stark heruntergemischt.
   Neutrales Grau wäre die Alternative gewesen und ist bewusst nicht gewählt — die
   Tageshistorie ist neutral grau, und genau davon muss sich ein Live-Tisch unterscheiden. */
/* V56/W-44: Bezugsgröße für den Foto-Kopf des Dropdowns — die volle Kachelbreite, also
   genau der Platz, den die Tisch-Anzeige darüber einnimmt. Ob die Porträts außen neben
   den Block passen, hängt daran und nicht an der Schirmbreite: zwei Kacheln nebeneinander
   sind auch auf einem Desktop einzeln schmal. Browser ohne Container-Queries stellen die
   Fotos immer über den Block — das ist die schmale Ansicht, kein Bruch. */
.lr-stats { padding: 4px 12px 4px; container-type: inline-size; }
.lr-stats-card {
    /* Fläche und Rahmen liegen als Variablen vor, weil der Foto-Kopf sie mitbenutzt: die
       „Ohren" (V56/W-44, unten) sind Fortsätze genau dieser Karte und müssen denselben
       Ton und dieselbe Linie tragen — sonst sitzt neben der Karte ein zweites Kästchen. */
    --ms-stats-card-bg: var(--sk-row-bg, #1a1a1a);
    --ms-stats-card-line: color-mix(in srgb, var(--sk-text, #fff) 16%, transparent);
    --ms-stats-card-pad-x: 18px;
    --ms-stats-card-pad-y: 14px;
    max-width: 78%;
    margin-inline: auto;
    background: var(--ms-stats-card-bg);
    color: var(--sk-text, #fff);
    border: 1px solid var(--ms-stats-card-line);
    border-radius: var(--ms-r-lg);
    padding: var(--ms-stats-card-pad-y) var(--ms-stats-card-pad-x);
    box-shadow: var(--ms-shadow-2);
}
/* Herausgenommen wird allein die **Buntheit**, nicht die Helligkeit: derselbe Farbton,
   Chroma auf ein Drittel (Dresdens Gelb wird ein ruhiger Sandton). Weil die Helligkeit
   bleibt, gelten `--sk-text` und die Break-Farbe unverändert weiter — keine einzige
   Kontrast-Zusage der Skin-Palette (Konventionen N2/N3) muss hier neu verhandelt werden.
   Ein Mischen zu Schwarz/Weiß hin kann das nicht: es verschöbe die Helligkeit und damit
   genau diese Zusagen. Nebeneffekt, der so gewollt ist: ohnehin stille Skins (Anthrazit,
   Scoring) und die neutrale Tageshistorie ändern sich praktisch nicht, laut wird leise.
   Braucht die relative Farb-Syntax; wo sie fehlt, bleibt es bei der Fläche oben. */
@supports (color: oklch(from white l c h)) {
    .lr-stats-card {
        --ms-stats-card-bg: oklch(from var(--sk-row-bg, #1a1a1a) l calc(c * 0.33) h);
    }
}
@media (max-width: 575px) {
    .lr-stats-card {
        --ms-stats-card-pad-x: 12px;
        --ms-stats-card-pad-y: 12px;
        max-width: 100%;
    }
}

/* ── Live-Stats-Dropdown: Frame-Tabelle + Kennzahlen (an alter SW orientiert) ── */
/* W-270: Drei Stufen statt einer flachen Fläche — wie in der Alt-SW stand jeder Block
   (Frame-Tabelle, Kennzahlen, Zeiten) eine Stufe von der Karte ab, die Zebra-Zeilen
   noch eine weiter. Alle Stufen sind Tönungen der Schriftfarbe, nicht der Skin-Farben:
   so sitzen sie in jedem Skin und in beiden Modi gleich weit auseinander. */
.lr-fstats {
    font-size: var(--ms-fs-small);
    --ms-stats-block: color-mix(in srgb, var(--sk-text, #fff) 5%, transparent);
    --ms-stats-zebra: color-mix(in srgb, var(--sk-text, #fff) 11%, transparent);
    --ms-stats-band: color-mix(in srgb, var(--sk-text, #fff) 19%, transparent);
}
.lr-fstats-frames,
.lr-fstats-figures,
.lr-fstats-times {
    background: var(--ms-stats-block);
    border-radius: var(--ms-r-md);
    overflow: hidden;
}
.lr-fstats-empty, .lr-fstats-loading { padding: 6px 0; text-align: center; }

/* Foto-Kopf (W-41): je Spielerseite ein Porträt, Formatierung wie Scoreboard (V2).

   V56/W-44: Die Porträts klebten an den Außenkanten, dazwischen war auf einem breiten
   Schirm fast die halbe Zeile leer. Beschriftet wird der Kopf deshalb NICHT — der Name
   steht schon auf der Kachel darüber und als Kopfzeile der Kennzahlen (W-219). Gelöst
   wird der Freiraum im Layout, in zwei Stufen:

   · schmale Karte — beide Porträts mittig nebeneinander über der Tabelle. Der Freiraum
     liegt außen und liest sich dort als Rand, nicht als Loch.
   · breite Karte  — die Porträts rücken nach außen und die Frame-Tabelle zwischen sie.
     Der Platz ist dann kein Rest mehr, sondern die Tabelle selbst.

   Maßstab ist die Breite der Kachel (Container-Query an `.lr-stats`), nicht die des
   Schirms: ab 992 px stehen zwei Kacheln nebeneinander, jede einzelne bleibt schmal. Ab
   600 px Kachelbreite bleiben dem Block noch rund 400 px — genug für fünf Spalten samt
   Break-Listen. Auf Full HD sind es bei zwei Tischen gut 700 px.

   Damit die Porträts den Block nicht schmaler machen, wächst die Karte in dieser Stufe
   auf die volle Kachelbreite: Tabellen behalten ihren Platz, die Fotos docken außen an.
   Breiter als die Tisch-Anzeige darüber wird der Aufklapper dabei nie.

   Das Foto ist in beiden Stufen gleich hoch (100 px): es soll ein Kopf bleiben, keine
   Bühne. Seine Breite ist damit 75 px (3:4) — die Zahl steht unten als Spaltenmaß. */
.lr-fstats-photos {
    display: flex;
    justify-content: center;
    align-items: flex-start;
    gap: 12px;
    margin-bottom: 8px;
}
/* Ohne Foto kein Platzhalter: schmal stehen zwei Porträts als Paar mittig. Fehlt eines,
   rückt das andere auf die Seite seines Spielers (User-Intake 2026-10-03) — mittig stand
   es da, als gehöre es keinem. Breit wird der leere Slot wieder eingeblendet — dort
   hält er die Spalte (siehe Container-Block). */
.lr-fstats-photo-slot:empty { display: none; }
.lr-fstats-photos:has(.lr-fstats-photo-slot.p2:empty) { justify-content: flex-start; }
.lr-fstats-photos:has(.lr-fstats-photo-slot.p1:empty) { justify-content: flex-end; }
.lr-fstats-photo {
    height: 100px;
    width: auto;
    aspect-ratio: 3 / 4;
    object-fit: cover;
    border-radius: var(--ms-r-md);
    border: 2px solid color-mix(in srgb, var(--sk-text, #fff) 18%, transparent);
    box-shadow: var(--ms-shadow-1);
    background: color-mix(in srgb, var(--sk-text, #fff) 4%, transparent);
    display: block;
}
@media (max-width: 575px) {
    .lr-fstats-photos { gap: 8px; }
}

/* Breite Kachel: die Porträts sitzen in „Ohren" — Fortsätzen der Karte, die links und
   rechts aus ihr herauswachsen. Rahmen und Fläche laufen um sie herum, weil jedes Ohr
   dieselbe Fläche, dieselbe Linie und dieselbe Polsterung trägt wie die Karte und ihre
   Kante um genau die Rahmenbreite überdeckt: die senkrechte Linie der Karte endet dort,
   die des Ohrs setzt sie fort. Ein Ohr ist damit keine Box neben der Karte, sondern Teil
   von ihr.

   Gleichmäßige Abstände: Das Foto steht im Ohr genauso weit von dessen Kanten wie die
   Tabellen von den Kanten der Karte (18 px seitlich, 14 px oben) — und der Abstand
   zwischen Foto und Tabellen ist wieder dasselbe Maß, weil dort Ohr-Polster (18) auf
   Karten-Polster (18) trifft, mit nur der Rahmenlinie dazwischen. Die Oberkante des Ohrs
   liegt auf der Oberkante der Frame-Tabelle.

   Die Karte wird dafür um die Ohrbreite schmaler: außen füllt der Aufklapper damit genau
   die Kachelbreite, nicht mehr — er wird nie breiter als die Tisch-Anzeige darüber.

   Die Spalten für die Ohren sind 0 breit; getragen werden sie von `position: relative`,
   greifen also nicht in die Breite des Blocks ein. Deshalb bleibt der leere Slot auch
   hier ausgeblendet: ein Ohr ohne Foto wäre eine Beule ohne Inhalt, und die Tabellen
   stehen ohne ihn genauso mittig. */
@container (min-width: 600px) {
    .lr-stats-card:has(.lr-fstats.has-photos) {
        /* 75 px Foto (100 px hoch, 3:4) + zweimal Polster + die eine Rahmenlinie außen. */
        --ms-stats-ear-w: calc(75px + 2 * var(--ms-stats-card-pad-x) + 1px);
        /* Was an Ohr über die Kartenkante hinausragt, fehlt der Karte an Breite. */
        max-width: calc(100% - 2 * (var(--ms-stats-ear-w) - 1px));
        /* Der Schlagschatten muss der neuen Silhouette folgen, nicht dem Rechteck: ein
           `box-shadow` läge hinter den Ohren und hörte an ihnen auf, als wären sie
           aufgeklebt. `drop-shadow` folgt der Umrisslinie samt Ohren — dieselben zwei
           Stufen wie `--ms-shadow-2`, nur als Filter. */
        box-shadow: none;
        filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .3)) drop-shadow(0 4px 12px rgba(0, 0, 0, .22));
    }

    .lr-fstats.has-photos {
        display: grid;
        grid-template-columns: 0 minmax(0, 1fr) 0;
        align-items: start;
    }
    .lr-fstats.has-photos > * { grid-column: 2; }
    /* Der Wrapper verschwindet, seine beiden Slots werden selbst zu Gitterfeldern. */
    .lr-fstats.has-photos .lr-fstats-photos { display: contents; }
    .lr-fstats.has-photos .lr-fstats-photo-slot:not(:empty) {
        position: relative;
        grid-row: 1;
        box-sizing: border-box;
        width: var(--ms-stats-ear-w);
        padding: var(--ms-stats-card-pad-y) var(--ms-stats-card-pad-x);
        background: var(--ms-stats-card-bg);
        border: 1px solid var(--ms-stats-card-line);
    }
    .lr-fstats.has-photos .lr-fstats-photo-slot.p1:not(:empty) {
        grid-column: 1;
        left: calc(-1 * (var(--ms-stats-ear-w) + var(--ms-stats-card-pad-x)));
        border-right: 0;
        border-radius: var(--ms-r-lg) 0 0 var(--ms-r-lg);
    }
    .lr-fstats.has-photos .lr-fstats-photo-slot.p2:not(:empty) {
        grid-column: 3;
        left: var(--ms-stats-card-pad-x);
        border-left: 0;
        border-radius: 0 var(--ms-r-lg) var(--ms-r-lg) 0;
    }
}

/* Tabellen erben die Skin-Farben der Karte; dezente, skin-getönte Linien/Streifen. */
.lr-stats-card .table {
    --bs-table-bg: transparent;
    --bs-table-color: var(--sk-text, #fff);
    color: var(--sk-text, #fff);
    border-color: color-mix(in srgb, var(--sk-text, #fff) 14%, transparent);
}
.lr-stats-card .table > :not(caption) > * > * { background-color: transparent; }

/* W-219: keine Zeilenlinien mehr — die Frame-Tabelle gruppiert über dieselbe
   Zebra-Fläche wie die Kennzahlen darunter (Konvention „Flächen statt Linien"). */
.lr-fstats-frames { text-align: center; }
.lr-fstats-frames > :not(caption) > * > * { border-bottom-width: 0; }
.lr-fstats-frames > :not(:first-child) { border-top: none; }
.lr-fstats-frames th {
    font-weight: 600;
    opacity: var(--ms-dim-2);
    font-size: var(--ms-fs-meta);
    text-transform: uppercase;
    letter-spacing: .03em;
}
.lr-fstats-frames td { vertical-align: middle; }
.lr-fstats-frames tbody tr:nth-child(odd) td {
    background: var(--ms-stats-zebra);
}
.lr-fstats-frames .lr-fstats-breaks { width: 26%; }
.lr-fstats-frames .lr-fstats-pts { width: 14%; }
.lr-fstats-frames .lr-fstats-frame { width: 20%; font-weight: 700; opacity: var(--ms-dim-1); }
.lr-fstats-frames .lr-fstats-frame .lr-fstats-dur { font-weight: 400; opacity: var(--ms-dim-3); font-size: var(--ms-fs-meta); }
/* W-243: Shootout-Marke neben der Frame-Nummer — dieselbe Optik wie die Spielart-Marken
   der Statistik-Listen (`.stats-mark` in stats.css: klein, blass, gesperrt). Die Regel
   steht hier noch einmal, weil stats.css auf der Live-Seite nicht geladen wird. Kein
   Fettdruck und keine eigene Fläche: die Nummer bleibt das Hauptwort der Zelle. */
.lr-fstats-frames .lr-fstats-frame .lr-fstats-so {
    font-weight: 400;
    opacity: var(--ms-dim-3);
    font-size: var(--ms-fs-meta);
    letter-spacing: .09em;
    margin-left: .3em;
}
/* Legende der SO-Marke — erscheint nur, wenn die Tabelle sie auch zeigt. */
.lr-fstats-so-legend {
    text-align: center;
    font-size: var(--ms-fs-meta);
    letter-spacing: .03em;
    opacity: var(--ms-dim-3);
    margin-bottom: 1rem;
}

.lr-fstats-figures { text-align: center; }
.lr-fstats-figures > :not(caption) > * > * { border-bottom-width: 0; }
.lr-fstats-figures > :not(:first-child) { border-top: none; }
/* W-219: Namenszeile als Tabellenkopf. Die Fläche ist eine Aufhellung der Textfarbe,
   keine der Skin-Farben: --sk-row-bg und --sk-frames-bg wählt jeder Verein einzeln,
   ihr Kontrast zueinander ist Zufall — je nach Skin fiel die Zeile ins Nichts oder
   schrie. Als Tint sitzt sie überall gleich weit von der Karte entfernt, und die
   16 % setzen sie bewusst über Zebra (6 %) und Fußzeile (9 %). Keine Versalien,
   volle Deckkraft; ein Name soll als Name lesbar sein, nicht als Etikett.
   Die Doppel-Klasse ist nötig, um die transparente Tabellen-Grundregel zu schlagen. */
.lr-fstats .lr-fstats-figures thead th {
    background: var(--ms-stats-band);
    font-weight: 700;
    font-size: var(--ms-fs-small);
    letter-spacing: 0;
    opacity: 1;
    padding: 5px 8px;
}
.lr-fstats .lr-fstats-figures thead th:first-child { border-radius: var(--ms-r-md) 0 0 var(--ms-r-md); }
.lr-fstats .lr-fstats-figures thead th:last-child { border-radius: 0 var(--ms-r-md) var(--ms-r-md) 0; }
.lr-fstats-figures .lr-fstats-line td { border-top: none; padding: 3px 4px; }
/* Zebra bewusst auf den geraden Zeilen: unter dem getönten Namensband bleibt die erste
   Zeile frei, sodass die Tönung abwechselt statt zweimal hintereinander zu stehen.
   Die Frames-Tabelle hat kein Band über sich und behält deshalb ihre ungeraden Zeilen. */
.lr-fstats-figures .lr-fstats-line:nth-child(even) td {
    background: var(--ms-stats-zebra);
}
.lr-fstats-figures .lr-fstats-label { width: 40%; opacity: var(--ms-dim-2); font-size: var(--ms-fs-small); }
.lr-fstats-figures .lr-fstats-v1,
.lr-fstats-figures .lr-fstats-v2 { width: 30%; font-weight: 700; }

/* Zeit-Fußzeile: eigene, gut lesbare Kennzahl-Zeile (Start · Netto-Spielzeit · Ø Frame). */
.lr-fstats-times {
    text-align: center;
    margin-top: 12px;
}
.lr-fstats-times th {
    font-weight: 600;
    font-size: var(--ms-fs-meta);
    text-transform: uppercase;
    letter-spacing: .04em;
    opacity: var(--ms-dim-2);
    border: none;
    padding: 7px 10px 1px;
}
.lr-fstats-times td {
    font-weight: 700;
    font-size: var(--ms-fs-body);
    border: none;
    padding: 0 10px 8px;
    white-space: nowrap;
}

/* ── Druck: nur die Tageshistorie, ohne Bedienelemente (einfacher Stub, IT-B6 #5) ── */
@media print {
    body { background: #fff !important; color: #000 !important; }
    header, nav, .club-streams, .visitor-count, #tablesContainer,
    #totalScore, footer, .history-print-btn, .lr-statstoggle, .lr-statsbar,
    .history-section .ms-panel-head {
        display: none !important;
    }
    #history-section {
        margin-top: 0 !important;
        background: none !important;
        box-shadow: none !important;
        color: #000 !important;
        padding: 0 !important;
    }
    .history-games { grid-template-columns: minmax(0, 1fr) !important; }
    .history-block {
        --sk-row-bg: #fff;
        --sk-text: #000;
        --sk-frames-bg: #eee;
        --sk-frames-text: #000;
        --sk-break: #000;
        break-inside: avoid;
    }
    .history-row {
        border: 1px solid #999 !important;
        box-shadow: none !important;
        break-inside: avoid;
    }
    /* W-233: Gedruckt wird nur die Historie, und die trägt seither keine Punkte-Pillen
       mehr — die Kontur gehört jetzt an den Framestand. */
    .lr-frames { border: 1px solid #999 !important; }
    .lr-stats { display: block !important; padding-top: 0; }
    .collapse:not(.show) { display: block !important; }

    /* W-59: Druck-Dokumentkopf (Heim/Gast + Datum aus Setup) und -fuß (Verweis). */
    .history-print-header {
        text-align: center;
        color: #000 !important;
        margin-bottom: 14px;
    }
    .history-print-header .hp-title {
        font-size: 20pt; font-weight: 800; line-height: 1.15; margin: 0 0 2px;
    }
    .history-print-header .hp-vs { font-weight: 400; }
    .history-print-header .hp-sub { font-size: 11pt; }
    .history-print-footer {
        position: fixed; bottom: 0; left: 0; right: 0;
        text-align: center; font-size: 8pt; color: #444 !important;
        padding-bottom: 4px;
    }
}

/* W-123 / A-13: Skin-Rahmung. Header (Navbar) und Footer tragen den Flaechenton des
   Skins (--skin-surface), nicht den Markenton. Der Markenton bleibt dem Namensbalken
   der Tisch-Zeilen vorbehalten — grosse Flaechen in voll gesaettigter Vereinsfarbe
   wirken grell (Konvention N1). Der Flaechenton ist je Skin explizit gesetzt und immer
   dunkel, deshalb ist die Schrift durchgehend --skin-text (#FFFFFF). Das Farbband
   ersetzt die Trennlinie (border → transparent, keine Haarlinie als UI-Mittel). Gilt
   auf allen Seiten mit body.ms-skinned (Live, Stats, Stats+, Setup); diese injizieren
   die :root-Skin-Variablen via getSkinCssVars().
   E-1 (2026-09-29): body.ms-skinned-frame = NUR diese Rahmung, ohne die Akzente darunter —
   der Super-Admin trägt so Kopf- und Fußleiste im Dresden-Skin (immer dunkel), sein Inhalt
   bleibt neutral und folgt weiter dem Hell/Dunkel-Umschalter. */
body.ms-skinned .navbar.ms-navbar,
body.ms-skinned-frame .navbar.ms-navbar {
    background-color: var(--skin-surface) !important;
    color: var(--skin-text);
}
body.ms-skinned .site-footer,
body.ms-skinned-frame .site-footer {
    background-color: var(--skin-surface);
    color: var(--skin-text) !important;
}
body.ms-skinned .ms-navbar .nav-link,
body.ms-skinned .ms-navbar .navbar-brand,
body.ms-skinned .ms-navbar .navbar-brand .fw-bold,
body.ms-skinned .ms-navbar #themeToggle,
body.ms-skinned .ms-navbar .ms-navbar-username,
body.ms-skinned-frame .ms-navbar .nav-link,
body.ms-skinned-frame .ms-navbar .navbar-brand,
body.ms-skinned-frame .ms-navbar .navbar-brand .fw-bold,
body.ms-skinned-frame .ms-navbar #themeToggle,
body.ms-skinned-frame .ms-navbar .ms-navbar-username {
    color: var(--skin-text) !important;
}
body.ms-skinned .site-footer a,
body.ms-skinned .site-footer .footer-line a,
body.ms-skinned-frame .site-footer a,
body.ms-skinned-frame .site-footer .footer-line a {
    color: var(--skin-text) !important;
}
/* Hamburger-Icon auf farbigem Grund sichtbar halten (seit W-504 auch für Gäste). */
body.ms-skinned .ms-navbar .navbar-toggler,
body.ms-skinned-frame .ms-navbar .navbar-toggler {
    color: var(--skin-text) !important;
    border-color: color-mix(in srgb, var(--skin-text) 40%, transparent);
}
body.ms-skinned .ms-navbar .navbar-toggler-icon,
body.ms-skinned-frame .ms-navbar .navbar-toggler-icon {
    background-image: none;
    width: auto;
    height: auto;
}
body.ms-skinned .ms-navbar .navbar-toggler-icon::before,
body.ms-skinned-frame .ms-navbar .navbar-toggler-icon::before {
    content: "\F479"; /* bi-list */
    font-family: "bootstrap-icons";
    font-size: 1.25rem;
    line-height: 1;
}

/* W-123: Dezente Skin-Akzente in Statistik / Stats+ / Setup. Der neutrale Inhalt
   bleibt lesbar; nur Detail-Elemente tragen die Vereinsfarbe. Titel/Tabellenkoepfe
   werden nur leicht Richtung Skin getoent (color-mix, nie voll gesaettigt), damit
   nichts grell wirkt. Diagramm-Datenreihen bleiben neutral.

   W-457: „und Speichern-Buttons" stand hier bis zum 2026-09-06 mit — die Bedienelemente
   tragen jetzt eine feste Hausfarbe (`--ms-action`, oben in `:root`), keine Vereinsfarbe.
   Die Regeln unten bleiben davon unberührt: sie färben *Zustände* — wo ich bin, wem das
   gehört —, und dafür ist die Vereinsfarbe genau richtig. */
body.ms-skinned .stats-tabs .nav-link.active {
    border-bottom-color: var(--skin-marked);
}
body.ms-skinned .stats-section-title,
body.ms-skinned .ms-setup-title {
    color: color-mix(in srgb, var(--skin-primary) 55%, var(--bs-body-color));
}
body.ms-skinned .stats-data th {
    background: color-mix(in srgb, var(--skin-primary) 12%, var(--stats-th-bg));
}
body.ms-skinned .sp-table thead th {
    background: color-mix(in srgb, var(--skin-primary) 12%, transparent);
}
body.ms-skinned .sp-bar {
    background: var(--skin-primary);
}
body.ms-skinned .ms-setup-nav .ms-setup-tab.active {
    background: var(--skin-player-bg);
    color: var(--skin-player-text);
}
/* Mobil ersetzt ein Dropdown die vertikalen Setup-Tabs. Der Toggle (= aktueller
   Reiter) und der aktive Menue-Eintrag tragen die Vereinsfarbe wie der aktive
   Desktop-Tab, damit die Auswahl nicht unscheinbar wirkt.

   W-416: Die Vereinsfarbe allein reicht dafuer nicht. Bei „Standard" (#012127),
   „Schwarz/Rot" (#000000), „Kupfer"/„Marine" (#0A1B2A), „Blau in Blau" (#003355) und
   „Anthrazit" (#263238) steht diese Flaeche randlos auf einer dunklen Seite — der Knopf
   hatte dann keine sichtbare Kante mehr und wirkte wie ein Loch statt wie ein
   Bedienelement. Der Ring behebt das, ohne eine einzige Farbe zu aendern: er traegt die
   **Theme**-Farbe (`--bs-body-color-rgb` — hell im Dark-, dunkel im Light-Layout) und
   steht damit unabhaengig vom Skin immer im Kontrast zur Seite. Innen (`inset`) beim
   Menue-Eintrag, weil ein aeusserer Ring dort ueber den Rand des Menues stuende. */
body.ms-skinned .ms-setup .dropdown > .dropdown-toggle.btn {
    background-color: var(--skin-player-bg);
    color: var(--skin-player-text);
    border-color: transparent;
    box-shadow: 0 0 0 2px rgba(var(--bs-body-color-rgb), .35);
}
body.ms-skinned .ms-setup .dropdown > .dropdown-toggle.btn:hover,
body.ms-skinned .ms-setup .dropdown > .dropdown-toggle.btn:focus,
body.ms-skinned .ms-setup .dropdown > .dropdown-toggle.btn:active,
body.ms-skinned .ms-setup .dropdown > .dropdown-toggle.btn.show {
    background-color: color-mix(in srgb, var(--skin-player-bg) 88%, #000);
    color: var(--skin-player-text);
    border-color: transparent;
    box-shadow: 0 0 0 2px rgba(var(--bs-body-color-rgb), .5);
}
body.ms-skinned .ms-setup .dropdown-menu .dropdown-item.active {
    background-color: var(--skin-player-bg);
    color: var(--skin-player-text);
    box-shadow: inset 0 0 0 2px rgba(var(--bs-body-color-rgb), .35);
}

/* --- Gerenderter Markdown-Text (W-134) ---
   Gehoert zum gemeinsamen Renderer (includes/markdown.php) und damit hierher: Hilfeseite
   und Changelog zeigen denselben Text-Aufbau. Lag die Regel in help.css, stand der
   Changelog ohne sie da. */

/* W-259: „Über uns" — eine Textspalte, sonst nichts. Die Breite ist begrenzt, weil
   Fließtext über die volle Fensterbreite auf einem Monitor nicht mehr lesbar ist. */
.ms-about { max-width: 46rem; padding-bottom: 2rem; }
.ms-about-title { font-size: 1.6rem; font-weight: 600; margin: 1.2rem 0 1.6rem; }

/* W-504: „Über MyScoreboard“ — kurz: Einleitung, zwei Karten, Links auf die Website. */
.ms-info { max-width: 52rem; padding-bottom: 2rem; }
.ms-info-title { font-size: 1.6rem; font-weight: 600; margin: 1.2rem 0 .8rem; }
.ms-info-lead { font-size: 1.05rem; opacity: var(--ms-dim-1); margin-bottom: 1.4rem; }
.ms-info-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
    gap: 1rem;
}
.ms-info-card h2 { font-size: 1.15rem; font-weight: 600; margin-bottom: .6rem; }
.ms-info-card p { line-height: 1.6; margin-bottom: 1rem; }
.ms-info-links {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: .25rem 1.5rem;
    margin-top: 1.6rem;
}
.ms-info-links a { color: var(--ms-action-link); }

.ms-md p { line-height: 1.65; margin-bottom: .9rem; }
.ms-md li { line-height: 1.6; margin-bottom: .3rem; }
.ms-md-list { padding-left: 1.35rem; margin-bottom: 1rem; }

.ms-md-h3 {
    font-size: 1.35rem;
    font-weight: 600;
    margin: 2.4rem 0 .9rem;
    scroll-margin-top: 5rem;
    color: color-mix(in srgb, var(--skin-primary, var(--ms-accent)) 55%, var(--bs-body-color));
}

.ms-md-h4 {
    font-size: var(--ms-fs-emph);
    font-weight: 600;
    margin: 1.6rem 0 .6rem;
    scroll-margin-top: 5rem;
}

.ms-md-h5 {
    font-size: var(--ms-fs-body);
    font-weight: 600;
    margin: 1.2rem 0 .5rem;
    scroll-margin-top: 5rem;
}

.ms-md-h3:first-child { margin-top: 0; }

.ms-md code {
    background: color-mix(in srgb, var(--bs-body-color) 8%, transparent);
    padding: .1em .35em;
    border-radius: var(--ms-r-sm);
    color: inherit;
}

/* Der Changelog trennt damit zwei Versionen — eine inhaltliche Zaesur, kein Zierrat. */
.ms-md-rule {
    margin: 2rem 0;
    opacity: .18;
}

/* Tabelle: Zeilen werden ueber abwechselnde Flaechen lesbar, nicht ueber Gitterlinien. */
.ms-md-table {
    width: 100%;
    margin-bottom: 1.2rem;
    border-collapse: separate;
    border-spacing: 0;
    font-size: var(--ms-fs-body);
}

.ms-md-table th,
.ms-md-table td {
    padding: .5rem .8rem;
    vertical-align: top;
    text-align: left;
}

.ms-md-table th {
    font-size: var(--ms-fs-small);
    letter-spacing: .04em;
    text-transform: uppercase;
    opacity: var(--ms-dim-2);
    font-weight: 600;
}

.ms-md-table tbody tr:nth-child(odd) td {
    background: color-mix(in srgb, var(--bs-body-color) 5%, transparent);
}

.ms-md-table tbody tr:first-child td:first-child { border-top-left-radius: 8px; }
.ms-md-table tbody tr:first-child td:last-child { border-top-right-radius: 8px; }
.ms-md-table tbody tr:last-child td:first-child { border-bottom-left-radius: 8px; }
.ms-md-table tbody tr:last-child td:last-child { border-bottom-right-radius: 8px; }

/* Hervorgehobener Hinweis (`> …`) — eigener Flaechenton statt Zitat-Randlinie. */
.ms-md-note {
    background: color-mix(in srgb, var(--skin-primary, var(--ms-accent)) 14%, transparent);
    border-radius: var(--ms-r-md);
    padding: .85rem 1.1rem;
    margin: 1.2rem 0;
    line-height: 1.6;
}

/* ===== „Erste Schritte" zum Ausdrucken (R6) =====
   Ein Blatt, zwei Seiten: vorn der Verein, hinten der Tisch. Am Bildschirm zwei Flächen
   untereinander, auf Papier zwei Seiten — die Zäsur macht der Seitenumbruch, nicht eine
   Linie (Konvention N9). */
.ms-firststeps { max-width: 46rem; padding-bottom: 2rem; }

.ms-firststeps-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    margin: 1.2rem 0 1.6rem;
}

.ms-firststeps-page {
    background: color-mix(in srgb, var(--bs-body-color) 4%, transparent);
    border-radius: var(--ms-r-lg);
    padding: 1.6rem 1.8rem;
    margin-bottom: 1.6rem;
}

/* Der Vereinsname steht über der ersten Seite — auf Papier ist er die einzige Auskunft
   darüber, zu wem der Zettel gehört. */
.ms-firststeps-club {
    font-size: var(--ms-fs-body);
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    margin-bottom: .4rem;
    color: color-mix(in srgb, var(--skin-primary, var(--ms-accent)) 65%, var(--bs-body-color));
}

/* Die Schritte als Kacheln — dasselbe Muster wie der Schnellstart in der Hilfe
   (`.ms-help-quickstart`): Ziffer im Kreis, gefüllte Fläche, keine Rahmenlinie. Auf
   Papier ist das der Unterschied zwischen einer Wand aus Text und etwas, das man im
   Vorbeigehen liest. Angesprochen wird über den Elementtyp: `ol` sind die nummerierten
   Schritte, `ul` ist „Gut zu wissen" — so braucht die Markdown-Datei keine Auszeichnung,
   die der gemeinsame Renderer gar nicht kennt. */
.ms-firststeps-page ol.ms-md-list {
    list-style: none;
    counter-reset: ms-fs-step;
    padding: 0;
    margin: 0 0 1.2rem;
}

.ms-firststeps-page ol.ms-md-list > li {
    counter-increment: ms-fs-step;
    position: relative;
    margin: 0 0 .45rem;
    padding: .6rem .9rem .6rem 3.1rem;
    border-radius: var(--ms-r-md);
    background: var(--bs-tertiary-bg);
}

.ms-firststeps-page ol.ms-md-list > li::before {
    content: counter(ms-fs-step);
    position: absolute;
    left: .7rem;
    top: .55rem;
    width: 1.7rem;
    height: 1.7rem;
    border-radius: 50%;
    background: color-mix(in srgb, var(--skin-primary, var(--ms-accent)) 30%, transparent);
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 600;
    font-size: var(--ms-fs-body);
}

/* Das Sinnbild steht vor dem fett gesetzten Stichwort und trägt die Wiedererkennung —
   gedämpft, damit es der Ziffer nicht den Rang abläuft. */
.ms-firststeps-icon {
    margin-right: .4rem;
    opacity: var(--ms-dim-2);
}

/* „Gut zu wissen": vier kurze Kacheln statt vier langer Aufzählungspunkte. */
.ms-firststeps-page ul.ms-md-list {
    list-style: none;
    padding: 0;
    margin: 0;
}

.ms-firststeps-page ul.ms-md-list > li {
    background: var(--bs-tertiary-bg);
    border-radius: var(--ms-r-md);
    padding: .55rem .8rem;
    margin: 0 0 .45rem;
    font-size: var(--ms-fs-body);
}

@media (min-width: 700px) {
    .ms-firststeps-page ul.ms-md-list {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: .5rem .7rem;
    }
    .ms-firststeps-page ul.ms-md-list > li { margin: 0; }
}

.ms-firststeps-foot {
    margin: 1.6rem 0 0;
    font-size: var(--ms-fs-label);
    opacity: var(--ms-dim-2);
}

/* Nur für den Ausdruck: die Herkunft des Blattes. Am Bildschirm steht sie ohnehin im
   Seitenfuß; auf Papier gäbe es sonst keine. */
.ms-firststeps-copy { display: none; }

@media print {
    /* Auf Papier bleibt der Inhalt und sonst nichts: kein Kopf, kein Fuß, kein Knopf,
       der sich ohnehin nicht drücken lässt. */
    body { background: #fff !important; color: #000 !important; }

    body:has(.ms-firststeps) header,
    body:has(.ms-firststeps) nav,
    body:has(.ms-firststeps) footer,
    .ms-firststeps-bar,
    .ms-setup-hint { display: none !important; }

    .ms-firststeps { max-width: none; padding: 0; }

    .ms-firststeps-page {
        background: none !important;
        padding: 0 !important;
        margin: 0 !important;
        break-inside: auto;
    }

    /* Die Rückseite fängt eine neue Seite an — beidseitig gedruckt ist sie damit die
       Rückseite und nicht die Fortsetzung. */
    .ms-firststeps-back { break-before: page; }

    .ms-firststeps-club { color: #000 !important; }
    .ms-md-h3, .ms-md-h4, .ms-md-h5 { color: #000 !important; }
    .ms-md-note {
        background: #f0f0f0 !important;
        color: #000 !important;
        break-inside: avoid;
    }
    .ms-md code { background: #eee !important; }
    .ms-md li, .ms-md p { break-inside: avoid; }

    /* Flächen drucken die Browser von sich aus NICHT mit — ohne `print-color-adjust`
       käme das Blatt als reine Liste heraus und die Gliederung wäre wieder weg. Helles
       Grau statt Vereinsfarbe: die Kachel soll den Block zeigen, nicht die Patrone leeren. */
    .ms-firststeps-page ol.ms-md-list > li,
    .ms-firststeps-page ul.ms-md-list > li {
        background: #f4f4f4 !important;
        color: #000 !important;
        -webkit-print-color-adjust: exact;
        print-color-adjust: exact;
        break-inside: avoid;
    }

    .ms-firststeps-page ol.ms-md-list > li::before {
        background: #dcdcdc !important;
        color: #000 !important;
        -webkit-print-color-adjust: exact;
        print-color-adjust: exact;
    }

    .ms-md-note {
        -webkit-print-color-adjust: exact;
        print-color-adjust: exact;
    }

    /* Auf Papier eine Spur enger als am Bildschirm: die Vorderseite lief sonst mit den
       letzten beiden „Gut zu wissen"-Kacheln auf ein zweites Blatt (User-Befund
       2026-08-23). Gekürzt wurde zuerst der Text — das hier holt den Rest. */
    .ms-firststeps-page { font-size: 10pt; line-height: 1.35; }

    .ms-firststeps-page ol.ms-md-list > li {
        padding: .3rem .6rem .3rem 2.4rem;
        margin-bottom: .28rem;
    }

    .ms-firststeps-page ol.ms-md-list > li::before {
        width: 1.35rem;
        height: 1.35rem;
        top: .32rem;
        left: .5rem;
        font-size: var(--ms-fs-small);
    }

    .ms-firststeps-page ul.ms-md-list > li {
        padding: .32rem .6rem;
        font-size: 9.5pt;
    }

    .ms-firststeps-page .ms-md-h3 { margin: 0 0 .35rem; }
    .ms-firststeps-page .ms-md-note { margin: .5rem 0; }
    .ms-firststeps-page > p { margin-bottom: .5rem; }

    /* `position: fixed` heißt im Druck „auf jedem Blatt" — so tragen beide Seiten den
       Vermerk, ohne dass er zweimal im Dokument stehen muss. */
    .ms-firststeps-copy {
        display: block !important;
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        margin: 0;
        text-align: center;
        font-size: 8pt;
        color: #666 !important;
    }
}

/* ===== Einrichtungs-Band (W-183/W-208) =====
   Steht über dem Inhalt, solange die Ersteinrichtung nicht durch ist — im Setup auf
   jedem Reiter, auf der Live-Seite für Admins, solange kein Tablet angemeldet ist.
   Abgesetzt wird über Fläche und Abstand, nie über Haarlinien (Konvention N9). */
.ms-setup-hint {
    display: flex;
    align-items: center;
    gap: .8rem;
    margin-bottom: 1.2rem;
    padding: .9rem 1.1rem;
    border-radius: var(--ms-r-md);
    background: color-mix(in srgb, var(--skin-primary, var(--ms-accent)) 22%, var(--bs-body-bg));
    color: var(--bs-body-color);
    text-decoration: none;
}

.ms-setup-hint > .bi:first-child { font-size: 1.4rem; opacity: var(--ms-dim-1); }

/* Die Ersteinrichtung als eine Schrittliste — ein Band statt eines Stapels einzelner
   Meldungen. Erledigtes bleibt gedämpft stehen, damit der Fortschritt sichtbar ist
   und nicht nur der Mangel. */
.ms-setup-guide {
    flex-direction: column;
    align-items: stretch;
    gap: .55rem;
}

/* W-300: Die Kopfzeile ist zugleich der Auslöser — als echter `<button>`, damit Tastatur
   und Screenreader sie ohne Zutun bedienen können. Bootstraps Knopf-Optik wäre hier falsch:
   das ist eine Überschrift, die sich klappen lässt, und kein Angebot. Deshalb bis auf den
   Zeiger vollständig zurückgesetzt. */
.ms-setup-guide-head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    row-gap: .15rem;
    width: 100%;
    padding: 0;
    border: 0;
    background: none;
    color: inherit;
    font-size: var(--ms-fs-body);
    text-align: left;
}
.ms-setup-guide-head:not(.is-static) { cursor: pointer; }
.ms-setup-guide-head > .bi:first-child { margin-right: .45rem; opacity: var(--ms-dim-1); }
.ms-setup-guide-head > span { opacity: var(--ms-dim-1); }
/* W-308: Der Punkt trennt Überschrift und Stand — dieselbe Schreibweise wie in der
   Führungsleiste darüber. Er gehört zur Anordnung nebeneinander und fällt weg, sobald
   der Stand eine eigene Zeile bekommt. */
.ms-setup-guide-head > span::before { content: '· '; }

@media (max-width: 575.98px) {
    /* Auf einem Telefon reicht die Breite nicht für Überschrift, Stand und Pfeil in einer
       Zeile — der Stand rutscht darunter, bündig mit dem Text statt mit dem Kompass-Icon.
       Der Pfeil bleibt oben rechts, deshalb die Umsortierung statt eines Umbruchs an Ort
       und Stelle: sonst stünde er allein in einer dritten Zeile. */
    .ms-setup-guide-head > span {
        order: 3;
        flex: 0 0 100%;
        padding-left: 1.4rem;
        font-size: var(--ms-fs-label);
    }
    .ms-setup-guide-head > span::before { content: none; }
}

/* Der Pfeil sitzt am rechten Ende und zeigt, wohin es geht: zu bei eingeklapptem Rumpf,
   auf bei offenem. `margin-left: auto` statt `space-between`, damit die Reihenfolge der
   Kinder nichts ändert. */
.ms-setup-guide-caret {
    margin-left: auto;
    padding-left: .5rem;
    opacity: var(--ms-dim-2);
    transition: transform .15s ease;
}
.ms-setup-guide-head[aria-expanded="false"] .ms-setup-guide-caret { transform: rotate(180deg); }

.ms-setup-guide-body {
    display: flex;
    flex-direction: column;
    gap: .55rem;
}

/* Zwischenüberschrift der beiden Listen („Für diesen Wettkampf" / „Einrichtung des
   Vereins"). Klein und in Versalien: sie gliedert, ohne mit den Schritten zu konkurrieren. */
.ms-setup-guide-section {
    margin: .35rem 0 0;
    font-size: var(--ms-fs-small);
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    opacity: var(--ms-dim-1);
}
.ms-setup-guide-section > span {
    margin-left: .4rem;
    font-weight: 600;
    letter-spacing: 0;
    text-transform: none;
    opacity: var(--ms-dim-2);
}

.ms-setup-guide-intro { font-size: var(--ms-fs-label); opacity: var(--ms-dim-2); }

/* R6: der Verweis aufs Blatt zum Ausdrucken, ganz am Fuß des Bandes. Klein und blass —
   er soll gefunden werden, wenn man ihn sucht, und sonst nicht stören. */
.ms-setup-guide-print { margin: 1.1rem 0 0; font-size: var(--ms-fs-label); opacity: var(--ms-dim-2); }
.ms-setup-guide-print a { color: inherit; }

.ms-setup-guide-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

/* Raster statt Flex: Haken/Kreis in der ersten Spalte, Text und Knopf in der zweiten.
   Damit steht das Icon immer auf Höhe der Überschrift, auch wenn der Hinweistext über
   mehrere Zeilen läuft, und der Knopf sitzt unter dem Text statt neben ihm. */
.ms-setup-guide-list > li {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    column-gap: .7rem;
    row-gap: .5rem;
    padding: .45rem .6rem;
    border-radius: var(--ms-r-md);
    background: color-mix(in srgb, var(--bs-body-bg) 55%, transparent);
}

.ms-setup-guide-list > li + li { margin-top: .35rem; }

/* W-300: Gedämpft wird Zeichen für Zeichen, nicht die ganze Zeile. Lag die Dämpfung auf
   dem `li`, war der „Ändern"-Link mit halb unsichtbar — und ein Weg, den man nicht lesen
   kann, ist keiner. Haken und Text bleiben zurückgenommen, der Link bleibt lesbar. */
.ms-setup-guide-list > li.is-done { background: none; }
/* W-474: Der erledigte Schritt trägt Lesetext (Label + Notiz), nicht nur ein
   Häkchen — also `dim-3`. Das Häkchen geht mit, damit es nicht in anderer
   Deckkraft neben seinem Text steht. */
.ms-setup-guide-list > li.is-done > .bi,
.ms-setup-guide-list > li.is-done > span { opacity: var(--ms-dim-3); }

/* W-298: Der nächste offene Schritt trägt die Aufmerksamkeit — kräftigere Fläche statt
   eines Rahmens, damit sich nicht sechs gleich laute Zeilen um den Blick streiten. */
.ms-setup-guide-list > li.is-next {
    background: color-mix(in srgb, var(--bs-success) 16%, var(--bs-body-bg));
}

.ms-setup-guide-list > li > .bi { font-size: 1.1rem; line-height: 1.4; }
.ms-setup-guide-list > li.is-done > .bi { color: var(--bs-success); }
.ms-setup-guide-list > li.is-next > .bi { color: var(--bs-success); }

.ms-setup-guide-extra {
    font-size: var(--ms-fs-label);
    opacity: var(--ms-dim-2);
    padding: 0 .6rem;
}

.ms-setup-guide-list > li > span { min-width: 0; }
.ms-setup-guide-list > li small { display: block; opacity: var(--ms-dim-2); line-height: 1.35; }

/* W-300: Der Weg zu einem **erledigten** Schritt — sichtbar, aber ohne Anspruch: Textfarbe
   statt Grün, keine Fläche, kein Rahmen (Konvention N9). Er soll gefunden werden, wenn
   jemand ihn sucht, und sonst nicht auffallen. */
.ms-setup-guide-go {
    display: inline-flex;
    align-items: center;
    gap: .2rem;
    color: inherit;
    font-size: var(--ms-fs-label);
    text-decoration: none;
    opacity: var(--ms-dim-2);
    white-space: nowrap;
}
.ms-setup-guide-go:hover,
.ms-setup-guide-go:focus-visible { color: inherit; opacity: 1; text-decoration: underline; }
.ms-setup-guide-go > .bi { font-size: var(--ms-fs-small); }

/* Mehrzeilige Schritte: Icon oben an der Überschrift statt in der Mitte des Blocks —
   der Knopf daneben bleibt mittig, sonst klebt er am oberen Rand. */
.ms-setup-guide-list > li:has(small) { align-items: start; }
.ms-setup-guide-list > li > .btn,
.ms-setup-guide-list > li > .ms-setup-guide-go { align-self: center; }

@media (max-width: 575.98px) {
    /* Zu schmal für Text und Knopf nebeneinander — der Knopf rückt unter den Text,
       bündig mit ihm, statt eine dritte Spalte zu erzwingen. */
    .ms-setup-guide-list > li {
        grid-template-columns: auto 1fr;
    }
    .ms-setup-guide-list > li > .btn,
    .ms-setup-guide-list > li > .ms-setup-guide-go {
        grid-column: 2;
        justify-self: start;
    }
}

/* ===== Führung der Erst-Einrichtung (W-306) =====
   Die schmale Leiste über dem Band, solange die Führung läuft: wo bin ich, wo geht es
   weiter, wie komme ich hier raus. Abgesetzt über eine leicht grüne Fläche — dieselbe
   Sprache wie der hervorgehobene nächste Schritt im Band —, nie über eine Haarlinie
   (Konvention N9). Flacher als das Band, weil sie nur eine Zeile trägt und nicht mit ihm
   um die Aufmerksamkeit streiten soll. */
.ms-guide-bar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: .5rem .8rem;
    margin-bottom: .8rem;
    padding: .6rem 1.1rem;
    border-radius: var(--ms-r-md);
    background: color-mix(in srgb, var(--bs-success) 18%, var(--bs-body-bg));
    font-size: var(--ms-fs-body);
}
.ms-guide-bar > .bi:first-child { font-size: 1.2rem; opacity: var(--ms-dim-1); }

/* Der Ort wächst, die beiden Wege bleiben so breit wie ihr Text — auf schmalen Geräten
   bricht der Ort in seine eigene Zeile, statt den Knopf zu quetschen. */
.ms-guide-bar-pos { flex: 1 1 14rem; min-width: 0; }

/* Der Grund für einen übersprungenen Schritt gehört unter die Leiste und nicht in sie
   hinein: eigene Zeile, kleiner, gedämpft. */
.ms-guide-bar-skip { flex: 1 1 100%; font-size: var(--ms-fs-label); opacity: var(--ms-dim-2); }

/* Das Angebot, die Führung zu starten — steht im Band, solange sie nicht läuft. */
.ms-setup-guide-lead {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: .5rem .8rem;
    padding: 0 .6rem;
    font-size: var(--ms-fs-label);
}
.ms-setup-guide-lead > span { flex: 1 1 14rem; min-width: 0; opacity: var(--ms-dim-1); }

/* ===== Zeilen-Aktionen: ein Auslöser je Zeile (W-208) =====
   Überall derselbe Dreipunkt — Setup-Tabellen wie Spielelisten der Statistik —, und
   überall öffnet er sofort den Dialog, statt erst ein Menü aufzuklappen. Gefüllt statt
   nur Icon, damit er in den dichten Setup-Tabellen nicht untergeht. Steht auch in
   `app.css`, weil die Statistik `admin.css` nicht lädt. */
.btn.btn-row-menu {
    background: var(--bs-secondary-bg);
    border-color: transparent;
    color: var(--bs-body-color);
    /* Eine Stufe kleiner seit 2026-10-03 (User-Intake: „wirkt zu groß"). */
    padding: .15rem .35rem;
    line-height: 1;
}
.btn.btn-row-menu:hover,
.btn.btn-row-menu:focus-visible {
    background: var(--bs-tertiary-bg);
    border-color: transparent;
    color: var(--bs-body-color);
}
.btn.btn-row-menu > .bi { font-size: 1rem; vertical-align: -.08em; }

/* W-31c: auf Touch-Geräten komfortable Tap-Fläche, Desktop bleibt kompakt. 40 → 34 px
   am 2026-10-03: der Knopf ist gefüllt, die Fläche ist also sichtbar und wirkte klobig. */
@media (pointer: coarse) {
    .btn.btn-row-menu {
        min-height: 34px;
        min-width: 34px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }
}

/* ---------------------------------------------------------------------------
   W-299: „Tisch einstellen" — Stift in der Kachel-Kopfzeile, das Panel darunter
   und das Formular „Tische im Einsatz" über dem Raster.

   Grundsatz wie überall (Konvention N9): abgesetzt wird über **Flächen** und
   Abstand, nicht über Haarlinien. Das Panel ist deshalb eine eigene Fläche unter
   der Kachel, keine umrandete Box; die Blöcke darin sitzen auf einer nochmals
   leicht abgehobenen Fläche.
   --------------------------------------------------------------------------- */

/* Der Stift teilt sich die Optik mit dem Kopf-Stift (`.ms-inline-edit`); hier ist
   nur die Größe anders — die Kopfzeile der Kachel ist winzig, `.55em` davon wäre
   auf dem Handy nicht zu treffen. */
.lr-edit {
    margin-left: .5rem;
    font-size: var(--ms-fs-base);
    opacity: var(--ms-dim-4);
}

/* Offen ist ein Zustand, kein Angebot: volle Deckkraft allein reicht nicht, das sähe
   aus wie „Maus liegt drauf". Der offene Stift bekommt deshalb eine eigene Fläche —
   dieselbe wie beim Zeigen, nur kräftiger und bleibend (kein Rahmen, Konvention N9). */
.lr-edit.is-open { opacity: 1; }
body.dark-mode  .lr-edit.is-open { background: rgba(255, 255, 255, .22); }
body.light-mode .lr-edit.is-open { background: rgba(0, 0, 0, .16); }

/* W-324: Der Papierkorb der Tageshistorie fragt an Ort und Stelle nach — der zweite Klick
   löscht. Gefragt wird im selben gedeckten Warnton wie der fällige Shootout-Hinweis
   (kein zweites Rot, kein Rahmen). Die Frage macht den Knopf breiter; `nowrap` hält die
   Kopfzeile einzeilig. */
.lr-del.is-asking {
    opacity: 1;
    color: #d98939;
    white-space: nowrap;
}
.lr-del .lr-del-ask {
    margin-left: .3rem;
    font-size: var(--ms-fs-meta);
    font-weight: 700;
    letter-spacing: .06em;
}

@media (pointer: coarse) {
    .lr-edit {
        min-width: 32px;
        min-height: 32px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }
}

/* Das Panel-Fach sitzt zwischen Kachel und Stream. Ohne Inhalt ist es `hidden`
   und nimmt keinen Platz. */
.lr-panel { margin-top: .5rem; }

.lta {
    padding: 1rem;
    border-radius: var(--ms-r-lg);
    text-transform: none;
    letter-spacing: normal;
}

body.dark-mode  .lta { background: var(--ms-surface-variant); color: #fff; }
body.light-mode .lta { background: #f1f3f5; color: #212529; }

/* W-319: Die Kopfzeile klebt. Das Panel ist auf dem Handy länger als der Bildschirm, und
   wer unten in der Warteschlange gearbeitet hat, müsste sonst erst wieder nach oben
   scrollen, um es zu schließen — ein zweiter Schließen-Knopf am Fuß wäre ein zweiter Weg
   zum selben Ziel. Die negativen Margen ziehen die Fläche bis an die Panel-Kante, sonst
   schiene der Inhalt beim Vorbeiscrollen seitlich an ihr vorbei.

   `top` ist die Höhe der Seiten-Kopfleiste, die selbst klebt (`sticky-top`): bei `top: 0`
   verschwände die Panel-Kopfzeile unter ihr und es sähe aus, als klebte sie gar nicht.
   Gemessen wird sie in `includes/header.php` — geraten wäre sie falsch, sobald das Logo
   umbricht. */
.lta-head {
    position: sticky;
    top: var(--ms-navbar-h, 4rem);
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    margin: -1rem -1rem .75rem;
    padding: 1rem 1rem .5rem;
    border-radius: var(--ms-r-lg) var(--ms-r-lg) 0 0;
}
body.dark-mode  .lta-head { background: var(--ms-surface-variant); }
body.light-mode .lta-head { background: #f1f3f5; }

/* Titel und Schließen-Knopf schrumpfen nie — nur die Meldung dazwischen darf das
   (eine lange Fehlermeldung bricht dann um, statt den Knopf aus der Zeile zu drängen). */
.lta-title { font-size: var(--ms-fs-emph); font-weight: 700; flex: 0 0 auto; }

.lta-close {
    flex: 0 0 auto;
    border: 0;
    background: none;
    color: inherit;
    opacity: var(--ms-dim-4);
    padding: .25rem .4rem;
    border-radius: var(--ms-r-sm);
    line-height: 1;
    cursor: pointer;
}
.lta-close:hover, .lta-close:focus-visible { opacity: 1; }
body.dark-mode  .lta-close:hover { background: rgba(255, 255, 255, .1); }
body.light-mode .lta-close:hover { background: rgba(0, 0, 0, .07); }

/* Rückmeldung (Gespeichert / Fehler): eine kleine Marke in der Kopfzeile, direkt hinter
   dem Titel — dort geht der Blick nach dem Klick ohnehin hin, und dort bewegt sie nichts.
   Gefärbt wird nur die Fläche, kein Rahmen, kein Icon.

   `margin-right: auto` schiebt den Schließen-Knopf ans rechte Ende. Die auto-Marge frisst
   den freien Platz VOR dem `space-between` der Kopfzeile — deshalb steht die Meldung dicht
   am Titel, wenn sie da ist, und der Knopf trotzdem rechts, wenn sie fehlt. */
.lta-msg {
    margin-right: auto;
    min-width: 0;
    padding: .12rem .5rem;
    border-radius: var(--ms-r-pill);
    font-size: var(--ms-fs-small);
    font-weight: 600;
}
body.dark-mode  .lta-msg { background: rgba(93, 150, 106, .24); }
body.light-mode .lta-msg { background: rgba(93, 150, 106, .18); }
body.dark-mode  .lta-msg.is-error { background: rgba(190, 90, 80, .28); }
body.light-mode .lta-msg.is-error { background: rgba(190, 90, 80, .18); }

.lta-grid {
    display: grid;
    /* `auto-fit` statt fester Spaltenzahl: das Panel sitzt in einer Kachel, und die ist
       im zweispaltigen Raster nur halb so breit wie das Fenster. Eine Media Query fragt
       das Fenster ab und wüsste davon nichts — hier zählt die Breite des Panels. */
    grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
    gap: .75rem 1rem;
}

.lta-grid-4 {
    grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
    margin-top: .75rem;
}

.lta-actions {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
    margin-top: .75rem;
}

/* Warteschlange und „Spiel hinzufügen" sind eigene Arbeitsschritte — je eine
   Fläche, damit sie nicht mit dem Formular oben verschwimmen. */
.lta-block {
    margin-top: 1rem;
    padding: .85rem 1rem;
    border-radius: var(--ms-r-md);
}
body.dark-mode  .lta-block { background: rgba(255, 255, 255, .06); }
body.light-mode .lta-block { background: rgba(0, 0, 0, .05); }

.lta-h {
    margin: 0 0 .6rem;
    font-size: var(--ms-fs-body);
    font-weight: 700;
}

.lta-empty, .lta-note {
    font-size: var(--ms-fs-label);
    opacity: var(--ms-dim-2);
}
.lta-note { margin: 0 0 .6rem; }

.lta-more { margin-top: .6rem; }

.lta-loading { padding: 1rem; text-align: center; }

/* Die Warteschlange ist eine Reihenfolge — die Nummer gehört dazu und trägt
   dieselbe Aussage wie das Tablet, das von oben abarbeitet. Jede Partie ist eine
   eigene Fläche: sie hebt sich damit vom Block ab UND von ihren Nachbarinnen, ohne
   dass eine Trennlinie nötig wäre (Konvention N9). Die Listen-Marke fällt weg, die
   Nummer steht als Feld in der Zeile (siehe live_table_panel.php). */
.lta-queue {
    margin: 0;
    padding-left: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: .45rem;
}

.lta-qrow {
    display: flex;
    align-items: center;
    gap: .75rem;
    padding: .5rem .6rem;
    border-radius: var(--ms-r-md);
}
body.dark-mode  .lta-qrow { background: rgba(255, 255, 255, .07); }
body.light-mode .lta-qrow { background: rgba(255, 255, 255, .7); }

/* Die erste Partie ist die, die als Nächstes an den Tisch geht — sie ist deutlich
   kräftiger als die Wartenden dahinter. Abgesetzt wird auch hier über die Fläche. */
body.dark-mode  .lta-qrow.is-next { background: rgba(79, 195, 247, .16); }
body.light-mode .lta-qrow.is-next { background: rgba(79, 195, 247, .22); }

/* Position bzw. „Nächstes": links, in fester Spur, damit die Namen aller Zeilen
   auf derselben Kante beginnen. */
.lta-qpos {
    flex: 0 0 auto;
    min-width: 1.6rem;
    text-align: center;
    font-size: var(--ms-fs-small);
    font-weight: 700;
    opacity: var(--ms-dim-3);
}

/* W-309: „Nächstes Spiel" steht zweizeilig — ausgeschrieben wäre die Marke sonst breiter
   als der halbe Panelrand und drückte die Namen aller Zeilen nach rechts. Die feste Spur
   von `.lta-qpos` wird dafür auf die Breite des längeren Wortes gestellt. */
.lta-qnext {
    padding: .18rem .45rem;
    min-width: 4.4rem;
    border-radius: var(--ms-r-md);
    line-height: 1.15;
    letter-spacing: .04em;
    text-transform: uppercase;
    opacity: 1;
}
body.dark-mode  .lta-qnext { background: var(--ms-accent); color: #08222c; }
body.light-mode .lta-qnext { background: #1d7fa3; color: #fff; }

/* W-319: Das laufende Spiel trägt dieselbe Zeilenform wie eine wartende Partie, seine
   Marke aber gedeckt — die Akzentfarbe gehört der Ansage „Nächstes Spiel", die eine
   Aufforderung ist. Was ohnehin schon läuft, muss nicht rufen. */
/* W-350: „Beendet" (der abgespielte Tisch, der noch auf der Anzeige steht) trägt dieselbe
   Form — es ist derselbe Gegenstand eine Stufe weiter, und eine zweite Formensprache dafür
   wäre eine Unterscheidung ohne Unterschied. */
.lta-qlive,
.lta-qdone {
    padding: .18rem .45rem;
    min-width: 4.4rem;
    border-radius: var(--ms-r-md);
    letter-spacing: .04em;
    text-transform: uppercase;
    opacity: 1;
}
body.dark-mode  .lta-qrow.is-live,
body.dark-mode  .lta-qrow.is-done { background: rgba(255, 255, 255, .12); }
body.light-mode .lta-qrow.is-live,
body.light-mode .lta-qrow.is-done { background: rgba(255, 255, 255, .92); }
body.dark-mode  .lta-qlive,
body.dark-mode  .lta-qdone { background: rgba(255, 255, 255, .16); color: #e6edf3; }
body.light-mode .lta-qlive,
body.light-mode .lta-qdone { background: rgba(0, 0, 0, .12); color: #23303a; }

.lta-qmain { min-width: 0; margin-right: auto; }
.lta-qplayers { display: block; font-weight: 600; }
.lta-qvs { opacity: var(--ms-dim-3); }
.lta-qmeta { display: block; font-size: var(--ms-fs-small); opacity: var(--ms-dim-2); }

.lta-qact {
    display: flex;
    gap: .3rem;
    flex-shrink: 0;
}

@media (max-width: 720px) {
    /* Schmal bricht die Zeile um: Marke und Namen oben, die Knöpfe darunter rechts.
       Die Marke behält ihre Zeile mit den Namen — sie erklärt sie. */
    .lta-qrow { flex-wrap: wrap; }
    .lta-qmain { flex: 1 1 60%; }
    .lta-qact { flex: 1 1 100%; justify-content: flex-end; }
}

/* ---- Admin-Leiste des Wettkampftags ----------------------------------------- */
.live-tables-bar {
    display: flex;
    justify-content: flex-end;
    gap: .5rem;
    flex-wrap: wrap;
    margin-bottom: .5rem;
}

/* W-325: Der Ansichts-Schalter ändert nichts am Wettkampf — er steht deshalb am linken Rand,
   die Knöpfe, die es tun, bleiben rechts. Abstand statt Trennlinie (Konvention N9); breiter
   als hier geht es in dieser Leiste nicht. Bricht sie um (Handy), steht er allein in der
   ersten Zeile — auch dann getrennt von den anderen. */
.live-tables-bar .live-view-toggle { margin-right: auto; }

/* Die Erklärzeile erscheint genau einmal — sie ist eine Antwort auf eine Frage, die sich
   beim ersten Ausschalten stellt, und danach nie wieder ein Dauertext. */
.live-view-note {
    font-size: var(--ms-fs-small);
    opacity: var(--ms-dim-2);
    margin: -.25rem 0 .5rem;
}

/* W-300: Das Einrichtungs-Band hinter dem Knopf „Einrichtung". Dieselbe Breite und
   Zentrierung wie das Tischformular darunter — beide klappen aus derselben Leiste auf und
   sollen als ein Paar wirken, nicht als zwei verschieden gebaute Kästen. */
.live-guide {
    max-width: 900px;
    margin: 0 auto 1rem;
}
.live-guide .ms-setup-guide { margin-bottom: 0; }

/* Offen ist ein Zustand, kein Angebot — genau wie beim Stift der Tisch-Kachel
   (`.lr-edit.is-open`). Der Knopf bekommt dafür eine bleibende Fläche, damit auf einen
   Blick klar ist, wozu das Formular darunter gehört. Ohne das sähe der geöffnete Knopf
   aus wie jeder andere, und bei mehreren offenen Panels wäre nicht zu sehen, welcher
   Klick den Kasten aufgemacht hat.

   Die Selektoren sind bewusst spezifisch (`body.<mode> .live-tables-bar .btn.is-open`):
   Bootstraps `.btn-outline-secondary` und dessen `:hover` müssen überstimmt werden, und
   zwar ohne `!important`. Der Rahmen wird mitgefärbt, sonst bliebe eine graue Linie um
   eine gefüllte Fläche stehen (Konvention N9: keine Rahmen als UI-Mittel). */
body.dark-mode .live-tables-bar .btn.is-open {
    background: rgba(255, 255, 255, .22);
    border-color: transparent;
    color: #fff;
}
body.light-mode .live-tables-bar .btn.is-open {
    background: rgba(0, 0, 0, .16);
    border-color: transparent;
    color: #212529;
}

.live-tables-form {
    max-width: 900px;
    margin: 0 auto 1rem;
    padding: 1.25rem;
    border-radius: var(--ms-r-lg);
}
body.dark-mode  .live-tables-form { background: var(--ms-surface-variant); color: #fff; }
body.light-mode .live-tables-form { background: #f1f3f5; color: #212529; }

.ltf-lead { font-size: var(--ms-fs-body); opacity: var(--ms-dim-2); }

.ltf-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
    gap: .5rem;
    margin: 1rem 0 0;
}

/* Ein Tisch ist eine Fläche, die man antippt — kein Häkchen in einer Liste. Der
   angehakte Zustand hebt sich über die Fläche ab, damit auf einen Blick klar ist,
   was heute läuft. */
.ltf-table {
    display: flex;
    align-items: center;
    gap: .6rem;
    padding: .6rem .75rem;
    border-radius: var(--ms-r-md);
    cursor: pointer;
    min-height: 44px;
}
body.dark-mode  .ltf-table { background: rgba(255, 255, 255, .06); }
body.light-mode .ltf-table { background: rgba(0, 0, 0, .05); }
body.dark-mode  .ltf-table.is-on { background: rgba(93, 150, 106, .24); }
body.light-mode .ltf-table.is-on { background: rgba(93, 150, 106, .18); }

.ltf-table .form-check-input { margin: 0; flex-shrink: 0; }
.ltf-table-text { min-width: 0; }
.ltf-table-name { display: block; font-weight: 600; }
.ltf-table-meta {
    display: block;
    font-size: var(--ms-fs-small);
    opacity: var(--ms-dim-2);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ltf-msg {
    margin-top: .75rem;
    padding: .5rem .75rem;
    border-radius: var(--ms-r-md);
    font-size: var(--ms-fs-body);
}
body.dark-mode  .ltf-msg.is-error { background: rgba(190, 90, 80, .28); }
body.light-mode .ltf-msg.is-error { background: rgba(190, 90, 80, .18); }

.ltf-actions {
    display: flex;
    flex-wrap: wrap;
    gap: .75rem;
    margin-top: 1rem;
}

@media (max-width: 640px) {
    .ltf-actions { flex-direction: column; align-items: stretch; }
    .ltf-actions .btn { width: 100%; }
}

/* ===== W-277: Spielerauswahl — ein Feld, Vorschläge darunter =====
   Stand bis W-299 in `admin.css`; seit das Tisch-Panel auf der Live-Seite sitzt,
   gehört sie hierher — einmal für Setup und Live-Seite. */
.ms-pick { position: relative; }
/* Die Liste liegt über dem Formular statt darin, damit die Felder darunter beim
   Tippen nicht springen. Getrennt wird nach Konvention N9 über die eigene Fläche
   und den Schatten, nicht über Haarlinien zwischen den Einträgen. */
.ms-pick-list {
    position: absolute;
    z-index: 1055;
    top: 100%;
    left: 0;
    right: 0;
    margin: .25rem 0 0;
    padding: .25rem;
    list-style: none;
    max-height: 17rem;
    overflow-y: auto;
    background: var(--bs-body-bg);
    border-radius: var(--ms-r-md);
    box-shadow: var(--ms-shadow-3);
}
.ms-pick-option {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    padding: .4rem .6rem;
    border-radius: var(--ms-r-sm);
    cursor: pointer;
}
.ms-pick-option.is-active,
.ms-pick-option:hover { background: var(--bs-secondary-bg); }
.ms-pick-club {
    color: var(--bs-secondary-color);
    font-size: .85em;
    text-align: right;
    white-space: nowrap;
}
.ms-pick-hint.is-profile { color: var(--bs-success); }

/* ===== W-303: Profil anlegen, ohne das Formular zu verlassen =====
   Die Hinweiszeile steht immer da, auch leer: eine Zeile, die erst beim Auswählen
   erscheint, würde nur das eine Feld verschieben (derselbe Sprung wie in W-260).
   „Profil anlegen" sitzt IN dieser Zeile, gleich hinter dem Hinweis, auf den es antwortet —
   als schlichter Text-Verweis in Hinweisgröße. Eine Schaltfläche wäre höher als die
   reservierte Zeile und würde beim Erscheinen genau den Sprung machen, den die reservierte
   Zeile verhindern soll. */
.ms-pick-foot {
    display: flex;
    align-items: baseline;
    flex-wrap: nowrap;
    gap: 0 .5rem;
    min-height: 1.5em;
    margin-top: .25rem;
}
/* W-327: Neben „Profil: Name · Verein" steht jetzt auch „Profil bearbeiten" — bei Platzmangel
   kürzt der Hinweis, die Zeile bricht nie um und der Verweis bleibt ganz. */
.ms-pick-hint {
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ms-pick-new-open {
    flex: 0 0 auto;
    white-space: nowrap;
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    color: var(--bs-link-color, #6ea8fe);
    text-decoration: underline;
    cursor: pointer;
}
.ms-pick-new-open:hover { color: var(--bs-link-hover-color, #8bb9fe); }
/* Die drei Felder stehen auf einer eigenen, gedeckten Fläche — abgesetzt wird nach
   Konvention N9 über Fläche und Abstand, nicht über einen Rahmen. */
.ms-pick-new {
    margin-top: .5rem;
    padding: .6rem .7rem;
    background: var(--bs-secondary-bg);
    border-radius: var(--ms-r-md);
}
.ms-pick-new-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
    gap: .5rem .75rem;
}
.ms-pick-new-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem;
    margin-top: .6rem;
}
.ms-pick-new-msg.is-error { color: var(--bs-danger); }
@media (pointer: coarse) {
    .ms-pick-option { min-height: 40px; }
}
