/* ═══════════════════════════════════════════════════════════════════════════
   Loheide Hub – Oberfläche nach Material Design 2

   Was das hier ist: eine vollständige, handgeschriebene Umsetzung der
   Material-2-Bausteine, die diese Anwendung braucht. Keine Bibliothek, kein
   Build-Schritt, keine fremden Abrufe – die Content-Security-Policy erlaubt
   nur eigene Quellen, und das soll so bleiben.

   ── Die vier Regeln, aus denen alles Übrige folgt ─────────────────────────
   1. Farbe hat Rollen, keine Namen. Nicht „blau", sondern „primary" und
      „on-primary". Das Farbschema für dunkel ist deshalb ein Austausch der
      Rollenwerte – nicht ein zweites Stylesheet.
   2. Höhe ersetzt Linien. Material trennt Flächen durch Schattenwurf in dp,
      nicht durch Haarlinien. Linien bleiben nur dort, wo sie Struktur INNEN
      zeigen (Tabellenzeilen, Trenner).
   3. Ein Raster von 8 px, für dichte Stellen 4 px. Jede Abmessung hier ist
      ein Vielfaches davon.
   4. Zustände sind Deckschichten, keine eigenen Farben. Überfahren 4 %,
      Fokus 12 %, Gedrückt 12 % der Vordergrundfarbe – immer dieselbe
      Rechnung, egal auf welcher Fläche.

   ── Warum die Klassennamen alt aussehen ───────────────────────────────────
   35 Vorlagen benutzen `.card`, `.btn`, `.input`, `.chip`. Der Vertrag bleibt
   deshalb unverändert; ausgetauscht wurde, was diese Namen BEDEUTEN. Die
   alten Farbvariablen (--ink, --surface, --accent …) leben als Aliasse
   weiter, weil Vorlagen und die Swagger-Anpassung sie direkt benutzen.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Schrift ─────────────────────────────────────────────────────────────────
   Roboto liegt lokal (siehe assets/fonts/HERKUNFT.md). Eine variable Datei je
   Zeichenbereich deckt 100–900 ab, deshalb nur zwei Dateien statt sechs.
   `font-display: swap` heißt: Text ist sofort lesbar, notfalls erst in der
   Systemschrift. Ein leeres Textfeld für 300 ms ist schlimmer als ein
   Schriftwechsel. */
@font-face {
    font-family: 'Roboto Hub';
    font-style: normal;
    font-weight: 100 900;
    font-display: swap;
    src: url('/assets/fonts/roboto-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
                   U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F,
                   U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
                   U+FFFD;
}
@font-face {
    font-family: 'Roboto Hub';
    font-style: normal;
    font-weight: 100 900;
    font-display: swap;
    src: url('/assets/fonts/roboto-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
                   U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF,
                   U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0,
                   U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ═══ Gestaltungsmarken ═══════════════════════════════════════════════════ */
:root {
    color-scheme: light;

    /* ── Farbrollen (hell) ────────────────────────────────────────────────
       Primärfarbe ist Material Blue 700. Sie trägt die Kopfleiste, die
       Hauptknöpfe und den aktiven Menüeintrag – und sonst nichts. Ein
       Werkzeug wird unruhig, wenn Farbe zur Dekoration wird.
       Die Sekundärfarbe (Teal) markiert ausschließlich Nebenaktionen. */
    --md-primary:            #1976d2;
    --md-primary-light:      #4791db;
    --md-primary-dark:       #115293;
    --md-on-primary:         #ffffff;

    --md-secondary:          #00796b;
    --md-secondary-dark:     #004c40;
    --md-on-secondary:       #ffffff;

    /* Verweise und farbiger Text auf hellen Flächen tragen die dunklere
       Abstufung der Primärfarbe. Grund ist gemessen, nicht Geschmack:
       #1976d2 auf Weiß erreicht 4,6:1 – auf einer getönten Fläche
       (Hinweiskasten, Chip, aktiver Menüeintrag) fällt derselbe Ton unter
       die geforderten 4,5:1. #115293 hält auch dort über 6:1.
       Für Flächen gilt das nicht: Weiß auf #1976d2 ist unkritisch. */
    --md-link:               var(--md-primary-dark);

    --md-background:         #f1f3f6;
    --md-surface:            #ffffff;
    --md-on-surface:         #000000;

    --md-error:              #b00020;
    --md-on-error:           #ffffff;
    --md-success:            #1b6b3a;
    --md-warning:            #a05a00;
    --md-info:               #0d6ea8;

    /* Flächen über der Grundfläche. Im Hellen echte Farbwerte, im Dunklen
       die Aufhellungsstufen aus der Material-Spezifikation. */
    --md-surface-1:          #ffffff;
    --md-surface-2:          #f5f6f8;
    --md-surface-3:          #eceef1;
    --md-surface-sunk:       #f8f9fa;

    /* ── Textschärfen ─────────────────────────────────────────────────────
       Material stuft Text nicht über eigene Grautöne ab, sondern über die
       Deckkraft derselben Farbe: 87 % / 60 % / 38 %. Das hält den Kontrast
       auf jeder Fläche automatisch richtig. */
    --md-emphasis-high:      .87;
    --md-emphasis-medium:    .60;
    --md-emphasis-disabled:  .38;

    --md-text-high:          rgba(0, 0, 0, .87);
    --md-text-medium:        rgba(0, 0, 0, .60);
    --md-text-disabled:      rgba(0, 0, 0, .38);
    --md-divider:            rgba(0, 0, 0, .12);
    --md-divider-strong:     rgba(0, 0, 0, .24);

    /* Zustandsdeckschichten – überall dieselbe Rechnung. */
    --md-state-hover:        .04;
    --md-state-focus:        .12;
    --md-state-pressed:      .12;
    --md-overlay-hover:      rgba(0, 0, 0, .04);
    --md-overlay-focus:      rgba(0, 0, 0, .12);
    --md-overlay-primary:    rgba(25, 118, 210, .08);
    --md-overlay-primary-2:  rgba(25, 118, 210, .12);

    /* Getönte Flächen für Statusmeldungen. Bewusst sehr blass: die Aussage
       trägt der Text, die Farbe sortiert nur. */
    --md-tint-success:       #e8f4ec;
    --md-tint-warning:       #fdf3e3;
    --md-tint-error:         #fdecee;
    --md-tint-info:          #e8f2fb;
    --md-tint-primary:       #e8f1fb;

    /* ── Höhe ────────────────────────────────────────────────────────────
       Die Schattenwerte stammen unverändert aus der Material-Spezifikation.
       Jede Stufe besteht aus drei Schatten: Kernschatten, Halbschatten,
       Umgebungslicht. Deshalb sehen nachgebaute „box-shadow: 0 2px 4px"
       immer nach Aufkleber aus und diese nicht. */
    --md-elev-0:  none;
    --md-elev-1:  0 2px 1px -1px rgba(0,0,0,.20), 0 1px 1px 0 rgba(0,0,0,.14), 0 1px 3px 0 rgba(0,0,0,.12);
    --md-elev-2:  0 3px 1px -2px rgba(0,0,0,.20), 0 2px 2px 0 rgba(0,0,0,.14), 0 1px 5px 0 rgba(0,0,0,.12);
    --md-elev-3:  0 3px 3px -2px rgba(0,0,0,.20), 0 3px 4px 0 rgba(0,0,0,.14), 0 1px 8px 0 rgba(0,0,0,.12);
    --md-elev-4:  0 2px 4px -1px rgba(0,0,0,.20), 0 4px 5px 0 rgba(0,0,0,.14), 0 1px 10px 0 rgba(0,0,0,.12);
    --md-elev-6:  0 3px 5px -1px rgba(0,0,0,.20), 0 6px 10px 0 rgba(0,0,0,.14), 0 1px 18px 0 rgba(0,0,0,.12);
    --md-elev-8:  0 5px 5px -3px rgba(0,0,0,.20), 0 8px 10px 1px rgba(0,0,0,.14), 0 3px 14px 2px rgba(0,0,0,.12);
    --md-elev-12: 0 7px 8px -4px rgba(0,0,0,.20), 0 12px 17px 2px rgba(0,0,0,.14), 0 5px 22px 4px rgba(0,0,0,.12);
    --md-elev-16: 0 8px 10px -5px rgba(0,0,0,.20), 0 16px 24px 2px rgba(0,0,0,.14), 0 6px 30px 5px rgba(0,0,0,.12);
    --md-elev-24: 0 11px 15px -7px rgba(0,0,0,.20), 0 24px 38px 3px rgba(0,0,0,.14), 0 9px 46px 8px rgba(0,0,0,.12);

    /* ── Form ─────────────────────────────────────────────────────────────
       Material 2 rundet klein: 4 px für Flächen, Vollrundung für Chips. */
    --md-shape-sm:  2px;
    --md-shape:     4px;
    --md-shape-lg:  8px;
    --md-shape-pill: 999px;

    /* ── Bewegung ─────────────────────────────────────────────────────────
       „standard" für alles Sichtbare, „decelerate" für Eintritte. Die Kurven
       sind die aus der Spezifikation; gleichmäßige Übergänge (linear) wirken
       mechanisch, weil nichts in der Welt so beschleunigt. */
    --md-ease-standard:   cubic-bezier(.4, 0, .2, 1);
    --md-ease-decelerate: cubic-bezier(0, 0, .2, 1);
    --md-ease-accelerate: cubic-bezier(.4, 0, 1, 1);
    --md-dur-short:  90ms;
    --md-dur-medium: 150ms;
    --md-dur-long:   250ms;

    --md-font: 'Roboto Hub', Roboto, -apple-system, BlinkMacSystemFont,
               'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
    --md-mono: ui-monospace, 'SF Mono', 'JetBrains Mono', 'Cascadia Mono',
               Menlo, Consolas, 'Liberation Mono', monospace;

    --nav-w: 256px;
    --topbar-h: 56px;

    /* ── Rollen, die sich nicht einfach aufhellen lassen ──────────────────
       Die Kopfleiste trägt im Hellen die Primärfarbe. Im Dunklen verlangt
       Material dort Flächenfarbe mit Aufhellung, weil große farbige Flächen
       auf dunklem Grund blenden – das ist keine Umrechnung, sondern eine
       andere Entscheidung. Als eigene Rolle steht sie hier statt als
       Sonderregel weiter unten: So gibt es genau EINEN Block, der sich
       zwischen den Erscheinungsbildern unterscheidet, und der lässt sich
       umschalten. */
    --md-topbar-bg:               var(--md-primary);
    --md-topbar-fg:               var(--md-on-primary);
    --md-topbar-btn-border:       rgba(255, 255, 255, .7);
    --md-topbar-btn-fg:           var(--md-on-primary);
    --md-topbar-btn-hover-bg:     rgba(255, 255, 255, .12);
    --md-topbar-btn-hover-border: #fff;
    --md-topbar-text-fg:          var(--md-on-primary);

    --md-brandmark-bg: #fff;
    --md-brandmark-fg: var(--md-primary-dark);

    /* Welche Logofassung sichtbar ist, ist eine Rolle wie jede andere Farbe
       auch. Als Schalter im Stylesheet waere es eine zweite Medienabfrage -
       und die erreichte der Schalter in der Kopfleiste nicht mehr. */
    --md-logo-hell:   block;
    --md-logo-dunkel: none;

    --md-select-arrow: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='rgba(0,0,0,0.6)'%3E%3Cpath d='M7 10l5 5 5-5z'/%3E%3C/svg%3E");

    /* ── Aliasse für den Bestand ──────────────────────────────────────────
       Vorlagen und swagger-dark.css sprechen diese Namen direkt an. Sie
       zeigen jetzt auf die Material-Rollen; nichts davon ist eine eigene
       Farbe mehr. */
    --canvas:       var(--md-background);
    --surface:      var(--md-surface);
    --surface-2:    var(--md-surface-2);
    --surface-3:    var(--md-surface-3);
    --surface-sunk: var(--md-surface-sunk);

    --ink:          var(--md-text-high);
    --ink-2:        var(--md-text-medium);
    --ink-3:        var(--md-text-medium);

    --line:         var(--md-divider);
    --line-strong:  var(--md-divider-strong);

    --accent:       var(--md-primary);
    --accent-hover: var(--md-primary-dark);
    --accent-soft:  var(--md-tint-primary);
    --accent-line:  rgba(25, 118, 210, .32);
    --on-accent:    var(--md-on-primary);

    --ok:        var(--md-success);
    --ok-soft:   var(--md-tint-success);
    --ok-line:   rgba(27, 107, 58, .30);
    --warn:      var(--md-warning);
    --warn-soft: var(--md-tint-warning);
    --warn-line: rgba(160, 90, 0, .30);
    --err:       var(--md-error);
    --err-soft:  var(--md-tint-error);
    --err-line:  rgba(176, 0, 32, .30);
    --info:      var(--md-info);
    --info-soft: var(--md-tint-info);
    --info-line: rgba(13, 110, 168, .30);

    --r-xs: var(--md-shape-sm);
    --r-sm: var(--md-shape);
    --r-md: var(--md-shape);
    --r-lg: var(--md-shape);

    --font: var(--md-font);
    --mono: var(--md-mono);
}

/* ── Farbschema dunkel ───────────────────────────────────────────────────────
   Material 2 macht im Dunklen drei Dinge anders, und alle drei stehen hier:

   1. Die Grundfläche ist #121212, nicht Schwarz. Reines Schwarz frisst jeden
      Schatten – Höhe wäre nicht mehr darstellbar.
   2. Höhe wird durch AUFHELLUNG gezeigt, nicht durch Schatten: je höher eine
      Fläche, desto mehr Weiß liegt darüber (5 % bei 1 dp bis 16 % bei 24 dp).
      Die Werte unten sind genau diese Stufen, ausgerechnet.
   3. Gesättigte Farben blenden auf dunklem Grund. Deshalb die 200er-Töne
      (Blue 200 statt Blue 700) – und auf ihnen steht dann schwarzer Text. */
/* ── Dunkel: automatisch ─────────────────────────────────────────────────
   Gilt, solange niemand ausdrücklich hell gewählt hat. Das `:not()` ist der
   ganze Unterschied zwischen „folgt dem System" und „übersteuert es". */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        color-scheme: dark;

        --md-primary:       #90caf9;
        --md-primary-light: #b3dcfb;
        --md-primary-dark:  #64b5f6;
        --md-on-primary:    rgba(0, 0, 0, .87);

        --md-secondary:     #80cbc4;
        --md-secondary-dark:#4db6ac;
        --md-on-secondary:  rgba(0, 0, 0, .87);

        --md-background:    #121212;
        --md-surface:       #1e1e1e;   /*  1 dp – Karten, Menü            */
        --md-on-surface:    #ffffff;

        --md-surface-1:     #1e1e1e;   /*  1 dp                            */
        --md-surface-2:     #272727;   /*  4 dp – Eingabefelder            */
        --md-surface-3:     #2e2e2e;   /*  8 dp – Kopfleiste, Dialoge      */
        --md-surface-sunk:  #181818;   /*  eingesenkt: unter der Karte     */

        --md-error:         #cf6679;
        --md-on-error:      rgba(0, 0, 0, .87);
        --md-success:       #81c995;
        --md-warning:       #e3b341;
        --md-info:          #79b8f3;

        --md-text-high:     rgba(255, 255, 255, .87);
        --md-text-medium:   rgba(255, 255, 255, .60);
        --md-text-disabled: rgba(255, 255, 255, .38);
        --md-divider:       rgba(255, 255, 255, .12);
        --md-divider-strong:rgba(255, 255, 255, .24);

        --md-overlay-hover:     rgba(255, 255, 255, .04);
        --md-overlay-focus:     rgba(255, 255, 255, .12);
        --md-overlay-primary:   rgba(144, 202, 249, .12);
        --md-overlay-primary-2: rgba(144, 202, 249, .20);

        /* Getönte Flächen im Dunklen: nicht die helle Tönung abdunkeln,
           sondern die Statusfarbe mit geringer Deckkraft über die Fläche
           legen. Sonst entstehen schlammige Töne. */
        --md-tint-success: #16281c;
        --md-tint-warning: #2a2312;
        --md-tint-error:   #2c1a1e;
        --md-tint-info:    #16273a;
        --md-tint-primary: #16273a;

        --md-link:     var(--md-primary);

        --accent-line: rgba(144, 202, 249, .38);
        --ok-line:     rgba(129, 201, 149, .32);
        --warn-line:   rgba(227, 179, 65, .32);
        --err-line:    rgba(207, 102, 121, .32);
        --info-line:   rgba(121, 184, 243, .32);

        --md-topbar-bg:               var(--md-surface-3);
        --md-topbar-fg:               var(--md-text-high);
        --md-topbar-btn-border:       var(--md-divider-strong);
        --md-topbar-btn-fg:           var(--md-text-high);
        --md-topbar-btn-hover-bg:     var(--md-overlay-hover);
        --md-topbar-btn-hover-border: var(--md-text-medium);
        --md-topbar-text-fg:          var(--md-primary);

        /* Im Dunklen ist die Leiste nicht farbig - dann bleibt das Zeichen
           die gefuellte Scheibe statt der Umkehrung. */
        --md-brandmark-bg: var(--md-primary);
        --md-brandmark-fg: var(--md-on-primary);

        --md-logo-hell:   none;
        --md-logo-dunkel: block;

        --md-select-arrow: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='rgba(255,255,255,0.6)'%3E%3Cpath d='M7 10l5 5 5-5z'/%3E%3C/svg%3E");
    }
}

/* ── Dunkel: ausdrücklich gewählt ────────────────────────────────────────
   Dieselben Werte ohne Medienabfrage. Beide Fassungen müssen gleich
   bleiben; tests/smoke.php vergleicht sie, weil eine Abweichung sonst nur
   einer der beiden Gruppen auffiele – und zwar der, die man selbst nicht
   benutzt. */
:root[data-theme="dark"] {
    color-scheme: dark;

    --md-primary:       #90caf9;
    --md-primary-light: #b3dcfb;
    --md-primary-dark:  #64b5f6;
    --md-on-primary:    rgba(0, 0, 0, .87);

    --md-secondary:     #80cbc4;
    --md-secondary-dark:#4db6ac;
    --md-on-secondary:  rgba(0, 0, 0, .87);

    --md-background:    #121212;
    --md-surface:       #1e1e1e;   /*  1 dp – Karten, Menü            */
    --md-on-surface:    #ffffff;

    --md-surface-1:     #1e1e1e;   /*  1 dp                            */
    --md-surface-2:     #272727;   /*  4 dp – Eingabefelder            */
    --md-surface-3:     #2e2e2e;   /*  8 dp – Kopfleiste, Dialoge      */
    --md-surface-sunk:  #181818;   /*  eingesenkt: unter der Karte     */

    --md-error:         #cf6679;
    --md-on-error:      rgba(0, 0, 0, .87);
    --md-success:       #81c995;
    --md-warning:       #e3b341;
    --md-info:          #79b8f3;

    --md-text-high:     rgba(255, 255, 255, .87);
    --md-text-medium:   rgba(255, 255, 255, .60);
    --md-text-disabled: rgba(255, 255, 255, .38);
    --md-divider:       rgba(255, 255, 255, .12);
    --md-divider-strong:rgba(255, 255, 255, .24);

    --md-overlay-hover:     rgba(255, 255, 255, .04);
    --md-overlay-focus:     rgba(255, 255, 255, .12);
    --md-overlay-primary:   rgba(144, 202, 249, .12);
    --md-overlay-primary-2: rgba(144, 202, 249, .20);

    /* Getönte Flächen im Dunklen: nicht die helle Tönung abdunkeln,
       sondern die Statusfarbe mit geringer Deckkraft über die Fläche
       legen. Sonst entstehen schlammige Töne. */
    --md-tint-success: #16281c;
    --md-tint-warning: #2a2312;
    --md-tint-error:   #2c1a1e;
    --md-tint-info:    #16273a;
    --md-tint-primary: #16273a;

    --md-link:     var(--md-primary);

    --accent-line: rgba(144, 202, 249, .38);
    --ok-line:     rgba(129, 201, 149, .32);
    --warn-line:   rgba(227, 179, 65, .32);
    --err-line:    rgba(207, 102, 121, .32);
    --info-line:   rgba(121, 184, 243, .32);

    --md-topbar-bg:               var(--md-surface-3);
    --md-topbar-fg:               var(--md-text-high);
    --md-topbar-btn-border:       var(--md-divider-strong);
    --md-topbar-btn-fg:           var(--md-text-high);
    --md-topbar-btn-hover-bg:     var(--md-overlay-hover);
    --md-topbar-btn-hover-border: var(--md-text-medium);
    --md-topbar-text-fg:          var(--md-primary);

    /* Im Dunklen ist die Leiste nicht farbig - dann bleibt das Zeichen
       die gefuellte Scheibe statt der Umkehrung. */
    --md-brandmark-bg: var(--md-primary);
    --md-brandmark-fg: var(--md-on-primary);

    --md-logo-hell:   none;
    --md-logo-dunkel: block;

    --md-select-arrow: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='rgba(255,255,255,0.6)'%3E%3Cpath d='M7 10l5 5 5-5z'/%3E%3C/svg%3E");
}


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

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

body {
    margin: 0;
    /* Lange Zeichenketten ohne Leerzeichen – Adressen, Schlüssel, Firmennamen
       mit Bindestrichen – haben keine Stelle, an der ein Browser von sich aus
       umbricht. Auf einem breiten Bildschirm fällt das nicht auf; auf einem
       Telefon schiebt eine einzige solche Zeile die ganze Seite auf. */
    overflow-wrap: break-word;
    font-family: var(--md-font);
    /* Body 2 – die Arbeitsschriftgröße dieser Anwendung. */
    font-size: 14px;
    line-height: 1.5;
    letter-spacing: .0179em;
    color: var(--md-text-high);
    background: var(--md-background);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* ── Schriftskala ────────────────────────────────────────────────────────────
   Material vergibt jeder Größe einen eigenen Buchstabenabstand – kleine
   Schrift braucht mehr Luft, große weniger. Genau das unterscheidet eine
   Skala von „einfach kleiner". */
h1, h2, h3, h4, h5, h6 {
    margin: 0 0 8px;
    color: var(--md-text-high);
    font-weight: 500;
}
h1 { font-size: 1.5rem;   line-height: 2rem;    letter-spacing: 0;       font-weight: 400; }
h2 { font-size: 1.25rem;  line-height: 1.75rem; letter-spacing: .0075em; }
h3 { font-size: 1rem;     line-height: 1.5rem;  letter-spacing: .0094em; }
h4 { font-size: .875rem;  line-height: 1.375rem;letter-spacing: .0071em; }
h5, h6 { font-size: .8125rem; line-height: 1.25rem; letter-spacing: .0077em; }

p { margin: 0 0 12px; }
p:last-child { margin-bottom: 0; }

a { color: var(--md-link); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 2px; }

code, kbd, pre, .sha, .mono { font-family: var(--md-mono); }
code {
    font-size: .8571em;
    background: var(--md-surface-2);
    padding: .1em .36em;
    border-radius: var(--md-shape-sm);
    overflow-wrap: anywhere;
    letter-spacing: 0;
}
pre {
    background: var(--md-surface-sunk);
    border: 1px solid var(--md-divider);
    padding: 12px 16px;
    border-radius: var(--md-shape);
    overflow-x: auto;
    margin: 0 0 12px;
    font-size: .8125rem;
    line-height: 1.6;
    letter-spacing: 0;
}
pre code { background: none; padding: 0; font-size: inherit; }

hr { border: 0; border-top: 1px solid var(--md-divider); margin: 24px 0; }

::selection { background: var(--md-primary); color: var(--md-on-primary); }

/* Fokus nur für die Tastatur sichtbar – ein Mausklick soll keinen Rahmen
   hinterlassen, eine Tabulatorreise sehr wohl. */
:focus-visible {
    outline: 2px solid var(--md-primary);
    outline-offset: 2px;
    border-radius: var(--md-shape-sm);
}

/* ═══ Aufbau: Menü und Kopfleiste ════════════════════════════════════════ */
.hub-shell { display: flex; min-height: 100vh; background: var(--md-background); }

/* Das Menü ist eine dauerhafte Navigationsschublade (permanent drawer).
   Sie liegt auf 0 dp – die Trennung zum Inhalt macht die Linie, nicht ein
   Schatten. Eine schwebende Schublade wäre die Variante für schmale Geräte;
   dort wird sie unten auch dazu. */
.hub-nav {
    width: var(--nav-w);
    flex: 0 0 var(--nav-w);
    background: var(--md-surface);
    border-right: 1px solid var(--md-divider);
    padding-bottom: 8px;
    display: flex;
    flex-direction: column;
    position: sticky;
    top: 0;
    height: 100vh;
    overflow-y: auto;
    z-index: 30;

    /* ── Der Bildlaufbalken ──────────────────────────────────────────────
       Der Standardbalken des Browsers ist breit, grau und gehört optisch zu
       keinem Element hier - er schneidet eine Rinne in die Seitenleiste.

       Stattdessen ein schmaler Griff ohne eigene Rinne, der nur andeutet,
       dass es weitergeht. Ganz ausblenden wäre falsch: Dann weiß niemand,
       dass unten noch etwas steht.

       `scrollbar-gutter: stable` hält den Platz frei, auch wenn gerade kein
       Balken nötig ist - sonst rückt das ganze Menü um ein paar Pixel, sobald
       ein Eintrag dazukommt. */
    scrollbar-gutter: stable;
    /* Firefox */
    scrollbar-width: thin;
    scrollbar-color: var(--md-divider-strong) transparent;
}

/* Chromium und Safari kennen scrollbar-color nicht und brauchen die
   ausführliche Schreibweise. */
.hub-nav::-webkit-scrollbar { width: 8px; }
.hub-nav::-webkit-scrollbar-track { background: transparent; }
.hub-nav::-webkit-scrollbar-thumb {
    background: var(--md-divider-strong);
    border-radius: 4px;
    /* Der durchsichtige Rand macht den Griff schmaler als die Spur, ohne
       dass er an der Kante klebt. */
    border: 2px solid transparent;
    background-clip: content-box;
}
.hub-nav:hover::-webkit-scrollbar-thumb { background-color: var(--md-text-medium); background-clip: content-box; }
.hub-nav::-webkit-scrollbar-thumb:hover { background-color: var(--md-text-high); background-clip: content-box; }

/* Der Markenblock ist so hoch wie die Kopfleiste und trägt dieselbe Farbe:
   oben läuft ein durchgehendes Band über die ganze Breite. Genau das meint
   Material mit einer Kopfleiste – nicht zwei Streifen nebeneinander. */
.hub-brand {
    display: flex;
    align-items: center;
    gap: 12px;
    height: var(--topbar-h);
    padding: 0 16px;
    background: var(--md-topbar-bg);
    color: var(--md-topbar-fg);
    font-size: .9375rem;
    font-weight: 500;
    letter-spacing: .0094em;
    flex: 0 0 var(--topbar-h);
    position: sticky;
    top: 0;
    z-index: 2;
}
.hub-brand:hover { text-decoration: none; color: var(--md-on-primary); }
/* Das Markenzeichen steht an zwei sehr verschiedenen Orten: in der farbigen
   Kopfleiste und – ohne diese Leiste – auf der weißen Anmeldekarte. Deshalb
   ist der Grundfall die gefüllte Scheibe in Primärfarbe; nur INNERHALB der
   farbigen Leiste dreht sich das um, sonst stünde Blau auf Blau.
   Vorher war es fest weiße Schrift auf 22 % Weiß: in der Leiste zu schwach
   (3,2:1) und auf der Anmeldekarte weiß auf weiß – also unsichtbar. */
.hub-brand-mark {
    width: 32px; height: 32px;
    flex: 0 0 32px;
    border-radius: 50%;
    background: var(--md-primary);
    color: var(--md-on-primary);
    display: grid; place-items: center;
    font-weight: 500; font-size: .8125rem;
    letter-spacing: .05em;
}
/* Auf der farbigen Leiste ist das Zeichen die Umkehrung: weiße Scheibe,
   blaue Schrift. Im Dunklen ist die Leiste nicht farbig, dann bleibt es die
   gefüllte Scheibe. Beides steckt in den Rollen. */
.hub-brand .hub-brand-mark {
    background: var(--md-brandmark-bg);
    color: var(--md-brandmark-fg);
}
.hub-brand small {
    display: block;
    font-weight: 400;
    font-size: .625rem;
    /* Overline: der kleinste Stil der Skala, weit gesperrt und versal. */
    letter-spacing: .15em;
    text-transform: uppercase;
    opacity: .78;
    margin-top: -2px;
}

/* ── Ein hochgeladenes Logo ──────────────────────────────────────────────
   Steht dort, wo sonst die Scheibe mit dem Kürzel und der Schriftzug stehen –
   und ersetzt beides: Ein Logo IST der Name. Deshalb darf es den Platz auch
   haben und ist deutlich größer als das Kürzel, an dessen Stelle es tritt.

   Die Höhe ist gesetzt, die Breite folgt – ein Logo ist mal ein Quadrat und
   mal ein langer Schriftzug, und was hochgeladen wird, weiß niemand vorher.
   Deshalb darf der Kasten auch schrumpfen (`flex: 0 1 auto`): Neben dem
   Bereichsnamen im Menü ist die Leiste 256 px breit, und ein Logo, das nicht
   passt, soll kleiner werden statt das Menü auseinanderzuschieben. `contain`
   hält dabei das Seitenverhältnis. */
.hub-logo {
    display: block;
    height: 40px;
    width: auto;
    max-width: 100%;
    min-width: 0;
    object-fit: contain;
    flex: 0 1 auto;
}
/* Auf der Anmeldekarte steht es allein und die Karte ist 400 px breit – hier
   ist das Logo das Erste, was jemand von dieser Anwendung sieht. */
.auth-card .hub-logo { height: 60px; }

/* Die Zeile über dem Anmeldeformular. War ein style-Attribut im Gerüst; mit
   zwei Fassungen und einem Rückfall aufs Kürzel sind es zu viele Fälle für
   eine Zeile HTML. */
.auth-brand {
    display: flex;
    align-items: center;
    gap: .7rem;
    margin-bottom: 1.5rem;
}

/* Welche der beiden Fassungen sichtbar ist, entscheidet das Erscheinungsbild –
   aber nur dort, wo die Fläche mitwechselt (die Anmeldekarte). Die Leiste ist
   hell wie dunkel eine dunkle Fläche; sie bekommt aus dem Gerüst nur ein Bild
   und braucht diese Regeln gar nicht.

   Die Wahl steckt in zwei Rollen weiter oben und nicht in einer eigenen
   Medienabfrage: Sonst gäbe es eine zweite Stelle, die am System hängt – und
   genau die erreichte der Schalter „Automatisch / Hell / Dunkel" nicht. */
.hub-logo--hell   { display: var(--md-logo-hell); }
.hub-logo--dunkel { display: var(--md-logo-dunkel); }

/* Abschnittsüberschrift im Menü – Overline-Stil. */
.hub-nav-section {
    font-size: .6875rem;
    text-transform: uppercase;
    letter-spacing: .12em;
    color: var(--md-text-medium);
    padding: 20px 16px 6px;
    font-weight: 500;
}

/* Menüeinträge sind Listenzeilen von 44 px. Material sieht 48 px vor; hier
   ist die Liste lang und die Zeile trotzdem gut zu treffen. */
.hub-nav a.hub-nav-item,
.hub-nav button.hub-nav-item {
    position: relative;
    display: flex;
    align-items: center;
    gap: 16px;
    min-height: 44px;
    padding: 0 16px;
    color: var(--md-text-medium);
    font-size: .875rem;
    font-weight: 500;
    letter-spacing: .0071em;
    border: 0;
    border-radius: 0;
    background: none;
    transition: background var(--md-dur-short) var(--md-ease-standard),
                color var(--md-dur-short) var(--md-ease-standard);
}
.hub-nav a.hub-nav-item:hover,
.hub-nav button.hub-nav-item:hover {
    background: var(--md-overlay-hover);
    color: var(--md-text-high);
    text-decoration: none;
}
/* Der aktive Eintrag: Primärfarbe auf einer 12-%-Deckschicht derselben Farbe.
   Kein zusätzlicher Balken – die Fläche IST die Markierung. */
.hub-nav a.hub-nav-item[aria-current="page"] {
    background: var(--md-overlay-primary-2);
    color: var(--md-link);
}
.hub-nav a.hub-nav-item[aria-current="page"]::before {
    content: "";
    position: absolute;
    left: 0; top: 0; bottom: 0;
    width: 3px;
    background: var(--md-primary);
}

/* Abmelden ist ein <button> in einem Formular – per GET abzumelden wäre ein
   Fehler, jeder Vorablader des Browsers beendete die Sitzung. Damit er nicht
   wie ein Knopf aussieht, muss die Browserumrandung ausdrücklich weg. */
.hub-nav button.hub-nav-item {
    width: 100%;
    font-family: inherit;
    text-align: left;
    cursor: pointer;
}

.hub-nav-foot {
    margin-top: auto;
    padding-top: 8px;
    border-top: 1px solid var(--md-divider);
}
.hub-nav-foot .hub-nav-account {
    padding: 0 16px 8px;
    font-size: .8125rem;
    color: var(--md-text-medium);
    overflow-wrap: anywhere;
}

.hub-nav-item .hub-icon { flex: 0 0 20px; width: 20px; height: 20px; }
.hub-nav-item .hub-badge { margin-left: auto; }

.hub-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }

/* Die Kopfleiste liegt auf 4 dp und bleibt beim Blättern stehen. Ihr Schatten
   ist das Einzige, was sagt: darunter läuft Inhalt durch. */
.hub-topbar {
    display: flex;
    align-items: center;
    gap: 16px;
    height: var(--topbar-h);
    padding: 0 24px;
    background: var(--md-topbar-bg);
    color: var(--md-topbar-fg);
    box-shadow: var(--md-elev-4);
    position: sticky; top: 0; z-index: 20;
}

/* Titel in der Kopfleiste: Header 6. */
.hub-topbar h1 {
    font-size: 1.25rem;
    font-weight: 500;
    letter-spacing: .0075em;
    margin: 0;
    color: inherit;
    line-height: 1.2;
    /* Neben der Überschrift steht auf manchen Seiten eine Aktion. Wird der
       Bildschirm sehr schmal, weicht die Überschrift – sie ist auf der Seite,
       auf der man ohnehin steht, die entbehrlichere der beiden Angaben. */
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.hub-topbar-actions { margin-left: auto; display: flex; flex-wrap: wrap; justify-content: flex-end; align-items: center; gap: 8px; }

/* Knöpfe in der farbigen Kopfleiste können nicht die Primärfarbe tragen –
   sie stünden auf sich selbst. Sie werden deshalb weiß umrandet. */
.hub-topbar .btn--tonal,
.hub-topbar .btn--outlined {
    background: transparent;
    border-color: var(--md-topbar-btn-border);
    color: var(--md-topbar-btn-fg);
    box-shadow: none;
}
.hub-topbar .btn--tonal:hover,
.hub-topbar .btn--outlined:hover {
    background: var(--md-topbar-btn-hover-bg);
    border-color: var(--md-topbar-btn-hover-border);
}
.hub-topbar .btn--text { color: var(--md-topbar-text-fg); }
.hub-topbar .btn--text:hover { background: var(--md-topbar-btn-hover-bg); }

/* ── Wie breit eine Seite wird ───────────────────────────────────────────
   Der Inhalt füllt den Bildschirm. Eine feste Obergrenze liess auf einem
   breiten Monitor die halbe Fläche leer – und ausgerechnet die Tabellen,
   die Platz gebrauchen könnten, blieben schmal.

   Lesbar bleibt es trotzdem: Fliesstext bekommt weiter unten ein eigenes
   Maximum, und die Formularraster wachsen in Spalten statt in die Breite.
   Was hier NICHT begrenzt wird, sind Tabellen und Kachelraster – genau die
   profitieren von jedem Pixel. */
.hub-content { padding: 24px; width: 100%; min-width: 0; }
.hub-content > * + * { margin-top: 16px; }

/* Zwei Blöcke im Fluss halten Abstand – auch wenn sie nicht direkt unter
   .hub-content hängen, sondern etwa gemeinsam in einem <form> stecken.
   Ohne diese Regel kleben Karten mit ihren Kanten aneinander. */
:is(.card, .card-grid, .stat-row, .notice, .filter-bar, .table-wrap, .empty)
+ :is(.card, .card-grid, .stat-row, .notice, .filter-bar, .table-wrap, .empty) {
    margin-top: 16px;
}
/* Behälter, die ihre Abstände selbst regeln, bleiben unberührt. */
:is(.card-grid, .row, .btn-row, .stat-row, .filter-bar)
> :is(.card, .card-grid, .stat-row, .notice, .filter-bar, .table-wrap, .empty) {
    margin-top: 0;
}

.hub-nav-toggle { display: none; }

/* ── Schmale Geräte ──────────────────────────────────────────────────────────
   Aus der dauerhaften Schublade wird eine schwebende (modal drawer): sie legt
   sich mit 16 dp über den Inhalt, statt ihn zu schieben. Das ist der
   Material-Weg und verhindert, dass der Inhalt auf 40 % Breite zusammenfällt. */
@media (max-width: 960px) {
    .hub-nav {
        position: fixed;
        top: 0; bottom: 0; left: 0;
        transform: translateX(-100%);
        transition: transform var(--md-dur-long) var(--md-ease-standard);
        box-shadow: var(--md-elev-16);
        border-right: 0;
        z-index: 40;
    }
    .hub-shell.nav-open .hub-nav { transform: translateX(0); }
    /* Abdunkelung dahinter: 32 % Schwarz, wie in der Spezifikation. */
    .hub-shell.nav-open::before {
        content: "";
        position: fixed; inset: 0;
        background: rgba(0, 0, 0, .32);
        z-index: 35;
    }
    .hub-nav-toggle { display: inline-flex; }
    .hub-content { padding: 16px; }
    .hub-topbar { padding: 0 8px 0 4px; gap: 8px; }
    .hub-topbar h1 { font-size: 1.125rem; }
}

/* ═══ Karten ═════════════════════════════════════════════════════════════ */
/* Eine Karte ist eine Fläche auf 1 dp. Kein Rahmen: In Material trennt der
   Schatten, nicht die Linie. Im Dunklen macht das die Aufhellung. */
.card {
    background: var(--md-surface);
    border-radius: var(--md-shape);
    box-shadow: var(--md-elev-1);
    padding: 16px 20px 20px;
}
.card--raised { box-shadow: var(--md-elev-4); }
.card--flat   { box-shadow: none; border: 1px solid var(--md-divider); }
.card > h2:first-child, .card > h3:first-child { margin-top: 0; }

.card-header {
    display: flex; align-items: center; gap: 12px;
    /* Überschrift links, Aktion rechts – auf einem Telefon passt das
       nebeneinander nicht mehr. Umbrechen ist dort die richtige Antwort;
       ohne das schiebt die Aktion die ganze Seite breiter als das Gerät. */
    flex-wrap: wrap;
    row-gap: 8px;
    min-height: 32px;
    padding-bottom: 12px;
    margin: 0 0 16px;
    border-bottom: 1px solid var(--md-divider);
}
.card-header h2, .card-header h3 { margin: 0; }

/* Beschreibungslisten tragen Firmennamen, Adressen und Nummern. In einem
   Raster ist die Mindestbreite einer Zelle sonst die Breite des längsten
   Wortes – und das schiebt auf einem Telefon die Seite auf. */
dl dt, dl dd { min-width: 0; overflow-wrap: anywhere; }
.card-header .hub-icon { color: var(--md-text-medium); flex: 0 0 20px; width: 20px; height: 20px; }
.card-header .spacer { margin-left: auto; }

/* min() statt der nackten Zahl: Auf einem 320-Pixel-Telefon ist eine
   Mindestbreite von 340 keine Mindestbreite mehr, sondern eine Spalte, die
   breiter ist als das Gerät – und die ganze Seite schiebt sich mit. Mit
   min(…, 100%) gilt die Zahl nur, solange sie hineinpasst. */
.card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr));
    gap: 16px;
}

/* ═══ Kennzahlen ═════════════════════════════════════════════════════════ */
.stat-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(170px, 100%), 1fr));
    background: var(--md-surface);
    border-radius: var(--md-shape);
    box-shadow: var(--md-elev-1);
    overflow: hidden;
}
.stat { padding: 16px 20px; border-left: 1px solid var(--md-divider); }
.stat:first-child { border-left: 0; }
/* Die Kacheln sind Links. Sie sollen aber nicht wie Links aussehen - blaue
   unterstrichene Zahlen waeren eine Zumutung -, sondern sich beim Ueberfahren
   melden wie eine Flaeche, auf die man druecken kann. */
a.stat { display: block; color: inherit; text-decoration: none; }
a.stat:hover { background: var(--md-overlay-hover); }
a.stat:focus-visible { outline: 2px solid var(--md-primary); outline-offset: -2px; }

/* Der Weg zurueck steht ueber der ersten Karte und traegt deshalb keinen
   eigenen Kasten - nur den Abstand, den er zum Inhalt braucht. */
.page-back { margin-bottom: 12px; }
.stat-label {
    font-size: .6875rem;
    color: var(--md-text-medium);
    text-transform: uppercase;
    letter-spacing: .12em;
    font-weight: 500;
}
/* Header 4 in Zahlen: groß genug, um von weitem gelesen zu werden. */
.stat-value {
    font-size: 2rem;
    font-weight: 400;
    line-height: 1.2;
    margin-top: 4px;
    letter-spacing: .0073em;
    font-variant-numeric: tabular-nums;
}
.stat-hint { font-size: .75rem; color: var(--md-text-medium); margin-top: 2px; letter-spacing: .0333em; }
.stat--alert .stat-value { color: var(--md-error); }
/* Ein Datum oder „vor 3 Tagen" braucht keine 2rem – es soll lesbar sein,
   nicht schreien. Die Zeile bleibt trotzdem so hoch wie bei einer Zahl,
   damit die Kacheln nebeneinander nicht wackeln. */
.stat-value--small { font-size: 1.125rem; line-height: 1.78; }
/* Der Nenner bei „2/6": gehört zur Zahl, ist aber nicht die Aussage. */
.stat-of { font-size: 1.25rem; color: var(--md-text-medium); }

/* ═══ Kopf einer Akte ════════════════════════════════════════════════════
   Wer ist das, und stimmt etwas nicht? Beides muss in der ersten Zeile
   stehen – ein gesperrter Kunde darf sich nicht in einem Auswahlfeld weiter
   unten verstecken. */
.record-head {
    display: flex;
    flex-wrap: wrap;
    /* Mittig statt oben: Der Knopf sitzt auf Höhe des Namens und nicht auf
       Höhe der ersten Textzeile – sonst steht er optisch zu hoch. */
    align-items: center;
    /* Die Zeilenlücke ist bewusst grösser als die Spaltenlücke. Bricht der
       Knopf auf eine eigene Zeile um, klebte er sonst unter der Meta-Zeile,
       als gehörte er dazu. */
    gap: 20px 24px;
    justify-content: space-between;
}
/* Der Textblock nimmt den Platz, der übrig ist, und gibt ihn wieder her,
   bevor er umbricht. Vorher war er starr breit – dann rutschte der Knopf
   schon bei mittleren Fensterbreiten grundlos nach unten. */
.record-head__main { flex: 1 1 22rem; min-width: 0; }
.record-head__actions { flex: 0 0 auto; }
.record-head__main h2 {
    margin: 0;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px 12px;
    font-size: 1.5rem;
    letter-spacing: 0;
}
.record-head__meta {
    margin: .25rem 0 0;
    font-size: .875rem;
    color: var(--md-text-medium);
    letter-spacing: .0179em;
}
.record-head__meta code { font-size: .8125rem; }

/* ═══ Register ═══════════════════════════════════════════════════════════
   Eine Leiste aus Schaltflächen, nicht aus Textlinks. Der erste Entwurf war
   Material-typisch zurückhaltend: Text mit Symbol und einer feinen Kante
   unter dem aktiven Reiter. Auf dem Bildschirm las sich das als Reihe blauer
   Links – man sah nicht, dass man darauf drücken kann und schon gar nicht,
   dass es eine Auswahl ist.

   Jetzt trägt die Leiste dieselbe Fläche wie die Kennzahlen darüber, und der
   aktive Reiter ist ausgefüllt. Damit ist auf einen Blick klar: Das sind
   Schalter, und dieser hier ist gedrückt.

   Bewusst Links und keine Umschalter im Browser – die Karte steht damit in
   der Adresse, lässt sich als Lesezeichen ablegen und überlebt den
   Zurück-Knopf. Auf schmalen Geräten scrollt die Leiste waagerecht, statt in
   vier Zeilen zu zerfallen. */
.tabs {
    display: flex;
    gap: 4px;
    overflow-x: auto;
    scrollbar-width: thin;
    background: var(--md-surface);
    border-radius: var(--md-shape);
    box-shadow: var(--md-elev-1);
    padding: 6px;
    margin-top: 16px;
}
.tab {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 10px 16px;
    color: var(--md-text-medium);
    text-decoration: none;
    white-space: nowrap;
    font-size: .875rem;
    font-weight: 500;
    letter-spacing: .0179em;
    border-radius: var(--md-shape-pill);
    transition: background-color .15s, color .15s;
}
.tab:hover { background: var(--md-overlay-hover); color: var(--md-text-high); text-decoration: none; }
.tab svg { width: 18px; height: 18px; flex: 0 0 18px; }
/* Auch beim Überfahren gefüllt bleiben: Sonst sähe der aktive Reiter unter
   dem Zeiger kurz aus wie ein beliebiger anderer. */
.tab--active,
.tab--active:hover {
    background: var(--md-tint-primary);
    color: var(--md-link);
}
.tab-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 20px;
    height: 20px;
    padding: 0 6px;
    border-radius: var(--md-shape-pill);
    background: var(--md-tint-primary);
    color: var(--md-link);
    font-size: .75rem;
    font-variant-numeric: tabular-nums;
}
.tab--active .tab-count { background: var(--md-link); color: var(--md-on-primary); }

/* ═══ Faktenliste ════════════════════════════════════════════════════════
   Stammdaten zum Lesen, nicht zum Ändern. Ein Formular mit zwölf Feldern
   beantwortet die Frage „wer ist das?" schlechter als zwölf Zeilen Text –
   und lädt zum versehentlichen Verstellen ein. Zwei Spalten, solange Platz
   ist; darunter untereinander. */
.facts {
    display: grid;
    grid-template-columns: minmax(120px, auto) 1fr;
    gap: 8px 20px;
    margin: 0;
    font-size: .875rem;
    letter-spacing: .0179em;
}
.facts dt { color: var(--md-text-medium); }
.facts dd { margin: 0; overflow-wrap: anywhere; }
@media (max-width: 480px) {
    .facts { grid-template-columns: 1fr; gap: 2px 0; }
    .facts dd { margin-bottom: 10px; }
}

/* ═══ Das steht an ═══════════════════════════════════════════════════════
   Die Kante links trägt die Dringlichkeit. Sie ersetzt ein Symbol: Bei fünf
   Einträgen untereinander liest man die Farbkante, nicht fünf Icons. */
.todo { list-style: none; margin: 0; padding: 0; }
.todo-item {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 8px 16px;
    padding: 12px 16px;
    border-left: 3px solid var(--md-divider);
    border-radius: 0 var(--md-shape-sm) var(--md-shape-sm) 0;
    background: var(--md-surface-2, transparent);
}
.todo-item + .todo-item { margin-top: 8px; }
.todo-item--error   { border-left-color: var(--md-error);   background: var(--md-tint-error); }
.todo-item--warning { border-left-color: var(--md-warning); background: var(--md-tint-warning); }
.todo-item--info    { border-left-color: var(--md-info);    background: var(--md-tint-info); }

/* ═══ Verlaufsliste ══════════════════════════════════════════════════════
   Datum links, Sache rechts – eine Zeile je Eintrag. Die ausführliche
   Darstellung mit Absätzen steht auf der Chronik-Karte; hier geht es nur
   darum, in drei Sekunden zu sehen, was zuletzt war. */
.feed { list-style: none; margin: 0; padding: 0; font-size: .875rem; }
.feed-item {
    display: flex;
    gap: 12px;
    padding: 8px 0;
    border-top: 1px solid var(--md-divider);
    letter-spacing: .0179em;
}
.feed-item:first-child { border-top: 0; padding-top: 0; }
.feed-time { color: var(--md-text-medium); font-variant-numeric: tabular-nums; flex: 0 0 auto; }
.feed-body { min-width: 0; overflow-wrap: anywhere; }

/* Eine Zahl, die an ihre Grenze stösst – belegte Geräteplätze etwa. Kein
   Chip: In einer Zahlenspalte soll die Zahl die Zahl bleiben. */
.text-warning { color: var(--md-warning); font-weight: 500; }

@media (max-width: 700px) {
    .stat { border-left: 0; border-top: 1px solid var(--md-divider); }
    .stat:first-child { border-top: 0; }
}

/* ═══ Schaltflächen ══════════════════════════════════════════════════════ */
/* Der Grundknopf ist „contained": Primärfarbe, 2 dp, beim Überfahren 4 dp,
   beim Drücken 8 dp. Diese Höhenänderung ist die eigentliche Rückmeldung –
   deshalb hier keine Farbwechsel beim Überfahren, sondern Schatten.
   Versalien mit 0,0892 em Sperrung sind die Schriftvorgabe für Knöpfe. */
.btn {
    position: relative;
    overflow: hidden;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    height: 36px;
    min-width: 64px;
    padding: 0 16px;
    border: 1px solid transparent;
    border-radius: var(--md-shape);
    font: inherit;
    font-size: .875rem;
    font-weight: 500;
    line-height: 1;
    letter-spacing: .0892em;
    text-transform: uppercase;
    cursor: pointer;
    background: var(--md-primary);
    color: var(--md-on-primary);
    box-shadow: var(--md-elev-2);
    text-decoration: none;
    white-space: nowrap;
    -webkit-tap-highlight-color: transparent;
    transition: box-shadow var(--md-dur-medium) var(--md-ease-standard),
                background-color var(--md-dur-medium) var(--md-ease-standard),
                border-color var(--md-dur-medium) var(--md-ease-standard);
}
.btn:hover  { box-shadow: var(--md-elev-4); text-decoration: none; color: var(--md-on-primary); }
.btn:active { box-shadow: var(--md-elev-8); }
.btn[disabled], .btn.is-disabled {
    background: var(--md-overlay-focus);
    color: var(--md-text-disabled);
    box-shadow: none;
    pointer-events: none;
}
.btn .hub-icon { width: 18px; height: 18px; flex: 0 0 18px; }

/* Die Deckschicht für Überfahren/Fokus liegt als eigene Ebene über der
   Fläche. Nur so bleibt die Rechnung „4 % der Vordergrundfarbe" unabhängig
   davon, welche Farbe der Knopf gerade hat. */
.btn::before {
    content: "";
    position: absolute; inset: 0;
    background: currentColor;
    opacity: 0;
    transition: opacity var(--md-dur-medium) var(--md-ease-standard);
    pointer-events: none;
}
.btn:hover::before { opacity: .08; }
.btn:focus-visible::before { opacity: .12; }

/* Umrandet: für Aktionen zweiten Ranges. Keine Höhe, nur eine Linie. */
.btn--outlined {
    background: transparent;
    border-color: var(--md-divider-strong);
    color: var(--md-link);
    box-shadow: none;
}
.btn--outlined:hover  { box-shadow: none; color: var(--md-link); border-color: var(--md-primary); }
.btn--outlined:active { box-shadow: none; }

/* „tonal" gab es in Material 2 nicht – hier bleibt der Name, weil ihn 25
   Stellen benutzen. Umgesetzt als umrandeter Knopf in Textfarbe: die
   neutrale Nebenaktion, die nicht nach Hauptaktion aussehen darf. */
.btn--tonal {
    background: var(--md-surface);
    border-color: var(--md-divider-strong);
    color: var(--md-text-high);
    box-shadow: none;
}
.btn--tonal:hover  { box-shadow: none; color: var(--md-text-high); border-color: var(--md-text-medium); }
.btn--tonal:active { box-shadow: none; }

/* Text: die leiseste Stufe. Ohne Fläche, ohne Rand, ohne Höhe. */
.btn--text {
    background: transparent;
    color: var(--md-link);
    box-shadow: none;
    padding: 0 8px;
    min-width: 0;
    border-color: transparent;
}
.btn--text:hover  { box-shadow: none; color: var(--md-link); }
.btn--text:active { box-shadow: none; }

.btn--danger { background: var(--md-error); color: var(--md-on-error); }
.btn--danger:hover { color: var(--md-on-error); }

.btn--small { height: 30px; padding: 0 12px; font-size: .8125rem; min-width: 0; }
.btn--small .hub-icon { width: 16px; height: 16px; flex: 0 0 16px; }
/* Ein Knopf ueber die ganze Breite darf umbrechen. Ohne das bestimmt die
   Beschriftung die kleinste moegliche Breite der Karte - „Anmelden und
   Schluessel anfordern" in Versalien sind 344 Pixel, und damit stand die
   Karte auf einem Telefon breiter da als der Bildschirm. Feste Hoehe geht
   dann nicht mehr; min-height haelt einzeilige Knoepfe unveraendert. */
.btn--block {
    width: 100%;
    height: auto;
    min-height: 40px;
    padding: 10px 16px;
    white-space: normal;
    line-height: 1.25;
    text-align: center;
}

.btn-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.btn-row--end { justify-content: flex-end; }
/* Abbrechendes links, Bestätigendes rechts – dazwischen Luft, damit man das
   Löschen nicht im Vorbeigehen trifft. */
.btn-row--between { justify-content: space-between; }

/* Die Welle beim Klicken (ripple). Sie wird von hub.js eingesetzt und räumt
   sich selbst wieder ab. Ohne JavaScript fehlt nur die Welle – der Knopf
   funktioniert unverändert. */
.md-ripple {
    position: absolute;
    border-radius: 50%;
    transform: scale(0);
    background: currentColor;
    opacity: .24;
    pointer-events: none;
    animation: md-ripple-out 500ms var(--md-ease-decelerate) forwards;
}
@keyframes md-ripple-out {
    to { transform: scale(2.4); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
    .md-ripple { display: none; }
    * { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* ═══ Formulare ══════════════════════════════════════════════════════════ */
.field { margin-bottom: 16px; }
.field:last-child { margin-bottom: 0; }

/* Beschriftung über dem Feld statt im Feld schwebend.
   Begründung: Material 2 setzt die Beschriftung in das Feld hinein und lässt
   sie beim Tippen nach oben wandern. Das verlangt eine andere Struktur im
   HTML (Beschriftung NACH dem Eingabefeld, plus Auskerbung der Umrandung).
   Diese Anwendung hat 100 Felder in 35 Vorlagen – ein Umbau aller Felder für
   eine Bewegung wäre viel Risiko für wenig Gewinn, und dichte Formulare
   lesen sich mit fester Beschriftung ohnehin schneller. Übernommen ist der
   Stil der schwebenden Beschriftung: klein, mittlere Schärfe, gesperrt. */
.field > label,
.field-label {
    display: block;
    font-size: .75rem;
    font-weight: 500;
    color: var(--md-text-medium);
    margin-bottom: 4px;
    letter-spacing: .0333em;
}
.field > label.check { font-size: .875rem; letter-spacing: .0179em; margin-bottom: 0; }

/* Das gefüllte Textfeld: getönte Fläche, oben gerundet, unten eine Linie.
   Bei Fokus wird die Linie 2 px stark und nimmt die Primärfarbe an – das ist
   die Bewegung, die Material hier vorsieht. */
.input, .select, .textarea {
    width: 100%;
    padding: 0 12px;
    font: inherit;
    font-size: .875rem;
    letter-spacing: .0179em;
    color: var(--md-text-high);
    background: var(--md-surface-2);
    border: 0;
    border-bottom: 1px solid var(--md-divider-strong);
    border-radius: var(--md-shape) var(--md-shape) 0 0;
    transition: background-color var(--md-dur-medium) var(--md-ease-standard),
                border-color var(--md-dur-medium) var(--md-ease-standard),
                box-shadow var(--md-dur-medium) var(--md-ease-standard);
}
.input { height: 40px; }
.input:hover, .select:hover, .textarea:hover { background: var(--md-surface-3); }
.input:focus, .select:focus, .textarea:focus {
    outline: none;
    background: var(--md-surface-3);
    border-bottom-color: var(--md-primary);
    /* Der zweite Pixel der Fokuslinie – als Schatten, damit die Zeile beim
       Fokussieren nicht um 1 px springt. */
    box-shadow: inset 0 -1px 0 0 var(--md-primary);
}
.input[readonly] { color: var(--md-text-medium); }
.input:disabled, .select:disabled, .textarea:disabled {
    color: var(--md-text-disabled);
    border-bottom-style: dotted;
}
.input::placeholder, .textarea::placeholder { color: var(--md-text-disabled); }
.textarea {
    min-height: 96px;
    resize: vertical;
    line-height: 1.5;
    padding: 10px 12px;
}

.select {
    height: 40px;
    appearance: none;
    padding-right: 32px;
    cursor: pointer;
    /* Der Pfeil ist eingebettet, kein zusätzlicher Abruf. Farbe muss je
       Schema getauscht werden – SVG in einer URL erbt kein currentColor. */
    background-image: var(--md-select-arrow);
    background-repeat: no-repeat;
    background-position: right 8px center;
}

.field-hint {
    font-size: .75rem;
    color: var(--md-text-medium);
    margin-top: 4px;
    line-height: 1.45;
    letter-spacing: .0333em;
}
.field-error {
    font-size: .75rem;
    color: var(--md-error);
    margin-top: 4px;
    font-weight: 500;
    letter-spacing: .0333em;
}
.field.has-error .input,
.field.has-error .select,
.field.has-error .textarea {
    border-bottom-color: var(--md-error);
    box-shadow: inset 0 -1px 0 0 var(--md-error);
}
.field.has-error > label { color: var(--md-error); }

/* ── Formularraster ──────────────────────────────────────────────────────
   Spalten von höchstens 460px: Ein Eingabefeld, das über einen halben
   Bildschirm läuft, ist nicht besser zu lesen, nur länger. Auf einem breiten
   Monitor entstehen deshalb mehr Spalten statt breiterer Felder.

   Die Untergrenze von 320px ist der Platz, den Beschriftung und Hilfetext
   brauchen, ohne zur Säule aus Einzelwörtern zu werden. */
.form-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 460px));
    gap: 4px 24px;
    /* Angefangene letzte Zeile links ausrichten statt auseinanderziehen. */
    justify-content: start;
}
/* Ein Feld über die volle Zeile – aber nicht über den ganzen Monitor.
   Zwei Spaltenbreiten sind das Maximum: Ein Eingabefeld für eine
   E-Mail-Adresse, das 2000px breit ist, sieht aus wie ein Fehler. */
.form-grid .field--wide { grid-column: 1 / -1; max-width: 944px; }

.check {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 6px 0;
    cursor: pointer;
}
.check input[type="checkbox"], .check input[type="radio"] {
    width: 18px; height: 18px; margin: 1px 0 0;
    accent-color: var(--md-primary);
    flex: 0 0 18px;
    cursor: pointer;
}
.check-text { font-size: .875rem; letter-spacing: .0179em; }
.check-text small,
.check-text > span {
    display: block;
    color: var(--md-text-medium);
    font-size: .75rem;
    line-height: 1.45;
    letter-spacing: .0333em;
    margin-top: 2px;
}

fieldset {
    border: 0;
    border-top: 1px solid var(--md-divider);
    padding: 16px 0 0;
    margin: 0 0 24px;
}
/* display:block statt float:left.
   Ein <legend> wird sonst in die Lücke der Umrandung gesetzt; hier ist die
   Umrandung nur eine Linie oben. Der frühere Ausweg war float:left – der hat
   alles zerlegt, was danach ein eigener Formatierungskontext ist: Ein
   Grid-Container darf einen Float nicht überlappen, wurde daneben gequetscht
   und klebte am rechten Rand. */
legend {
    padding: 0;
    font-weight: 500;
    font-size: .6875rem;
    text-transform: uppercase;
    letter-spacing: .12em;
    color: var(--md-text-medium);
    display: block;
    float: none;
    width: 100%;
    margin-bottom: 12px;
}

/* ═══ Datentabellen ══════════════════════════════════════════════════════ */
.table-wrap {
    overflow-x: auto;
    border-radius: var(--md-shape);
    background: var(--md-surface);
    box-shadow: var(--md-elev-1);
}
/* In einer Karte liegt die Tabelle schon auf deren Höhe – ein zweiter
   Schatten darüber wäre eine Höhe, die es nicht gibt. */
.card .table-wrap { box-shadow: none; border: 1px solid var(--md-divider); }

table.data {
    width: 100%;
    border-collapse: collapse;
    font-size: .875rem;
    letter-spacing: .0179em;
    font-variant-numeric: tabular-nums;
}
table.data th, table.data td {
    padding: 10px 16px;
    text-align: left;
    border-bottom: 1px solid var(--md-divider);
    vertical-align: middle;
}
/* Kopfzeile: 12 px, mittlere Schärfe, nicht versal. Material setzt in
   Datentabellen bewusst keine Versalien – bei vielen Spalten wird das
   unruhig und schlechter lesbar. */
table.data th {
    font-size: .75rem;
    font-weight: 500;
    color: var(--md-text-medium);
    letter-spacing: .0333em;
    white-space: nowrap;
    height: 48px;
    background: var(--md-surface);
}
table.data tbody tr { transition: background-color var(--md-dur-short) var(--md-ease-standard); }
table.data tbody tr:last-child td { border-bottom: 0; }
table.data tbody tr:hover { background: var(--md-overlay-hover); }
table.data td.num { text-align: right; }
table.data td.actions { text-align: right; white-space: nowrap; }
table.data .muted { color: var(--md-text-medium); }
table.data a { font-weight: 500; }

/* Aktionen IN einer Tabellenzeile bekommen keine Versalien.
   Begründung: Material schreibt Knöpfe versal – aber die Datentabelle ist der
   eine Ort, wo die Spezifikation das ausdrücklich nicht tut. Der Grund ist
   messbar: „BEARBEITEN AUSLIEFERUNG FREIGABE" mit 0,0892 em Sperrung ist rund
   40 % breiter als dieselbe Zeile gemischt geschrieben. Auf der Softwareliste
   schob das die letzte Spalte aus dem sichtbaren Bereich – man musste
   waagerecht scrollen, um an „Freigabe" zu kommen. */
table.data .btn {
    text-transform: none;
    letter-spacing: .0179em;
    padding: 0 8px;
}
table.data td.actions { padding-right: 8px; }
table.data td.actions .btn + .btn { margin-left: 2px; }

.empty {
    padding: 48px 24px;
    text-align: center;
    color: var(--md-text-medium);
    font-size: .875rem;
}
.empty strong {
    display: block;
    color: var(--md-text-high);
    margin-bottom: 4px;
    font-weight: 500;
    font-size: 1rem;
    letter-spacing: .0094em;
}

/* ═══ Chips und Marken ═══════════════════════════════════════════════════ */
/* Material-Chips sind 32 px hoch. Diese hier stehen zu Dutzenden in
   Tabellenzellen und wären damit höher als die Zeile – deshalb die dichte
   Fassung: 22 px, voll gerundet, sonst unverändert. */
.chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    height: 22px;
    padding: 0 10px;
    border-radius: var(--md-shape-pill);
    border: 0;
    font-size: .75rem;
    font-weight: 500;
    line-height: 1;
    letter-spacing: .0333em;
    background: var(--md-surface-3);
    color: var(--md-text-high);
    white-space: nowrap;
}
.chip--success { background: var(--md-tint-success); color: var(--md-success); }
.chip--warning { background: var(--md-tint-warning); color: var(--md-warning); }
.chip--error   { background: var(--md-tint-error);   color: var(--md-error); }
.chip--info    { background: var(--md-tint-info);    color: var(--md-info); }
.chip--primary { background: var(--md-tint-primary); color: var(--md-link); }

.dot { width: .5em; height: .5em; border-radius: 50%; background: currentColor; display: inline-block; }

.hub-badge {
    min-width: 20px;
    height: 20px;
    padding: 0 6px;
    border-radius: var(--md-shape-pill);
    background: var(--md-error);
    color: var(--md-on-error);
    font-size: .6875rem;
    font-weight: 500;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0;
}

/* ═══ Hinweise ═══════════════════════════════════════════════════════════ */
/* Material nennt das ein Banner: getönte Fläche, Sinnbild links, kein
   Rahmen. Die Tönung ist sehr blass – die Aussage trägt der Text. */
.notice {
    display: flex;
    gap: 12px;
    padding: 12px 16px;
    border: 0;
    border-radius: var(--md-shape);
    background: var(--md-tint-info);
    color: var(--md-text-high);
    font-size: .875rem;
    line-height: 1.5;
    letter-spacing: .0179em;
}
.notice--success { background: var(--md-tint-success); }
.notice--warning { background: var(--md-tint-warning); }
.notice--error   { background: var(--md-tint-error); }
/* Nur die ERSTE Hervorhebung ist die Überschrift des Hinweises. Ohne das
   :first-child riss jedes <strong> mitten im Satz eine eigene Zeile auf. */
.notice strong { font-weight: 500; }
.notice > div > strong:first-child { display: block; margin-bottom: 2px; }
.notice .hub-icon {
    flex: 0 0 20px; width: 20px; height: 20px; margin-top: 1px;
    color: var(--md-info);
}
.notice--success .hub-icon { color: var(--md-success); }
.notice--warning .hub-icon { color: var(--md-warning); }
.notice--error   .hub-icon { color: var(--md-error); }

/* ── Kurzmeldung (snackbar) ──────────────────────────────────────────────────
   In Material ist das eine dunkle Fläche unten links auf 6 dp – bewusst NICHT
   in der Farbe der Meldung. Erfolg und Fehler unterscheidet der Text und ein
   farbiger Streifen, nicht die ganze Fläche. So sieht eine Rückmeldung nie
   aus wie ein Alarm. */
.flash-stack {
    position: fixed;
    left: 24px;
    bottom: 24px;
    z-index: 100;
    display: flex;
    flex-direction: column-reverse;
    gap: 8px;
    width: min(480px, calc(100vw - 48px));
}
.flash {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 14px 16px;
    border-radius: var(--md-shape);
    background: #323232;
    color: rgba(255, 255, 255, .92);
    box-shadow: var(--md-elev-6);
    font-size: .875rem;
    line-height: 1.45;
    letter-spacing: .0179em;
    animation: md-snack-in var(--md-dur-long) var(--md-ease-decelerate);
}
@keyframes md-snack-in {
    from { transform: translateY(16px); opacity: 0; }
}
.flash::before {
    content: "";
    flex: 0 0 3px;
    align-self: stretch;
    border-radius: var(--md-shape-pill);
    background: transparent;
}
.flash--success::before { background: #81c995; }
.flash--error::before   { background: #f28b82; }
/* Weder gut noch schlecht, sondern „später noch einmal" – etwa wenn eine
   andere Stelle gerade die Datenbank nachführt. */
.flash--info::before    { background: #8ab4f8; }
/* Der Schließknopf ist die Aktion der Kurzmeldung – in Material trägt sie
   die Sekundärfarbe und steht rechts. */
.flash button {
    margin-left: auto;
    background: none;
    border: 0;
    color: #80cbc4;
    cursor: pointer;
    font-size: 1.25rem;
    line-height: 1;
    padding: 0 4px;
    border-radius: var(--md-shape-sm);
}
.flash button:hover { color: #fff; }

/* ═══ Schlüssel und Kennungen ════════════════════════════════════════════ */
.keybox {
    display: flex;
    align-items: center;
    gap: 12px;
    background: var(--md-surface-2);
    border-radius: var(--md-shape);
    padding: 8px 12px;
    font-family: var(--md-mono);
    font-size: .8125rem;
    letter-spacing: 0;
    overflow-wrap: anywhere;
    min-height: 40px;
}
.keybox .btn { margin-left: auto; flex: 0 0 auto; }

.sha { font-family: var(--md-mono); font-size: .8125rem; color: var(--md-text-medium); letter-spacing: 0; }

/* ═══ Anmeldeseiten ══════════════════════════════════════════════════════ */
.auth-page {
    min-height: 100vh;
    display: grid;
    place-items: center;
    padding: 32px 16px;
    background: var(--md-background);
}

/* ── Hintergrundbild ─────────────────────────────────────────────────────
   Das Bild fuellt den Schirm, darueber liegt ein Schleier in der
   Hintergrundfarbe des gewaehlten Erscheinungsbilds. Deshalb muss niemand
   zwei Bilder pflegen: Derselbe Schleier beruhigt ein helles Foto im hellen
   Modus und dunkelt es im dunklen ab, ohne dass die Farben kippen.

   Die drei Werte kommen als Variablen aus dem Layout - sie stehen in der
   Datenbank und haben im Stylesheet nichts verloren. */
/* Diese drei setzt das Layout im style-Attribut, weil sie aus der Datenbank
   kommen. Hier stehen ihre Vorgaben - damit an einer Stelle nachlesbar ist,
   welche Werte die Regeln unten erwarten. */
:root {
    --auth-image: none;
    --auth-dim: .45;
    --auth-focus: center;
}
.auth-page--image {
    position: relative;
    background-image: var(--auth-image);
    background-size: cover;
    /* Kein `fixed`: Auf iOS ruckelt es und beim Aufklappen der Tastatur
       springt der Ausschnitt. */
    background-position: center var(--auth-focus, center);
    background-repeat: no-repeat;
}
.auth-page--image::before {
    content: '';
    position: absolute;
    inset: 0;
    background: var(--md-background);
    opacity: var(--auth-dim, .45);
}
/* Ueber dem Schleier, sonst verschwindet die Karte darunter. */
.auth-page--image > * { position: relative; z-index: 1; }
/* Auf einem Foto braucht die Karte mehr Kante als auf einer Flaeche - sonst
   schwimmt sie. */
.auth-page--image .auth-card { box-shadow: var(--md-elev-24); }

/* ── Das Suchfeld in der Kopfleiste ──────────────────────────────────────
   Es steht zwischen Titel und Knoepfen und nimmt sich, was uebrig ist. Auf
   schmalen Schirmen verschwindet es: Dort ist die Kopfleiste fuer Titel und
   Menue da, und die Suche erreicht man ueber die Seite selbst. */
.hub-suche { flex: 1 1 200px; max-width: 340px; margin-left: 24px; }
.hub-suche .input {
    height: 36px;
    background: var(--md-topbar-btn-hover-bg);
    border: 1px solid var(--md-topbar-btn-border);
    color: var(--md-topbar-fg);
}
.hub-suche .input::placeholder { color: var(--md-topbar-btn-fg); opacity: .75; }
.hub-suche .input:focus {
    background: var(--md-surface);
    color: var(--md-text-high);
    border-color: var(--md-surface);
}
.hub-suche .input:focus::placeholder { color: var(--md-text-medium); }
@media (max-width: 800px) { .hub-suche { display: none; } }

/* Fuer Bildschirmleser da, fuer das Auge nicht. */
.sr-only {
    position: absolute; width: 1px; height: 1px;
    padding: 0; margin: -1px; overflow: hidden;
    clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* ── Tabellen auf schmalen Schirmen ──────────────────────────────────────
   Acht Spalten auf einem Telefon heissen: viel seitwaerts ziehen. Unter 720
   Pixeln wird jede Zeile zu einer Karte – links die Spaltenueberschrift,
   rechts der Wert.

   Die Beschriftung kommt als data-spalte aus dem Skript. Fehlt sie (kein
   JavaScript), bleibt es bei der scrollenden Tabelle: unschoen, aber
   vollstaendig. Deshalb haengt die ganze Regel an [data-spalte]. */
@media (max-width: 720px) {
    .data:has([data-spalte]) thead { display: none; }
    .data:has([data-spalte]),
    .data:has([data-spalte]) tbody,
    .data:has([data-spalte]) tr { display: block; width: 100%; }

    .data:has([data-spalte]) tbody tr {
        padding: 12px 16px;
        border-bottom: 1px solid var(--md-divider);
    }
    .data:has([data-spalte]) tbody tr:last-child { border-bottom: 0; }

    .data:has([data-spalte]) td {
        display: flex;
        align-items: baseline;
        gap: 16px;
        border: 0;
        padding: 3px 0;
        white-space: normal;
    }
    /* Leere Zellen kosten auf dem Telefon eine Zeile fuer nichts. */
    .data:has([data-spalte]) td:empty { display: none; }

    .data:has([data-spalte]) td[data-spalte]::before {
        content: attr(data-spalte);
        flex: 0 0 38%;
        color: var(--md-text-medium);
        font-size: .8125rem;
    }
    /* Die erste Zelle ist die Ueberschrift der Karte, nicht ein Feld darin. */
    .data:has([data-spalte]) td:first-child {
        font-weight: 500;
        padding-bottom: 6px;
    }
    .data:has([data-spalte]) td:first-child::before { display: none; }

    /* Der Rahmen darf nicht mehr scrollen - es gibt nichts mehr zu scrollen. */
    .table-wrap:has([data-spalte]) { overflow-x: visible; }
}

/* ── Anklickbare Zeilen ──────────────────────────────────────────────────
   Die Klasse setzt das Skript, nicht die Vorlage: Ohne JavaScript ist die
   Zeile nicht anklickbar, und dann darf sie auch nicht so aussehen. Ein
   Zeiger, der nichts tut, ist schlimmer als keiner. */
.data tr.row--klickbar { cursor: pointer; }

/* ── „Hier sind Sie am Zug" ──────────────────────────────────────────────
   Eine Kante am linken Rand statt einer eingefaerbten Zeile: Die Liste soll
   nicht bunt werden, sondern eine Kante haben, an der das Auge haengenbleibt.
   Der Chip in der Zeile sagt es aus; die Kante macht es ueber die ganze Liste
   hinweg zaehlbar. */
.data tr.row--wartet > td:first-child { box-shadow: inset 3px 0 0 var(--warn); }

/* ── Vorschau in den Einstellungen ───────────────────────────────────────
   Dasselbe Bild mit demselben Schleier, nur klein. Wer die Abdunklung
   einstellt, will genau eine Frage beantwortet haben: Ist die Karte darauf
   noch lesbar? Ein Bild ohne Schleier beantwortet sie nicht. */
.login-preview {
    position: relative;
    height: 200px;
    margin-bottom: 20px;
    border-radius: var(--md-shape);
    overflow: hidden;
    background-image: var(--auth-image);
    background-size: cover;
    background-position: center var(--auth-focus, center);
    display: grid;
    place-items: center;
}
.login-preview::before {
    content: '';
    position: absolute;
    inset: 0;
    background: var(--md-background);
    opacity: var(--auth-dim, .45);
}
/* ── Die Probe aufs Logo ─────────────────────────────────────────────────
   Zwei Felder nebeneinander: eines hell, eines dunkel. Ihre Farben sind
   absichtlich FEST und folgen nicht dem Erscheinungsbild des Betrachters –
   die Frage lautet ja gerade, ob das Logo auf der jeweils anderen Fläche
   noch etwas taugt. Wer im dunklen Modus arbeitet, sähe sonst nie, was ein
   Kunde am hellen Fenster sieht. */
.logo-proben { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 20px; }
.logo-probe {
    flex: 1 1 220px;
    margin: 0;
    padding: 16px;
    min-height: 136px;
    border: 1px solid var(--md-divider-strong);
    border-radius: var(--md-shape);
    display: grid;
    gap: 10px;
    align-content: center;
    justify-items: center;
    text-align: center;
}
/* So hoch wie auf der Anmeldekarte - eine Probe, die kleiner zeigt als die
   Wirklichkeit, beantwortet die Frage nach der Lesbarkeit nicht. */
.logo-probe img { max-height: 60px; max-width: 100%; width: auto; }
.logo-probe figcaption { font-size: .75rem; letter-spacing: .0179em; }
.logo-probe--hell   { background: #ffffff; }
.logo-probe--hell   figcaption { color: #5f6368; }
.logo-probe--hell   .logo-probe-leer { color: #9aa0a6; }
.logo-probe--dunkel { background: #1f1f1f; }
.logo-probe--dunkel figcaption { color: #bdc1c6; }
.logo-probe--dunkel .logo-probe-leer { color: #80868b; }
.logo-probe-leer { font-size: .8125rem; }

/* Die Datei-Auswahl bringt der Browser mit, samt englischer Aufschrift und
   eigener Vorstellung von Knoepfen. Das laesst sich nicht uebersetzen, aber
   der Knopf darf wenigstens wie die anderen aussehen. */
.input[type="file"] { padding: 8px 12px; line-height: 1.6; }
.input[type="file"]::file-selector-button {
    margin-right: 12px;
    padding: 6px 16px;
    border: 1px solid var(--md-divider-strong);
    border-radius: var(--md-shape);
    background: var(--md-surface);
    color: var(--md-text-high);
    font: inherit;
    font-weight: 500;
    cursor: pointer;
}
.input[type="file"]::file-selector-button:hover { border-color: var(--md-text-medium); }

.login-preview-card {
    position: relative;
    z-index: 1;
    background: var(--md-surface);
    color: var(--md-text-high);
    border-radius: var(--md-shape);
    box-shadow: var(--md-elev-24);
    padding: 14px 32px;
    font-size: .9375rem;
}
/* Eine schwebende Karte auf 8 dp – die einzige Fläche auf dem Schirm, also
   darf sie deutlich abheben. */
.auth-card {
    width: min(400px, 100%);
    background: var(--md-surface);
    border-radius: var(--md-shape);
    box-shadow: var(--md-elev-8);
    padding: 32px;
}
.auth-card h1 { font-size: 1.5rem; font-weight: 400; margin-bottom: 4px; letter-spacing: 0; }
.auth-card .auth-sub {
    color: var(--md-text-medium);
    font-size: .875rem;
    margin-bottom: 28px;
    letter-spacing: .0179em;
}
/* Drei Kartenmasse, nicht eines. Die schmale Karte ist fuer zwei Felder und
   einen Knopf gebaut - fuer eine Anmeldung. Ein Formular mit Anschrift und
   eine Datenschutzerklaerung sind etwas anderes, und beide wurden darin zu
   einer Kolonne, durch die man scrollt.

   `--breit` ist fuer Formulare: zwei Felder passen nebeneinander, ohne dass
   eines davon zur Briefmarke wird. `--text` ist fuer Fliesstext und darf
   weiter gehen, weil dort nichts nebeneinander steht, sondern nur die Zeile
   laenger wird. Mehr als das nicht: Wer eine Zeile bis zum Bildschirmrand
   zieht, findet den naechsten Zeilenanfang nicht mehr wieder. */
.auth-card--breit { width: min(760px, 100%); padding: 40px; }
.auth-card--text  { width: min(880px, 100%); padding: 40px 48px; }

/* Auf dem Telefon zaehlt jeder Punkt Rand: 48 Pixel links und rechts sind
   dort ein Viertel der Breite. */
@media (max-width: 560px) {
    .auth-card--breit,
    .auth-card--text { padding: 24px; }
}

/* Zwei bis drei Felder nebeneinander, auf dem Telefon untereinander. Ohne
   den Umbruch stuenden PLZ und Ort auf 360 Pixel Breite als zwei
   Briefmarken nebeneinander. */
.field-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0 16px;
}
.field-row--plz { grid-template-columns: 1fr 2fr 0.8fr; }
@media (max-width: 560px) {
    .field-row, .field-row--plz { grid-template-columns: 1fr; }
}

/* Der Fliesstext der Datenschutzseite. Anders als sonst auf einer Karte ist
   hier Lesbarkeit ueber laengere Strecken gefragt. */
.datenschutz-h {
    font-size: 1rem; font-weight: 500; margin: 28px 0 8px;
    color: var(--md-text-high);
}
.datenschutz-h:first-of-type { margin-top: 16px; }
.datenschutz-p {
    font-size: .875rem; line-height: 1.7; margin-bottom: 12px;
    color: var(--md-text-medium);
}

/* Der Koeder auf dem Anmeldeformular: ein Feld, das kein Mensch sieht und
   ein einfacher Automat trotzdem ausfuellt. Nicht display:none - manche
   Automaten pruefen darauf; stattdessen aus dem Bild geschoben. Vom
   Vorleseprogramm haelt es aria-hidden fern, von der Tabulatorreihenfolge
   tabindex="-1". */
.feld-webseite {
    position: absolute;
    left: -9999px;
    width: 1px; height: 1px;
    overflow: hidden;
}

.auth-foot {
    margin-top: 24px; padding-top: 16px;
    border-top: 1px solid var(--md-divider);
    text-align: center;
    font-size: .8125rem;
    color: var(--md-text-medium);
}

/* ═══ Dateibrowser ═══════════════════════════════════════════════════════ */
.breadcrumb {
    display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
    font-size: .875rem; margin-bottom: 16px;
}
.breadcrumb a { color: var(--md-text-medium); }
.breadcrumb a:hover { color: var(--md-link); }
.breadcrumb .sep { color: var(--md-text-disabled); }
.breadcrumb .current { font-weight: 500; color: var(--md-text-high); }

.file-name { display: flex; align-items: center; gap: 12px; }
.file-name .hub-icon { flex: 0 0 20px; width: 20px; height: 20px; color: var(--md-text-medium); }
.file-name--folder .hub-icon { color: var(--md-primary); }

.dropzone {
    border: 2px dashed var(--md-divider-strong);
    border-radius: var(--md-shape);
    padding: 24px;
    text-align: center;
    color: var(--md-text-medium);
    font-size: .875rem;
    background: var(--md-surface-2);
    cursor: pointer;
    transition: border-color var(--md-dur-medium) var(--md-ease-standard),
                background-color var(--md-dur-medium) var(--md-ease-standard),
                color var(--md-dur-medium) var(--md-ease-standard);
}
.dropzone:hover { border-color: var(--md-text-medium); color: var(--md-text-high); }
.dropzone.is-over {
    border-color: var(--md-primary);
    background: var(--md-overlay-primary);
    color: var(--md-primary);
}
.dropzone .hub-icon { display: block; margin: 0 auto 8px; width: 28px; height: 28px; }

/* ═══ Blättern ═══════════════════════════════════════════════════════════ */
.pagination { display: flex; gap: 4px; align-items: center; justify-content: center; margin-top: 16px; }
.pagination a, .pagination span {
    min-width: 36px; height: 36px;
    display: inline-flex; align-items: center; justify-content: center;
    padding: 0 10px;
    border-radius: var(--md-shape);
    font-size: .875rem;
    color: var(--md-text-medium);
    font-variant-numeric: tabular-nums;
    transition: background-color var(--md-dur-short) var(--md-ease-standard);
}
.pagination a:hover { background: var(--md-overlay-hover); color: var(--md-text-high); text-decoration: none; }
.pagination .is-current {
    background: var(--md-primary);
    color: var(--md-on-primary);
    font-weight: 500;
}

/* ═══ Fortschritt ════════════════════════════════════════════════════════ */
/* Die Spur ist die Farbe des Balkens mit 24 % Deckkraft – so wie es die
   Spezifikation vorgibt, nicht ein neutrales Grau. */
.progress {
    height: 4px;
    border-radius: var(--md-shape-pill);
    background: var(--md-overlay-primary-2);
    overflow: hidden;
}
.progress > i {
    display: block; height: 100%;
    background: var(--md-primary);
    border-radius: inherit;
    transition: width var(--md-dur-long) var(--md-ease-standard);
}
.progress--warning > i { background: var(--md-warning); }
.progress--error > i   { background: var(--md-error); }

/* ═══ Ausklapper ═════════════════════════════════════════════════════════ */
details.disclosure {
    border-radius: var(--md-shape);
    padding: 12px 16px;
    background: var(--md-surface);
    box-shadow: var(--md-elev-1);
}
.card details.disclosure { box-shadow: none; border: 1px solid var(--md-divider); }
details.disclosure > summary {
    cursor: pointer;
    font-weight: 500;
    font-size: .875rem;
    list-style: none;
    display: flex; align-items: center; gap: 12px;
    color: var(--md-text-high);
    /* Eine Zusammenfassung trägt Name, Adresse und zwei bis drei Merkmale.
       Nebeneinander passt das auf einem Bildschirm und auf einem Telefon
       nicht: Ein Merkmal lässt sich nicht schmaler machen, es steht dann
       einfach rechts aus dem Bild heraus, und die ganze Seite wird breiter
       als das Gerät. Deshalb umbrechen statt überlaufen. */
    flex-wrap: wrap;
    row-gap: 6px;
    /* Eine lange Adresse ohne Leerzeichen bricht sonst nirgends um. */
    overflow-wrap: anywhere;
    min-width: 0;
}
details.disclosure > summary::-webkit-details-marker { display: none; }
/* Das Dreieck dreht sich beim Öffnen – die Bewegung sagt, was passiert ist. */
details.disclosure > summary::before {
    content: "";
    width: 0; height: 0;
    border-left: 5px solid var(--md-text-medium);
    border-top: 4px solid transparent;
    border-bottom: 4px solid transparent;
    transition: transform var(--md-dur-medium) var(--md-ease-standard);
    flex: 0 0 5px;
}
details.disclosure[open] > summary::before { transform: rotate(90deg); }
details.disclosure > summary .spacer { margin-left: auto; }
details.disclosure > *:not(summary) { margin-top: 12px; }
details.disclosure > summary:hover { color: var(--md-link); }

/* ═══ Lizenz als aufklappbare Karte ══════════════════════════════════════ */
/* Im Ruhezustand eine Zeile, aufgeklappt die volle Auskunft. Die Karte trägt
   ihre Höhe wie jede andere; nur die Innenabstände weichen, damit die
   zugeklappte Fassung wirklich flach bleibt. */
.lic-card { padding: 0; }
.lic-card > *:not(summary) { margin-left: 20px; margin-right: 20px; }
.lic-card > *:not(summary):last-child { margin-bottom: 20px; }
.lic-card[open] > summary { border-bottom: 1px solid var(--md-divider); margin-bottom: 16px; }

.lic-head {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    padding: 14px 20px;
    cursor: pointer;
    list-style: none;
    border-radius: var(--md-shape);
    transition: background-color var(--md-dur-short) var(--md-ease-standard);
}
.lic-head::-webkit-details-marker { display: none; }
.lic-head:hover { background: var(--md-overlay-hover); }
.lic-head > .hub-icon { color: var(--md-text-medium); flex: 0 0 20px; }

.lic-name { font-size: 1rem; font-weight: 500; letter-spacing: .0094em; }
.lic-meta { font-size: .8125rem; color: var(--md-text-medium); letter-spacing: .0179em; }

/* Der Kopierknopf steht rechts und klappt nichts auf. */
.lic-copy { margin-left: auto; }

/* Das Dreieck sitzt ganz rechts – es ist der leiseste Teil der Zeile. */
.lic-head::after {
    content: "";
    order: 99;
    width: 0; height: 0;
    border-left: 5px solid var(--md-text-medium);
    border-top: 4px solid transparent;
    border-bottom: 4px solid transparent;
    transition: transform var(--md-dur-medium) var(--md-ease-standard);
}
.lic-card:not([open]) .lic-copy + .lic-head::after,
.lic-head::after { margin-left: 4px; }
.lic-card:not(:has(.lic-copy)) .lic-head::after { margin-left: auto; }
.lic-card[open] > .lic-head::after { transform: rotate(90deg); }

@media (max-width: 620px) {
    .lic-copy { margin-left: 0; }
    .lic-head::after { margin-left: auto; }
}

/* ═══ Filterleiste ═══════════════════════════════════════════════════════ */
.filter-bar {
    display: flex; flex-wrap: wrap; gap: 12px; align-items: flex-end;
    background: var(--md-surface);
    border-radius: var(--md-shape);
    box-shadow: var(--md-elev-1);
    padding: 12px 16px;
}
.filter-bar .field { margin-bottom: 0; }
.filter-bar .input, .filter-bar .select { min-width: 180px; }

/* ═══ Meldungsverlauf ════════════════════════════════════════════════════ */
/* Zwei Sprechrichtungen, unterschieden durch Kante und Fläche – bewusst
   keine Sprechblasen. Das hier ist ein Vorgang, kein Chat. */
.thread { display: flex; flex-direction: column; gap: 12px; }

.thread-item {
    border-left: 3px solid var(--md-divider-strong);
    border-radius: 0 var(--md-shape) var(--md-shape) 0;
    padding: 12px 16px;
    background: var(--md-surface);
    box-shadow: var(--md-elev-1);
}
.thread-item--customer { border-left-color: var(--md-primary); }
.thread-item--internal {
    border-left-color: var(--md-warning);
    background: var(--md-tint-warning);
    box-shadow: none;
}

.thread-head {
    display: flex; align-items: center; gap: 8px;
    font-size: .875rem;
    font-weight: 500;
    margin-bottom: 6px;
}
.thread-head .spacer { margin-left: auto; }

.thread-body {
    font-size: .875rem;
    line-height: 1.6;
    color: var(--md-text-high);
    overflow-wrap: anywhere;
}

/* Kanalzeilen in der Softwareliste: als Teil des Programms lesbar, nicht als
   eigenes Programm – eingesenkte Fläche statt eigener Rahmen. */
.data tr.row-sub > td {
    background: var(--md-surface-sunk);
    border-top: 0;
}
.data tr.row-sub > td:first-child { padding-left: 32px; }
.row-sub-mark { color: var(--md-text-medium); margin-right: 6px; }

/* ═══ Kleinteile ═════════════════════════════════════════════════════════ */
.hub-icon { width: 20px; height: 20px; display: inline-block; vertical-align: -.22em; }

.muted { color: var(--md-text-medium); }
.small { font-size: .75rem; letter-spacing: .0333em; }
.tabular { font-variant-numeric: tabular-nums; }
.nowrap { white-space: nowrap; }
/* Unter 360 Pixel ist "nicht umbrechen" kein Versprechen mehr, das sich
   halten lässt: Der Text ist dann breiter als das Gerät. Lieber ein Umbruch
   an ungünstiger Stelle als eine Seite, die sich seitlich schieben lässt. */
@media (max-width: 360px) {
    .nowrap { white-space: normal; }
}
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.stack > * + * { margin-top: 12px; }
.stack-sm > * + * { margin-top: 6px; }
.row { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
.row--between { justify-content: space-between; }
.spacer { margin-left: auto; }
.hidden { display: none !important; }

dl { font-size: .875rem; }
dt { color: var(--md-text-medium); font-size: .75rem; letter-spacing: .0333em; }

/* ── Sortierbare Spaltenkoepfe ───────────────────────────────────────────
   Der ganze Kopf ist die Trefferflaeche, nicht nur das Wort: Auf ein
   17-Pixel-Wort zu zielen ist mit der Maus laestig und auf dem Telefon
   Gluecksache. */
th.sortable { padding: 0; }
th.sortable > a {
    display: flex;
    align-items: center;
    gap: 6px;
    /* Dieselben Abstaende wie in einem gewoehnlichen Kopf - die Zeile darf
       durch das Sortieren nicht die Hoehe wechseln. */
    padding: 10px 16px;
    color: inherit;
    text-decoration: none;
    white-space: nowrap;
}
th.sortable > a:hover {
    background: var(--md-overlay-hover);
    color: var(--md-link);
}
th.sortable[aria-sort] > a { color: var(--md-link); }
.sort-arrow { font-size: .625rem; line-height: 1; }

/* Der Umschalter fuer hell und dunkel. Auf schmalen Fenstern zaehlt jeder
   Pixel in der Kopfleiste - dann bleibt nur das Zeichen stehen. */
.hub-theme-toggle { gap: 8px; }
@media (max-width: 700px) {
    .hub-theme-toggle span { display: none; }
}

/* ── Einstellungsseite: Rubriken, Sprungmarken, Speicherleiste ───────────
   Ueber hundert Felder in gleich aussehenden Kaesten sind keine Seite,
   sondern eine Liste. Die Rubrikueberschriften geben ihr eine Gliederung,
   die Sprungmarken einen Einstieg und die Leiste unten einen Knopf, den man
   nicht erst suchen muss. */
.jump-nav {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin: 16px 0 4px;
}
.jump-nav a {
    display: inline-flex;
    align-items: center;
    height: 30px;
    padding: 0 14px;
    border-radius: var(--md-shape-pill);
    border: 1px solid var(--md-divider-strong);
    background: var(--md-surface);
    color: var(--md-text-high);
    font-size: .8125rem;
    text-decoration: none;
    /* Lange Rubriknamen sollen umbrechen statt die Zeile aufzuschieben. */
    min-width: 0;
    overflow-wrap: anywhere;
}
.jump-nav a:hover,
.jump-nav a:focus-visible {
    background: var(--md-surface-2);
    border-color: var(--md-primary);
    color: var(--md-link);
}

.section-title {
    margin: 32px 0 0;
    font-size: 1.0625rem;
    letter-spacing: .0125em;
    /* Damit die Sprungmarke nicht unter der klebenden Kopfzeile landet. */
    scroll-margin-top: 76px;
}
.section-title::before {
    content: "";
    display: block;
    width: 40px;
    height: 3px;
    margin-bottom: 10px;
    border-radius: 2px;
    background: var(--md-primary);
}
.section-help {
    margin: 4px 0 12px;
    color: var(--md-text-medium);
    font-size: .875rem;
}
.section-title + .section-help + .card,
.section-help + .card { margin-top: 0; }
form .card + .card { margin-top: 16px; }

/* Breitere Spalten als sonst: Auf dieser Seite hat fast jedes Feld einen
   Hilfetext darunter, und in einer 240px-Spalte wird daraus eine Saeule aus
   Einzelwoertern. */
.settings-grid .field-hint { margin-bottom: 10px; }

/* Klebt unten am Fenster, solange das Formular laeuft. Was NACH dem Formular
   kommt, wuerde sonst dauerhaft darunter verschwinden - deshalb schiebt die
   Leiste den Inhalt hinter sich weg, statt sich darueberzulegen. */
.save-bar + *, form:has(.save-bar) + * { margin-top: 24px; }
.save-bar {
    position: sticky;
    bottom: 0;
    z-index: 15;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: 12px;
    margin-top: 24px;
    padding: 12px 16px;
    border-radius: var(--md-shape);
    background: var(--md-surface);
    box-shadow: var(--md-elev-4);
}

/* ── Fliesstext bleibt lesbar, auch auf einem breiten Monitor ────────────
   Eine Zeile über 2500px liest niemand: Das Auge findet den Anfang der
   nächsten Zeile nicht mehr. Tabellen und Kachelraster sind davon bewusst
   ausgenommen – die füllen weiter alles. */
.card > p,
.section-help,
.notice > div,
.empty {
    max-width: 92ch;
}

@media print {
    .hub-nav, .hub-topbar, .btn, .flash-stack { display: none !important; }
    .hub-content { padding: 0; }
    .card, .table-wrap, .stat-row { box-shadow: none; border: 1px solid var(--md-divider); }
}

/* ═══ Auswahlliste ═══════════════════════════════════════════════════════
   Eine Reihe gleichrangiger Angebote, aus denen genau eines gewählt wird –
   Vertragsvorlagen, Laufzeiten. Bewusst keine Tabelle: Es geht nicht um
   Vergleich Spalte für Spalte, sondern um „welches nehme ich?". */
.picklist { list-style: none; margin: 0; padding: 0; }
.picklist-item {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 12px 20px;
    padding: 14px 16px;
    border: 1px solid var(--md-divider);
    border-radius: var(--md-shape-sm);
}
.picklist-item + .picklist-item { margin-top: 8px; }
.picklist-item:hover { border-color: var(--md-link); }
.picklist-main { min-width: min(280px, 100%); flex: 1 1 auto; }
.picklist-main .chip { margin-left: 8px; }

.text-success { color: var(--md-success); }

/* ── Anteilsbalken ───────────────────────────────────────────────────────
   Ein Balken sagt in einem Blick, was eine Prozentzahl erst nach dem Lesen
   sagt: ob ein Stand bei fast allen liegt oder bei zweien. Bewusst schmal -
   er steht in einer Tabellenzelle und soll die Zeile nicht sprengen. */
.anteil {
    display: block;
    width: 6rem;
    height: .4rem;
    border-radius: 999px;
    background: var(--md-overlay-hover);
    overflow: hidden;
}
.anteil > span {
    display: block;
    height: 100%;
    background: var(--md-primary);
    border-radius: 999px;
}

/* ── Wiederherstellungscodes ─────────────────────────────────────────────
   Sie werden genau einmal angezeigt, direkt nach dem Erzeugen - danach steht
   nur noch ihr Hash in der Datenbank. Deshalb gross genug zum Abschreiben
   und mit Zeichenabstand: Ein O und eine 0 duerfen hier nicht gleich
   aussehen. */
.code-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    gap: .5rem;
    font-family: var(--mono);
    font-size: .95rem;
    margin: 1rem 0;
}
.code-grid > code {
    background: var(--md-surface);
    border: 1px solid var(--ok-line);
    border-radius: var(--r-sm);
    padding: .5rem .7rem;
    letter-spacing: .04em;
}
