/* ============================================================
   GutachterBook — zentrales Theme
   Alle Brand-/Status-/Severity-Farben gibt es NUR hier.
   Pages: Variablen nutzen, keine Hex-Werte hartkodieren.
   ============================================================ */

/* hidden-Attribut hat im Browser default display:none, wird aber von
   explizitem display: inline-flex/flex auf Buttons/Links überschrieben.
   !important stellt den Browser-Default global wieder her. */
[hidden] { display: none !important; }

:root {
    /* ============================================
       Neue Grünpalette (Claude-inspiriert) — 10 Stufen.
       Bestehende --brand-green-* Aliase darauf gemappt.
       ============================================ */
    --g-50:  #f2f6f3;
    --g-100: #e6efe8;
    --g-200: #cfdfd4;
    --g-300: #a8c6b3;
    --g-400: #79a589;
    --g-500: #4d8560;
    --g-600: #3a6b4a;
    --g-700: #2a5239;
    --g-800: #1c3a2a;
    --g-900: #11241a;

    /* Warme Neutrale — Cream-Hintergrund + Ink-Skala */
    --cream:    #faf9f5;
    --paper:    #f4f2ea;
    --sand:     #e8e4d5;
    --ink:      #1a1a17;
    --ink-soft: #4a4a44;
    --ink-mute: #7a7a72;

    /* Feedback (Warn/Danger/Success) — mockup-konform */
    --warn:      #b07a2b;
    --warn-bg:   #fbf3e4;
    --danger:    #a13c2e;
    --danger-bg: #fbe6e1;
    --success:   #3a6b4a;
    --success-bg:#e6efe8;

    /* Brand-Grün — Aliase auf neue Skala (Namen bleiben, Werte neu) */
    --brand-green-900: var(--g-900);
    --brand-green-800: var(--g-800);
    --brand-green-700: var(--g-700);
    --brand-green-600: var(--g-600);
    --brand-green-500: var(--g-500);
    --brand-green-300: var(--g-300);
    --brand-green-100: var(--g-100);
    --brand-green-tint:     rgba(42,82,57,.08);   /* dezenter Grün-Wash für Hover-States */

    /* Dreistufiger Brand-Gradient — kippt bei 45 % spürbar heller, wirkt lebendig. */
    --brand-gradient:        linear-gradient(135deg, var(--g-800) 0%, var(--g-700) 45%, var(--g-500) 100%);
    --brand-gradient-hover:  linear-gradient(135deg, var(--g-700) 0%, var(--g-500) 45%, var(--g-400) 100%);
    --brand-gradient-soft:   linear-gradient(135deg, var(--g-700) 0%, var(--g-500) 45%, var(--g-300) 100%);
    --brand-gradient-bar:    linear-gradient(180deg, #1f2a24 0%, #15201a 100%);

    --brand-ink:        var(--g-900);   /* Headlines */
    --brand-ink-soft:   var(--ink-soft);/* Body */
    --brand-ink-mute:   var(--ink-mute);/* Sub / Meta */
    --brand-ink-faint:  #8a9b94;        /* Labels */

    --brand-surface:        #ffffff;
    --brand-surface-alt:    #f7f6f0;
    --brand-surface-tint:   var(--g-50);
    --brand-line:           #efece2;
    --brand-line-strong:    var(--sand);

    /* Status-Farben — Termine, Buchungen, Gutachten-Status */
    --status-confirmed:     var(--g-700);
    --status-confirmed-bg:  rgba(42,82,57,.12);
    --status-confirmed-ink: var(--g-800);

    --status-completed:     #3a6b8f;
    --status-completed-bg:  rgba(58,107,143,.12);
    --status-completed-ink: #26506b;

    --status-canceled:      #c25450;
    --status-canceled-bg:   rgba(194,84,80,.12);
    --status-canceled-ink:  #a8403c;

    --status-blocked:       #6b4f3a;
    --status-blocked-bg:    rgba(107,79,58,.12);
    --status-blocked-ink:   #5a4230;

    --status-draft:         #8a9b94;
    --status-draft-bg:      rgba(138,155,148,.14);
    --status-draft-ink:     #4d5b56;

    /* Schaden-Schwere — bewusst eigene Skala (Ampel) */
    --severity-low:     var(--g-700);
    --severity-low-bg:  rgba(42,82,57,.12);
    --severity-medium:    #c79a2a;
    --severity-medium-bg: rgba(199,154,42,.14);
    --severity-high:    #c25450;
    --severity-high-bg: rgba(194,84,80,.12);

    /* Geometrie */
    --radius-card:  18px;
    --radius-pill:  999px;
    --radius-btn:   12px;
    --radius-chip:  10px;
    --radius-input: 8px;

    --shadow-card:        0 4px 18px rgba(17,36,26,.05);
    --shadow-card-hover:  0 12px 32px rgba(17,36,26,.10);
    --shadow-btn:         0 6px 16px rgba(26,58,42,.25);
    --shadow-btn-hover:   0 10px 24px rgba(26,58,42,.30);

    /* Aliase für das Style-Guide-Vokabular — gleiche Werte, klarere Namen */
    --shadow-brand:       var(--shadow-btn);
    --shadow-brand-hover: var(--shadow-btn-hover);
}

/* ============================================================
   Wiederverwendbare Bausteine — bewusst klein gehalten.
   Pages dürfen das via class="th-btn-primary" o.Ä. nutzen.
   ============================================================ */

.th-btn-primary {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 11px 18px;
    border-radius: var(--radius-btn);
    background: var(--brand-gradient);
    color: #ffffff;
    font-size: 14px;
    font-weight: 700;
    text-shadow: 0 1px 2px rgba(15,31,24,.35);
    text-decoration: none;
    border: none;
    box-shadow:
        inset 0 0 0 1px rgba(15,31,24,.18),
        var(--shadow-btn);
    cursor: pointer;
    transition: transform .15s, box-shadow .25s, background .3s;
    font-family: inherit;
}
.th-btn-primary:hover {
    transform: translateY(-1px);
    box-shadow:
        inset 0 0 0 1px rgba(15,31,24,.22),
        var(--shadow-btn-hover);
    background: var(--brand-gradient-hover);
    color: #ffffff;
}
.th-btn-primary:active { transform: translateY(0); }
/* Kleine Primary-Buttons (z. B. in der Topbar) — Lesbarkeit kommt aus
   Kontrast, kein text-shadow. */
.th-btn-primary.th-btn-sm,
.th-btn-sm.th-btn-primary {
    padding: 7px 13px;
    font-size: 13px;
    text-shadow: none;
}

.th-btn-ghost {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 10px 16px;
    border-radius: var(--radius-btn);
    background: var(--brand-surface);
    color: var(--brand-ink);
    font-size: 14px;
    font-weight: 600;
    text-decoration: none;
    border: 1.5px solid var(--brand-line-strong);
    cursor: pointer;
    transition: background .15s, border-color .15s, transform .15s;
    font-family: inherit;
}
.th-btn-ghost:hover {
    background: var(--brand-surface-tint);
    border-color: var(--brand-green-700);
}

.th-btn-danger {
    background: #fff;
    border: 1px solid #f0c8c8;
    color: #a8242a;
    padding: 10px 18px;
    border-radius: var(--radius-btn);
    font-weight: 600;
    font-size: 14px;
    cursor: pointer;
    font-family: inherit;
}
.th-btn-danger:hover {
    background: #fde0e0;
    border-color: #dca0a0;
}

.th-status {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 5px 11px;
    border-radius: var(--radius-pill);
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: .2px;
    line-height: 1;
}
.th-status-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
}
.th-status--confirmed { background: var(--status-confirmed-bg); color: var(--status-confirmed-ink); }
.th-status--confirmed .th-status-dot { background: var(--status-confirmed); }
.th-status--completed { background: var(--status-completed-bg); color: var(--status-completed-ink); }
.th-status--completed .th-status-dot { background: var(--status-completed); }
.th-status--canceled  { background: var(--status-canceled-bg);  color: var(--status-canceled-ink); }
.th-status--canceled  .th-status-dot { background: var(--status-canceled); }
.th-status--draft     { background: var(--status-draft-bg);     color: var(--status-draft-ink); }
.th-status--draft     .th-status-dot { background: var(--status-draft); }

.th-sev {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 9px;
    border-radius: var(--radius-pill);
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .4px;
    line-height: 1;
}
.th-sev--low    { background: var(--severity-low-bg);    color: var(--severity-low); }
.th-sev--medium { background: var(--severity-medium-bg); color: var(--severity-medium); }
.th-sev--high   { background: var(--severity-high-bg);   color: var(--severity-high); }

/* Mobile: Schwere-Badges in Schadens-Listen ausblenden — auf engem
   Bildschirm ist die Info nicht entscheidend genug, um Platz zu rechtfertigen.
   Die Schwere bleibt im Detail-View des einzelnen Schadens sichtbar. */
@media (max-width: 600px) {
    .th-sev { display: none; }
}

.th-card {
    background: var(--brand-surface);
    border-radius: var(--radius-card);
    border: 1px solid rgba(0,0,0,.05);
    box-shadow: var(--shadow-card);
    transition: transform .15s ease, box-shadow .25s ease;
}
.th-card:hover {
    box-shadow: var(--shadow-card-hover);
}

.th-input,
.th-select,
.th-textarea {
    padding: 10px 12px;
    border-radius: var(--radius-input);
    border: 1.5px solid var(--brand-line-strong);
    font-size: 14px;
    color: var(--brand-ink);
    font-family: inherit;
    background: #fff;
    width: 100%;
    box-sizing: border-box;
}

/* type=date / time / datetime auf iOS/Safari haben Webkit-eigene
   Innenraender + min-height, die nicht zu den anderen .th-input
   passen — Layout sieht sonst auf Mobile schief aus. Vereinheitlichen. */
.th-input[type="date"],
.th-input[type="time"],
.th-input[type="datetime-local"],
.th-input[type="month"],
.th-input[type="week"] {
    appearance: none;
    -webkit-appearance: none;
    min-height: 42px;
    line-height: 1.2;
}
.th-input:focus,
.th-select:focus,
.th-textarea:focus {
    outline: none;
    border-color: var(--brand-green-700);
    box-shadow: 0 0 0 4px rgba(45,106,79,.12);
}

/* Native <select> mit Brand-Look: eigener Caret statt Browser-Default,
   etwas mehr rechter Innenabstand für den Caret-Platz, Hover-Highlight. */
.th-select {
    appearance: none;
    -webkit-appearance: none;
    -moz-appearance: none;
    padding-right: 36px;
    cursor: pointer;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%235c7c6f' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
    background-repeat: no-repeat;
    background-position: right 12px center;
    background-size: 14px 14px;
    transition: border-color .15s, box-shadow .15s;
}
.th-select:hover { border-color: var(--brand-green-500); }
/* IE/Edge-Legacy: nativen Pfeil ausblenden. */
.th-select::-ms-expand { display: none; }

/* ============================================================
   th-cs: vollständig gestyltes Custom-Select (Trigger + Dropdown).
   Pattern wie cal-cs/av-cs, aber als globaler App-Standard.
   ============================================================ */
.th-cs { position: relative; width: 100%; }
.th-cs-trigger {
    width: 100%;
    /* Symmetrisches Padding: der Caret ist ein echtes Flex-Kind, kein
       Background-SVG mehr — sonst sitzt der Pfeil 36px vom rechten Rand
       weg statt am Rand. */
    padding: 10px 12px;
    border-radius: var(--radius-input);
    border: 1.5px solid var(--brand-line-strong);
    background: #fff;
    color: var(--brand-ink);
    font-size: 14px;
    font-family: inherit;
    text-align: left;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    transition: border-color .15s, box-shadow .15s;
    line-height: 1.3;
}
.th-cs-trigger:hover { border-color: var(--brand-green-500); }
.th-cs-trigger:focus {
    outline: none;
    border-color: var(--brand-green-700);
    box-shadow: 0 0 0 4px rgba(45,106,79,.12);
}
.th-cs.is-open .th-cs-trigger {
    border-color: var(--brand-green-700);
    box-shadow: 0 0 0 4px rgba(45,106,79,.12);
}
.th-cs-label {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.th-cs-label.is-placeholder { color: var(--brand-ink-faint); }
.th-cs-caret { color: var(--brand-ink-mute); flex-shrink: 0; transition: transform .15s; }
.th-cs.is-open .th-cs-caret { transform: rotate(180deg); }
.th-cs-dropdown {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    right: 0;
    background: #fff;
    border: 1.5px solid var(--brand-line-strong);
    border-radius: var(--radius-input);
    box-shadow: 0 14px 40px rgba(15,31,24,.14);
    padding: 6px;
    max-height: 280px;
    overflow: auto;
    z-index: 30;
    display: none;
}
.th-cs.is-open .th-cs-dropdown { display: block; }
.th-cs-option {
    padding: 10px 12px;
    border-radius: 10px;
    font-size: 14px;
    color: var(--brand-ink);
    cursor: pointer;
    line-height: 1.35;
    transition: background .12s;
}
.th-cs-option:hover { background: var(--brand-surface-tint); }
.th-cs-option.is-selected {
    background: rgba(45,106,79,.1);
    color: var(--brand-green-700);
    font-weight: 600;
}
.th-cs-option-meta {
    display: block;
    font-size: 12px;
    color: var(--brand-ink-mute);
    font-weight: 400;
    margin-top: 2px;
}

/* Generische Badge — neutral grün gefüllt, runde Pille.
   Für funktionale Status-Farben bleiben die .th-status*-Klassen. */
.th-badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: var(--g-100);
    color: var(--g-800);
    border-radius: var(--radius-pill);
    padding: 3px 10px;
    font-size: 11.5px;
    font-weight: 600;
    letter-spacing: .15px;
    line-height: 1;
}
.th-badge--accent {
    background: var(--brand-gradient);
    color: #ffffff;
    box-shadow: var(--shadow-brand);
}

.th-label {
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .5px;
    color: var(--brand-ink-faint);
    margin-bottom: 6px;
    display: block;
}

.th-empty {
    text-align: center;
    padding: 60px 28px;
    background: var(--brand-surface);
    border-radius: var(--radius-card);
    border: 1px dashed var(--brand-line-strong);
}
.th-empty-zero {
    margin: 0 auto 14px;
    width: 72px;
    height: 72px;
    border-radius: 50%;
    background: rgba(92,124,111,.08);
    color: var(--brand-ink-mute);
    font-size: 34px;
    font-weight: 800;
    display: flex;
    align-items: center;
    justify-content: center;
    letter-spacing: -1px;
}
.th-empty-text {
    font-size: 16px;
    color: var(--brand-ink-mute);
    margin: 0 0 20px;
}

/* ============================================================
   Ab hier: Bausteine aus dem Styleguide (KAN-36).
   Alles additiv — bestehende Klassen bleiben unverändert.
   Sichtbar unter /styleguide (nur Superadmin).
   ============================================================ */

/* --- Breakpoints -------------------------------------------------------
   Zielset. CSS erlaubt keine Variablen in Media-Queries, die Werte müssen
   also literal stehen — die Variablen sind Dokumentation und für
   Container-Berechnungen nutzbar.

   Im Bestand stehen 39 verschiedene max-width-Werte. Die Migration läuft
   als Folge-Ticket; NEUER Code nutzt ausschließlich diese vier. */
:root {
    --bp-sm:  480px;   /* großes Handy quer / kleines Tablet hoch */
    --bp-md:  768px;   /* Tablet */
    --bp-lg: 1024px;   /* kleiner Laptop */
    --bp-xl: 1280px;   /* Desktop */

    /* Touch-Ziel nach WCAG 2.5.8 / Apple HIG. Jede tippbare Fläche
       mindestens so groß, mit mindestens 8px Abstand zur nächsten. */
    --touch-min: 44px;
}

/* --- Buttons: Zustände und Icon-Variante ------------------------------ */
/* Alles, was an BESTEHENDEN Klassen hängt, ist an `.th-page` gebunden.
   Sonst verändert es sofort den Bestand: `beta/_layout.html` setzt bei jedem
   Submit `btn.disabled = true`, alle Beta-Formulare hätten ihren
   Speichern-Knopf plötzlich ausgeblasst. */
.th-page .th-btn-primary[disabled],
.th-page .th-btn-ghost[disabled],
.th-page .th-btn-danger[disabled],
.th-page .th-btn-primary.is-disabled {
    opacity: .55;
    cursor: not-allowed;
    transform: none;
    box-shadow: none;
    pointer-events: none;
}
/* Ladezustand: Beschriftung bleibt stehen, davor ein Spinner. Text
   auszutauschen lässt den Button springen. */
.th-page .th-btn-primary.is-loading,
.th-page .th-btn-ghost.is-loading { cursor: progress; pointer-events: none; }
.th-page .th-btn-primary.is-loading::before,
.th-page .th-btn-ghost.is-loading::before {
    content: "";
    width: 14px; height: 14px;
    border-radius: 50%;
    border: 2px solid currentColor;
    border-right-color: transparent;
    animation: th-spin .7s linear infinite;
}
@keyframes th-spin { to { transform: rotate(360deg); } }

/* Nur-Icon-Button. Immer mit aria-label — sonst ist er für Screenreader stumm. */
.th-btn-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--touch-min);
    height: var(--touch-min);
    border-radius: var(--radius-btn);
    border: 1.5px solid var(--brand-line-strong);
    background: var(--brand-surface);
    color: var(--brand-ink-soft);
    cursor: pointer;
    transition: background .15s, border-color .15s, color .15s;
}
.th-btn-icon:hover { background: var(--brand-surface-tint); border-color: var(--brand-green-700); color: var(--brand-ink); }

/* --- Formular-Zeile, Hilfetext, Fehlerzustand ------------------------- */
.th-field { margin-bottom: 16px; }
.th-required { color: var(--danger); margin-left: 2px; }
.th-help { font-size: 12.5px; color: var(--brand-ink-mute); margin: 6px 0 0; line-height: 1.45; }
.th-error {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    font-size: 12.5px;
    color: var(--danger);
    margin: 6px 0 0;
    line-height: 1.45;
}
.th-field.has-error .th-input,
.th-field.has-error .th-select,
.th-field.has-error .th-textarea,
.th-field.has-error .th-cs-trigger { border-color: var(--danger); }
.th-field.has-error .th-input:focus,
.th-field.has-error .th-select:focus,
.th-field.has-error .th-textarea:focus { box-shadow: 0 0 0 4px rgba(161,60,46,.12); }

/* --- Checkbox, Radio, Toggle ------------------------------------------ */
/* Klickfläche ist das ganze Label, nicht nur das 18px-Kästchen. */
.th-check {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    min-height: var(--touch-min);
    padding: 6px 0;
    font-size: 14px;
    color: var(--brand-ink);
    cursor: pointer;
    line-height: 1.45;
}
.th-check input[type="checkbox"],
.th-check input[type="radio"] {
    width: 18px; height: 18px;
    margin: 2px 0 0;
    accent-color: var(--g-700);
    flex-shrink: 0;
    cursor: pointer;
}
.th-toggle { display: inline-flex; align-items: center; gap: 10px; cursor: pointer; min-height: var(--touch-min); }
.th-toggle input { position: absolute; opacity: 0; width: 0; height: 0; }
.th-toggle-track {
    width: 44px; height: 26px;
    border-radius: var(--radius-pill);
    background: var(--sand);
    position: relative;
    transition: background .18s;
    flex-shrink: 0;
}
.th-toggle-track::after {
    content: "";
    position: absolute;
    top: 3px; left: 3px;
    width: 20px; height: 20px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 3px rgba(0,0,0,.2);
    transition: transform .18s;
}
.th-toggle input:checked + .th-toggle-track { background: var(--g-600); }
.th-toggle input:checked + .th-toggle-track::after { transform: translateX(18px); }
.th-toggle input:focus-visible + .th-toggle-track { box-shadow: 0 0 0 4px rgba(45,106,79,.18); }

/* --- Tabellen: auf Mobile zu Karten stapeln --------------------------- */
/* Horizontales Scrollen auf Seitenebene ist verboten; eine Tabelle mit
   fünf Spalten passt auf 390px nicht. Deshalb Card-Stacking: jede Zeile
   wird zur Karte, die Spaltenüberschrift kommt aus data-label. */
.th-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.th-table thead th {
    text-align: left;
    padding: 10px 12px;
    font-size: 11.5px;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: .4px;
    color: var(--brand-ink-mute);
    border-bottom: 1.5px solid var(--brand-line-strong);
    white-space: nowrap;
}
.th-table tbody td { padding: 12px; color: var(--brand-ink-soft); border-bottom: 1px solid var(--brand-line); }
.th-table tbody tr:last-child td { border-bottom: none; }

@media (max-width: 768px) {
    .th-table--stack thead { display: none; }
    .th-table--stack tbody tr {
        display: block;
        background: var(--brand-surface);
        border: 1px solid var(--brand-line);
        border-radius: var(--radius-chip);
        padding: 4px 12px;
        margin-bottom: 10px;
    }
    .th-table--stack tbody td {
        display: flex;
        justify-content: space-between;
        gap: 16px;
        padding: 8px 0;
        border-bottom: 1px solid var(--brand-line);
    }
    .th-table--stack tbody td:last-child { border-bottom: none; }
    .th-table--stack tbody td::before {
        content: attr(data-label);
        font-size: 11.5px;
        font-weight: 800;
        text-transform: uppercase;
        letter-spacing: .4px;
        color: var(--brand-ink-mute);
        flex-shrink: 0;
    }
}

/* --- Tabs -------------------------------------------------------------- */
.th-tabs { display: flex; gap: 6px; border-bottom: 1.5px solid var(--brand-line); overflow-x: auto; }
.th-tab {
    padding: 10px 16px;
    min-height: var(--touch-min);
    border: none;
    background: none;
    font: inherit;
    font-weight: 600;
    font-size: 14px;
    color: var(--brand-ink-mute);
    cursor: pointer;
    border-radius: var(--radius-btn) var(--radius-btn) 0 0;
    border-bottom: 2px solid transparent;
    margin-bottom: -1.5px;
    white-space: nowrap;
}
.th-tab:hover { color: var(--brand-ink); background: var(--brand-surface-tint); }
.th-tab.is-active { color: var(--brand-green-700); border-bottom-color: var(--brand-green-700); }

/* --- Modal / auf Mobile Bottom-Sheet ---------------------------------- */
.th-modal-backdrop {
    position: fixed; inset: 0;
    background: rgba(17,36,26,.45);
    display: flex; align-items: center; justify-content: center;
    padding: 20px;
    z-index: 100;
}
.th-modal {
    background: var(--brand-surface);
    border-radius: var(--radius-card);
    box-shadow: 0 24px 60px rgba(15,31,24,.28);
    padding: 24px;
    width: 100%;
    max-width: 480px;
    max-height: 85vh;
    overflow: auto;
}
.th-modal-title { font-size: 18px; font-weight: 800; color: var(--brand-ink); margin: 0 0 8px; }
.th-modal-actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 20px; }

/* Daumenzone: auf dem Handy fährt der Dialog von unten ein und sitzt am
   unteren Rand — oben in der Mitte ist er einhändig kaum erreichbar. */
@media (max-width: 480px) {
    .th-modal-backdrop { align-items: flex-end; padding: 0; }
    .th-modal {
        max-width: none;
        border-radius: var(--radius-card) var(--radius-card) 0 0;
        padding-bottom: calc(24px + env(safe-area-inset-bottom, 0px));
    }
    .th-modal-actions { flex-direction: column-reverse; }
    .th-modal-actions > * { width: 100%; }
}

/* --- Toast ------------------------------------------------------------- */
.th-toast {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 12px 16px;
    border-radius: var(--radius-chip);
    font-size: 14px;
    line-height: 1.45;
    background: var(--brand-surface);
    border: 1px solid var(--brand-line-strong);
    box-shadow: 0 10px 30px rgba(15,31,24,.14);
}
.th-toast--success { background: var(--success-bg); color: var(--success); border-color: transparent; }
.th-toast--error   { background: var(--danger-bg);  color: var(--danger);  border-color: transparent; }
.th-toast--warn    { background: var(--warn-bg);    color: var(--warn);    border-color: transparent; }

/* --- Accordion ---------------------------------------------------------- */
.th-acc { border: 1px solid var(--brand-line); border-radius: var(--radius-chip); overflow: hidden; }
.th-acc + .th-acc { margin-top: 8px; }
.th-acc-head {
    width: 100%;
    min-height: var(--touch-min);
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    padding: 12px 16px;
    background: var(--brand-surface);
    border: none;
    font: inherit;
    font-weight: 600;
    font-size: 14px;
    color: var(--brand-ink);
    text-align: left;
    cursor: pointer;
}
.th-acc-head:hover { background: var(--brand-surface-tint); }
.th-acc-caret { color: var(--brand-ink-mute); transition: transform .15s; flex-shrink: 0; }
.th-acc.is-open .th-acc-caret { transform: rotate(180deg); }
.th-acc-body { padding: 0 16px 14px; font-size: 14px; color: var(--brand-ink-soft); line-height: 1.6; display: none; }
.th-acc.is-open .th-acc-body { display: block; }

/* --- Sticky-CTA in der Daumenzone -------------------------------------- */
/* Für lange Formulare: die Hauptaktion bleibt erreichbar, ohne ans Ende
   scrollen zu müssen. Safe-Area, sonst liegt der Knopf unter dem
   Home-Indicator. */
.th-sticky-cta {
    position: sticky;
    bottom: 0;
    background: linear-gradient(180deg, rgba(250,249,245,0) 0%, var(--cream) 40%);
    padding: 16px 0 calc(16px + env(safe-area-inset-bottom, 0px));
    margin-top: 8px;
}
.th-sticky-cta > .th-btn-primary { width: 100%; }

/* --- Lade- und Wartezustände -------------------------------------------- */
/* Erst ab ~400ms zeigen. Ein Skeleton, der sofort aufblitzt, wirkt
   unruhiger als eine kurze Pause. */
.th-skeleton {
    background: linear-gradient(90deg, var(--brand-surface-alt) 25%, var(--paper) 37%, var(--brand-surface-alt) 63%);
    background-size: 400% 100%;
    animation: th-shimmer 1.4s ease infinite;
    border-radius: var(--radius-input);
    height: 14px;
}
.th-skeleton--title { height: 22px; width: 45%; }
.th-skeleton--line  { margin-top: 10px; }
.th-skeleton--short { width: 60%; }
@keyframes th-shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }

.th-spinner {
    width: 20px; height: 20px;
    border-radius: 50%;
    border: 2.5px solid var(--brand-line-strong);
    border-right-color: var(--brand-green-700);
    animation: th-spin .7s linear infinite;
    display: inline-block;
}

/* --- Icons -------------------------------------------------------------- */
/* Immer stroke-basiert (currentColor), damit sie die Textfarbe erben. */
.th-icon { width: 18px; height: 18px; stroke-width: 2; flex-shrink: 0; }
.th-icon--sm { width: 14px; height: 14px; stroke-width: 2.2; }
.th-icon--lg { width: 24px; height: 24px; stroke-width: 1.8; }

/* --- Mobile-Grundregeln — OPT-IN über .th-page --------------------------
   Bewusst NICHT global: 16 bestehende Templates nutzen bereits th-input &
   Co., eine globale Regel hätte deren Mobile-Darstellung sofort verändert.
   Der Bestand bleibt, wie er ist; neue Seiten setzen `class="th-page"` auf
   ihren Wurzel-Container und bekommen die Regeln damit bewusst dazu.
   Die Migration des Bestands läuft als eigenes Ticket. */
@media (max-width: 768px) {
    /* iOS Safari zoomt beim Fokus, sobald die Schrift unter 16px liegt.
       Danach ist die Seite verschoben und der Nutzer muss zurückzoomen. */
    .th-page .th-input,
    .th-page .th-select,
    .th-page .th-textarea,
    .th-page .th-cs-trigger,
    /* Zeitfelder im Öffnungszeiten-Gitter sind rohe <input type="time"> ohne
       th-input-Klasse und fielen deshalb nicht unter diese Regel. Der Schutz
       gehört hierher und nicht in den 480er-Block der Komponente: zwischen 481
       und 768 px zoomte iOS sonst weiter beim Fokus (KAN-33, Runde 2). */
    .th-page .th-hours-times input { font-size: 16px; }

    /* Kein Seitwärtsscrollen auf Seitenebene.
       `clip`, NICHT `hidden`: `hidden` macht das Element zum Scroll-Container
       und rechnet `overflow-y` auf `auto` hoch. Auf dem <body> fällt das nicht
       auf (dort propagiert der Overflow zum Viewport) — aber `th-page` sitzt in
       der Praxis auf einem <div>, und dort verliert `.th-sticky-cta` seine
       Klebrigkeit und `.th-cs-dropdown` wird abgeschnitten. `clip` kappt ohne
       diese Nebenwirkungen; ältere Browser ignorieren die Zeile schlicht. */
    .th-page { overflow-x: clip; }
}

/* Tastatur-Navigation muss sichtbar bleiben. Ein globales
   `outline: none` macht die App für Tastaturnutzer unbedienbar. */
.th-page .th-btn-primary:focus-visible,
.th-page .th-btn-ghost:focus-visible,
.th-page .th-btn-danger:focus-visible,
.th-btn-icon:focus-visible,
.th-tab:focus-visible,
.th-acc-head:focus-visible {
    outline: 2px solid var(--g-600);
    outline-offset: 2px;
}

/* Lange Studio-/Leistungsnamen mit Umlauten brechen sonst das Layout. */
.th-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.th-clamp-2 {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* Bilder nie beschneiden, wo das Motiv zählt (Hero, Galerie). */
.th-img { display: block; width: 100%; height: auto; max-width: 100%; }
.th-img--cover { object-fit: cover; }
.th-img--contain { object-fit: contain; background: var(--brand-surface-alt); }

@media (prefers-reduced-motion: reduce) {
    .th-skeleton, .th-spinner,
    .th-page .th-btn-primary.is-loading::before,
    .th-page .th-btn-ghost.is-loading::before { animation: none; }
    .th-page .th-btn-primary,
    .th-page .th-btn-ghost,
    .th-page .th-card { transition: none; }
}

/* === SEITEN-HÜLLE ========================================================
   Ersetzt die je Seite eigens gesetzten Wrapper (as-container, ab-wrap …),
   die sich in Breite und Padding alle leicht unterschieden.

   KEIN eigenes Padding: `.app-container` in base.html bringt bereits
   `60px var(--layout-px) 100px` mit. Ein zusätzliches Shell-Padding
   addiert sich dazu — der erste Versuch kam so auf 80 px oben und 160 px
   unten, und der Seitenkopf schwebte sichtbar in der Fläche.

   Die Breite wird hier begrenzt, statt `is-wide` zu setzen: `is-wide` hebt
   `max-width` ganz auf, und ein Formular oder eine vierspaltige Tabelle
   zieht sich dann über die volle Monitorbreite. Die Alt-Seiten standen auf
   1100 px; 1180 gibt den breiten Tabellen (Buchungen, Studios) etwas mehr
   Luft, ohne die Zeilen unlesbar lang zu machen. */
.th-shell { max-width: 1180px; margin-left: auto; margin-right: auto; }
/* Breite Datenlisten nehmen die volle Fensterbreite. Die Begrenzung oben ist
   für Formulare und Textspalten gedacht; eine achtspaltige Liste scrollt darin
   in sich, während rechts Platz frei bleibt. Die Seite setzt dazu zusätzlich
   `is-wide` am Container (siehe admin/_layout.html), sonst greift dessen
   eigener Deckel von 1440 px. */
.th-shell--wide { max-width: none; }

/* === SEITENKOPF ==========================================================
   Titel + Subline links, Aktionen rechts. Ersetzt die je Seite selbst
   gebauten Kopf-Blöcke (as-header, bt-page-head, ab-head, …).

   `.th-pagetitle` ist bewusst eine Klasse und kein reiner h1-Selektor:
   styles.css wird NACH _theme.css geladen und setzt dort `h1{font-size:40px}`.
   Ein Element-Selektor hier würde also verlieren; eine Klasse gewinnt gegen
   `h1` unabhängig von der Ladereihenfolge — deshalb braucht es kein
   !important, wie es die Alt-Templates durchgehend gesetzt hatten. */
.th-pagehead {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 20px;
    flex-wrap: wrap;
    margin-bottom: 24px;
}
/* Spalte, damit Zurück-Link und Kicker untereinander stehen. Beide sind
   inline-flex und liefen sonst in eine Zeile — der Link sah aus wie ein
   zweites Badge neben „Admin". */
.th-pagehead-titles {
    flex: 1 1 280px;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
}
.th-pagehead-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.th-kicker {
    display: inline-flex;
    padding: 5px 13px;
    margin-bottom: 10px;
    border-radius: var(--radius-pill);
    background: var(--brand-green-100);
    color: var(--brand-green-700);
    font-size: 11.5px;
    font-weight: 800;
    letter-spacing: .5px;
    text-transform: uppercase;
    line-height: 1.4;
}
.th-pagetitle {
    font-size: 28px;
    font-weight: 800;
    color: var(--brand-ink);
    letter-spacing: -.5px;
    line-height: 1.15;
    margin: 0 0 6px;
    /* styles.css zentriert unter 768 px JEDES h1. Der Titel stand dadurch
       mittig, während Kicker und Subline links blieben. */
    text-align: left;
}
.th-pagesub {
    font-size: 14.5px;
    color: var(--brand-ink-mute);
    margin: 0;
    max-width: 70ch;
}
.th-backlink {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 4px 11px 4px 8px;
    margin-bottom: 10px;
    border-radius: var(--radius-pill);
    background: var(--brand-surface-tint);
    color: var(--brand-ink-soft);
    font-size: 12.5px;
    font-weight: 600;
    line-height: 1.4;
    transition: background .15s, color .15s, transform .15s;
}
.th-backlink:hover {
    background: var(--brand-green-100);
    color: var(--brand-green-700);
    transform: translateX(-1px);
}

/* === FILTERLEISTE ========================================================
   Eine Zeile aus Feldern über einer Liste. Bricht auf Mobile um, statt
   waagerecht überzulaufen — die Felder werden dort volle Breite. */
.th-filters {
    display: flex;
    align-items: flex-end;
    gap: 10px;
    flex-wrap: wrap;
    margin-bottom: 18px;
}
.th-filters > * { min-width: 0; }
.th-filters-end { margin-left: auto; }
/* Abgesetzte Variante: die Leiste wird als eigener Block erkennbar, wenn
   direkt darunter eine Liste beginnt. */
.th-filters--boxed {
    background: var(--brand-surface-tint);
    border: 1px solid var(--brand-line);
    border-radius: var(--radius-chip);
    padding: 14px 18px;
}
@media (max-width: 480px) {
    .th-filters { gap: 8px; }
    .th-filters .th-input,
    .th-filters .th-select,
    .th-filters .th-cs { width: 100%; }
    .th-filters-end { margin-left: 0; width: 100%; }
}

/* === LISTEN-ZELLEN =======================================================
   Zweizeilige Zelle: Primärinfo fett, Sekundärinfo klein darunter (Name +
   E-Mail, Büro + Slug). Auf Mobile stapelt `.th-table--stack` die Zeile,
   beide Zeilen bleiben dabei erhalten. */
/* Mehrzeilige Zelle: Wert-Teil in EINEN Container, sonst bricht das Stapeln.
   `.th-table--stack` macht die <td> zu `display:flex` mit `space-between` —
   eine Zelle mit drei Kindern (Name, Mail, Telefon) legt die dort
   nebeneinander statt untereinander, und die Zeilen-Karte lief rechts aus
   dem Bild. Mit dem Wrapper hat die Zelle genau zwei Flex-Kinder: Label und
   Wert. Am Desktop ist es ein normaler Block. */
.th-cell-val { min-width: 0; }
@media (max-width: 768px) {
    .th-page .th-table--stack .th-cell-val {
        display: flex;
        flex-direction: column;
        align-items: flex-end;
        text-align: right;
        overflow-wrap: anywhere;
    }
}
.th-cell-title { font-weight: 700; color: var(--brand-ink); }
.th-cell-sub { font-size: 12.5px; color: var(--brand-ink-mute); margin-top: 2px; }
.th-cell-id { font-size: 13px; font-weight: 600; color: var(--brand-ink-mute); }
.th-cell-right { text-align: right; }

/* Aktions-Gruppe am Zeilenende. Die Buttons müssen die Touch-Mindestgröße
   halten — im Stack-Modus sind sie das Einzige, was man auf dem Handy
   trifft, und lagen vorher bei 28 px Höhe. */
.th-rowactions {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
    justify-content: flex-end;
}
.th-rowactions form { display: inline; margin: 0; }
/* Am Ende einer Detailseite stehen die Aktionen links, nicht rechts — dort
   gibt es keine Tabellenspalte, an der sie sich ausrichten könnten. */
.th-rowactions--start { justify-content: flex-start; }

/* Feld-Wert-Paare auf Detailseiten (Stadt, Telefon, Zweigstelle). Vorher
   waren das <br>-getrennte Zeilen in einem grauen Absatz — nicht
   auszeichenbar und auf dem Handy nicht umbruchfähig. */
.th-deflist {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 14px 20px;
    margin: 0 0 18px;
}
.th-deflist dt { margin-bottom: 3px; }
.th-deflist dd { margin: 0; color: var(--brand-ink-soft); }
@media (max-width: 768px) {
    .th-rowactions { justify-content: flex-start; }
    .th-rowactions .th-btn-sm,
    .th-rowactions .th-btn-primary,
    .th-rowactions .th-btn-ghost,
    .th-rowactions .th-btn-danger { min-height: var(--touch-min); }
}

/* === HINWEIS-BOX =========================================================
   Fehler-/Erfolgsmeldung über einer Liste. Farben ausschließlich aus den
   Status-Tokens, damit eine Fehlermeldung im Admin genauso aussieht wie im
   Studio-Bereich. */
.th-alert {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 13px 17px;
    margin-bottom: 20px;
    border-radius: var(--radius-chip);
    border: 1px solid transparent;
    font-size: 14px;
    font-weight: 600;
    line-height: 1.45;
}
.th-alert svg { flex: 0 0 auto; }
.th-alert--err  { background: var(--danger-bg);  color: var(--danger);  border-color: rgba(185,28,28,.18); }
.th-alert--ok   { background: var(--success-bg); color: var(--success); border-color: rgba(22,101,52,.18); }
.th-alert--warn { background: var(--warn-bg);    color: var(--warn);    border-color: rgba(146,64,14,.18); }

/* === PANEL ===============================================================
   `.th-card` ist bewusst nur die Hülle (Hintergrund, Radius, Schatten) — das
   Padding kam bisher aus einer seitenlokalen Klasse. Für Seiten, die ohne
   eigenen <style>-Block auskommen sollen, ist `.th-panel` die gepolsterte
   Variante samt Überschriften-Muster. */
.th-panel { padding: 22px 24px; }
.th-panel-title {
    font-size: 16px;
    font-weight: 700;
    color: var(--brand-ink);
    letter-spacing: -.1px;
    line-height: 1.3;
    margin: 0;
}
.th-panel-sub { margin: 4px 0 18px; }
/* Trennlinie zwischen zwei Abschnitten IM Panel (Büro-Daten und
   Öffnungszeiten). Ersetzt das `<div class="hr">` der Alt-Templates. */
.th-sep { border: none; border-top: 1px solid var(--brand-line); margin: 22px 0 18px; }
/* Der einzige Abstands-Helfer, den die Formularseiten brauchen: ein Absatz
   oder Knopf, der nicht direkt am vorigen Feld kleben soll. */
.th-mt-14 { margin-top: 14px; }
/* Formularseiten: begrenzte Zeilenlänge. Eingabefelder über ~640 px werden
   nicht besser lesbar, nur schwerer zu treffen. Vorher stand das als
   `style="max-width:600px;margin:0 auto"` in jedem einzelnen Template.

   Linksbündig, NICHT mittig: Der Seitenkopf steht links, ein zentriertes
   Panel darunter wirkt davon abgekoppelt und lässt links eine leere Fläche
   stehen. */
.th-panel--form { max-width: 640px; }
@media (max-width: 480px) {
    .th-panel { padding: 18px 16px; }
}

/* Zwei gleich breite Spalten, ab 768 px untereinander. Mehr Spalten braucht
   der Admin-Bereich nicht — für alles andere gilt weiter eigenes Grid. */
.th-grid-2 {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16px;
    align-items: start;
}
@media (max-width: 768px) {
    .th-grid-2 { grid-template-columns: 1fr; }
}

/* Feld + Button in einer Zeile (Passwort zurücksetzen, Schnell-Eingaben).
   Das Label wird dabei versteckt, nicht weggelassen — ein Eingabefeld ohne
   zugängliches Label ist für Screenreader stumm. */
.th-inline-form { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.th-inline-form .th-input { width: auto; min-width: 150px; flex: 1 1 150px; }
.th-sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* === BEFEHLS-BLOCK =======================================================
   Mehrzeilige Kommandos zum Abtippen oder Kopieren (Restore-Anleitung).
   Scrollt in sich statt die Seite zu verbreitern, und bricht NICHT um: ein
   umgebrochener Shell-Befehl wird beim Kopieren falsch. */
.th-code {
    margin: 0 0 14px;
    padding: 14px 16px;
    background: var(--brand-green-900);
    color: var(--brand-green-100);
    border-radius: var(--radius-chip);
    font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    font-size: 12.5px;
    line-height: 1.65;
    overflow-x: auto;
    white-space: pre;
    -webkit-overflow-scrolling: touch;
}

/* === NACHRICHTEN-KARTE ===================================================
   Liste aus Karten statt aus Tabellenzeilen — für Einträge mit Freitext
   (FAQ-Anfragen, Notizen). Kopfzeile: Status und Betreff links, Datum rechts;
   auf schmalen Schirmen untereinander, weil das Datum sonst den Betreff
   auf eine Zeichenbreite quetscht. */
.th-stack-item { margin-bottom: 12px; }
/* Abgesetzter Block INNERHALB eines Panels — ein Eintrag mit eigenem
   Formular (Preistyp, Stadt). Kein zweiter Schatten, nur ein Rahmen: eine
   Karte in einer Karte wirkt sonst wie zwei Ebenen. */
.th-subcard {
    border: 1px solid var(--brand-line);
    border-radius: var(--radius-chip);
    padding: 16px 18px;
    margin-bottom: 12px;
}
.th-subcard:last-child { margin-bottom: 0; }
.th-subcard.is-muted { opacity: .72; }
@media (max-width: 480px) {
    .th-subcard { padding: 14px; }
}
.th-item-top {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
}
.th-item-head {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    min-width: 0;
}
.th-item-from {
    font-size: 13.5px;
    color: var(--brand-ink-soft);
    margin: 10px 0;
}
.th-item-from a { color: var(--brand-green-700); font-weight: 600; }
.th-item-from a:hover { text-decoration: underline; }
.th-nowrap { white-space: nowrap; }
.th-rowactions--spaced { margin-top: 14px; }
@media (max-width: 480px) {
    .th-item-top { flex-direction: column; gap: 6px; }
}

/* === ÖFFNUNGSZEITEN-GITTER ===============================================
   Ein Wochentag pro Zeile: Schalter links, Zeiten rechts, „Geschlossen" statt
   der Zeitfelder wenn der Tag aus ist. Unter 600 px untereinander, weil zwei
   time-Felder plus Label in einer Zeile nicht in 390 px passen. */
.th-hours { display: flex; flex-direction: column; gap: 8px; }
.th-hours-row {
    display: grid;
    grid-template-columns: 160px 1fr;
    gap: 12px;
    align-items: center;
    padding: 8px 10px;
    background: var(--brand-surface-tint);
    border-radius: var(--radius-chip);
}
.th-hours-toggle {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-weight: 600;
    min-height: var(--touch-min);
}
.th-hours-times { display: flex; align-items: center; gap: 8px; }
.th-hours-times input {
    padding: 8px 10px;
    border: 1px solid var(--brand-line-strong);
    border-radius: var(--radius-input);
    font: inherit;
    font-size: 13px;
    background: var(--brand-surface);
    color: var(--brand-ink);
}
.th-hours-closed { display: none; color: var(--brand-ink-mute); font-style: italic; font-size: 13px; }
.th-hours-row.is-closed .th-hours-times { display: none; }
.th-hours-row.is-closed .th-hours-closed { display: inline; }
@media (max-width: 480px) {
    .th-hours-row { grid-template-columns: 1fr; gap: 6px; }
    /* Die 16 px gegen den iOS-Zoom stehen bei den Mobile-Grundregeln (768 px),
       nicht hier — hier nur das Umbruchverhalten. */
    .th-hours-times input { flex: 1 1 0; min-width: 0; }
}

/* === ZITAT-BLOCK =========================================================
   Fremder Text, den wir unverändert wiedergeben: Nachricht aus einem
   Kontaktformular, Notiz eines Kunden. `pre-wrap` erhält die Absätze des
   Absenders, `overflow-wrap` bricht eine 200 Zeichen lange URL ohne Leerzeichen
   um, statt die Karte aufzuspannen. */
.th-quote {
    background: var(--brand-surface-tint);
    border: 1px solid var(--brand-line);
    border-radius: var(--radius-chip);
    padding: 12px 14px;
    font-size: 14.5px;
    line-height: 1.6;
    color: var(--brand-ink-soft);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

/* === KENNZAHLEN-LEISTE ===================================================
   Zahlenreihe über einer Liste, optional mit Pfeilen als Trichter
   (Registriert → Aktiviert → Vollständig → Öffentlich). Auf dem Handy
   umbrechend statt scrollend; die Pfeile verschwinden dort, weil eine
   umgebrochene Reihe keine Richtung mehr hat. */
.th-kpis {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    padding: 20px 24px;
    margin-bottom: 18px;
}
.th-kpi {
    display: flex;
    flex-direction: column;
    align-items: center;
    min-width: 84px;
}
.th-kpi-num {
    font-size: 30px;
    font-weight: 800;
    color: var(--brand-green-700);
    line-height: 1;
}
.th-kpi-label {
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: .4px;
    text-transform: uppercase;
    color: var(--brand-ink-mute);
    margin-top: 6px;
    text-align: center;
}
.th-kpi-arrow { color: var(--brand-ink-faint); font-weight: 700; font-size: 18px; }
@media (max-width: 480px) {
    .th-kpis { justify-content: space-between; gap: 14px; padding: 18px 16px; }
    .th-kpi-arrow { display: none; }
    .th-kpi-num { font-size: 24px; }
}

/* === LISTEN-CARD =========================================================
   Für Card + Tabelle. `.th-panel` ist dafür der falsche Baustein: dessen
   Padding addiert sich auf das Zellen-Padding, der Zelleninhalt sitzt dann
   sichtbar weiter innen als die Überschrift darüber. Hier trägt stattdessen
   der Kopf sein Padding selbst, und die äußeren Zellen nehmen dasselbe Maß —
   Überschrift, Spaltenkopf und Wert stehen dadurch auf einer Kante. */
.th-listcard { padding: 0; overflow: hidden; }
/* Zwei Listen untereinander (offene Anfragen + Historie) brauchen Luft.
   Als Geschwister-Regel, damit kein Template dafür eine Klasse setzen muss. */
.th-listcard + .th-listcard { margin-top: 18px; }
/* Ausklappbares Formular unter einer Aktions-Gruppe. */
.th-inline-form--below { margin-top: 8px; }
.th-listcard-head {
    padding: 18px 24px;
    border-bottom: 1px solid var(--brand-line);
}
.th-listcard-head > :last-child { margin-bottom: 0; }
/* Kopf, der nur eine Tab-Leiste trägt: Die Leiste bringt ihre eigene
   Unterkante mit, ein zweiter Rahmen darunter wäre eine doppelte Linie. */
.th-listcard-head--tabs { padding: 6px 12px 0; border-bottom: none; }
/* Kompakte Selects für Formulare in einer Tabellenzeile. */
.th-select--sm { padding: 7px 30px 7px 10px; font-size: 13px; }
.th-listcard .th-table th:first-child,
.th-listcard .th-table td:first-child { padding-left: 24px; }
.th-listcard .th-table th:last-child,
.th-listcard .th-table td:last-child { padding-right: 24px; }
.th-listcard .th-table tbody tr:last-child td { border-bottom: none; }
/* `.th-table thead th` setzt text-align:left und ist spezifischer als
   `.th-cell-right` allein — die Spalte „Aktionen" stand dadurch links,
   während die Buttons darunter rechts saßen. An `.th-page` gebunden, weil
   `.th-table` eine bestehende Klasse ist und die Regel sonst jede Tabelle im
   Bestand trifft. */
.th-page .th-table th.th-cell-right { text-align: right; }
/* Freitext in einer Zelle (Notiz, Beschreibung, Ablehnungsgrund) hat keine
   Längenbegrenzung. Ohne Umbruchregel sprengt eine lange Zeichenfolge ohne
   Leerzeichen die Tabellenbreite, und im Stack-Modus die Zeilen-Karte. Nur
   `.th-cell-val` hatte das bisher — hier für jede Zelle, an `.th-page`
   gebunden, damit der Bestand unberührt bleibt. */
.th-page .th-table td { overflow-wrap: anywhere; }
/* Ein Leerzustand IM Card braucht keinen zweiten Rahmen. */
.th-listcard > .th-empty {
    border: none;
    background: none;
    border-radius: 0;
    padding: 48px 24px;
}
@media (max-width: 768px) {
    /* Im Stack-Modus wird jede ZEILE zur Karte. Der umgebende Card-Rahmen
       ergäbe dann Karte-in-Karte — zwei weiße Kästen ineinander. Die Hülle
       gibt ihren Rahmen deshalb hier auf und der Kopf wird normale
       Überschrift; die Zeilen-Karten stehen auf dem Seitenhintergrund. */
    .th-page .th-card.th-listcard { background: none; border: none; box-shadow: none; }
    .th-listcard-head { padding: 0 0 12px; border-bottom: none; }
    .th-listcard .th-table--stack tbody { display: block; }
    .th-listcard .th-table--stack tbody td:first-child { padding-left: 0; }
    .th-listcard .th-table--stack tbody td:last-child { padding-right: 0; }
    .th-listcard > .th-empty { padding: 40px 0; }
}

/* === MATRIX-TABELLE ======================================================
   Nur für echte Matrizen, die sich nicht stapeln lassen. Der Scroll bleibt
   im Container, die Seite selbst scrollt nie waagerecht. Datenlisten nehmen
   stattdessen `.th-table--stack`. */
.th-table-scroll {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

/* === GITTER-LISTE ========================================================
   Datenliste als CSS-Grid statt als <table>. Der Unterschied zur
   `.th-table`: Die Spaltenbreiten stehen fest, statt sich am Inhalt zu
   verteilen. Eine lange Bürobezeichnung schiebt damit nicht die Preisspalte
   zusammen, und die Spalten stehen über alle Zeilen auf derselben Kante.

   Passt die Summe nicht in den Viewport, scrollt der Container waagerecht —
   die SEITE nie. Unter 768 px stapelt jede Zeile zur Karte, wie bei
   `.th-table--stack`, und jede Zelle braucht dort ihr `data-label`.

   Die Spaltenaufteilung setzt die Seite über `--th-glist-cols` am Element,
   damit nicht jede Liste eine eigene Klasse braucht:
     <div class="th-glist" style="--th-glist-cols: 48px 1.5fr 96px">

   Semantik: Kopf und Zeilen tragen `role="row"`/`role="cell"`, der Rahmen
   `role="table"`. Ein <table> waere ehrlicher, kann aber keine festen
   Spalten ueber einen Scroll-Container halten, ohne `table-layout: fixed`
   samt aller Nebenwirkungen. */
.th-glist-scroll {
    /* Die eine Quelle der Kopfhöhe: Der klebende Kopf ist so hoch, und die
       Bedienelemente der Zeilen halten denselben Abstand frei (Begründung
       dort). Beide lesen sie mit einem `var()`-Rückfall auf dieselbe Zahl —
       für eine Liste ohne diesen Rahmen, die sie nicht erbt. Die Rückfälle
       MÜSSEN dieser Deklaration folgen; ein Test vergleicht sie. */
    --th-glist-head-h: 42px;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}
.th-glist { min-width: var(--th-glist-min, 1010px); }
.th-glist-row {
    display: grid;
    grid-template-columns: var(--th-glist-cols);
    align-items: center;
    gap: 0 12px;
    padding: 0 20px;
}
.th-glist-head {
    /* Rückfall, weil die Variable am Scroll-Rahmen steht: Eine Liste ohne
       diesen Rahmen erbt sie nicht, und `var()` ohne Rückfall ließe die Höhe
       auf `auto` fallen — der Kopf schrumpfte still auf Texthöhe. */
    height: var(--th-glist-head-h, 42px);
    background: var(--brand-surface-tint);
    border-bottom: 1px solid var(--brand-line-strong);
    font-size: 11px;
    font-weight: 800;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--brand-ink-mute);
    white-space: nowrap;
}
.th-glist-body .th-glist-row {
    min-height: 62px;
    border-bottom: 1px solid var(--brand-line);
    font-size: 13.5px;
    color: var(--brand-ink-soft);
}
.th-glist-body .th-glist-row:last-child { border-bottom: none; }
.th-glist-body .th-glist-row:hover { background: var(--brand-surface-tint); }

/* Zellen-Spielarten. `--stack` ist die zweizeilige Zelle (Name + Kennung),
   `--num` richtet Zahlen rechts aus und nutzt Tabellenziffern, damit
   Preise unter einander auf derselben Stelle stehen. */
.th-glist-cell { min-width: 0; }
.th-glist-cell--id {
    color: var(--brand-ink-mute);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}
/* `align-items` gesetzt, sonst streckt der Flex-Container seine Kinder auf
   die volle Spaltenbreite — eine Status-Pille sieht dann aus wie ein Balken. */
.th-glist-cell--stack {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
}
.th-glist-cell--num {
    text-align: right;
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    color: var(--brand-ink);
}
.th-glist-cell--nowrap { white-space: nowrap; font-variant-numeric: tabular-nums; }
.th-glist-cell--end { display: flex; justify-content: flex-end; gap: 6px; }
/* Einzeiler, die sonst die Spalte sprengen: kürzen statt umbrechen. Nur
   hier, nicht in `.th-glist-cell--stack`, wo zwei Zeilen gewollt sind. */
.th-glist-clip {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.th-glist-strong { font-weight: 700; font-size: 14px; color: var(--brand-ink); }
.th-glist-meta { font-size: 11.5px; color: var(--brand-ink-mute); }

/* Kopfzeile der Liste: Titel, Anzahl, Aktionen. */
.th-glist-toolbar {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    padding: 14px 20px;
    border-bottom: 1px solid var(--brand-line);
}
.th-glist-count {
    margin-left: auto;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--brand-ink-mute);
}
.th-glist-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 14px 20px;
    font-size: 12.5px;
    color: var(--brand-ink-mute);
}

/* Klebende Kopfzeile — und warum sie ohne Höhendeckel nichts tut.
   `.th-glist-scroll` trägt `overflow-x: auto` und ist damit schon ein
   Scroll-Container; ohne Deckel gibt es darin aber nichts zu scrollen.
   Gescrollt wird die Seite, und für die ist ein `sticky` hier nicht mehr
   zuständig. Der Deckel ist also die Voraussetzung, nicht die Zutat.

   **Der Deckel ist Opt-in.** Er steht bewusst NICHT als Vorgabe da: Aus einer
   Liste im Seitenfluss wird damit ein eigener Scrollbereich in einer Seite,
   die selbst scrollt — das ist eine Entscheidung je Liste, und eine Liste, die
   den Deckel nicht braucht, hätte nur das verschachtelte Scrollen davon.

   Gesetzt wird `--th-glist-max` am **Scroll-Rahmen** und nicht an der Liste
   darin. Das ist der Fallstrick: Die Geschwister-Variablen `--th-glist-cols`
   und `--th-glist-min` stehen am Kind `.th-glist`, und Variablen erben nur
   nach unten. Wer sie aus Gewohnheit dort hinschreibt, setzt ins Leere.

     <div class="th-glist-scroll" style="--th-glist-max: 72vh"> */
.th-glist-scroll { max-height: var(--th-glist-max, none); }

/* Fokusrand der Zeilen-Bedienelemente, damit der klebende Kopf den Fokusring
   beim Sprung nach oben nicht verdeckt (Anleitung im Styleguide).

   Hier steht nur, warum es NICHT `scroll-padding-top` am Scroll-Rahmen ist:
   Das gälte auch für die Sortierköpfe IM klebenden Kopf, und die können die
   verlangte Position nie einnehmen — gemessen sprang die Liste bei jedem Tab
   auf einen Spaltenkopf von 400 auf 76 zurück. Der Nachfahren-Selektor trifft
   auch die Einträge im Kebab-Menü; dort ist er wirkungslos (`position: fixed`,
   also kein Scrollziel dieses Containers). */
/* `:is(...)` statt sechs Selektoren: Eine Verwaltungsliste hat mehr Fokusziele
   je Zeile als Links und Knöpfe — `admin_users.html` führt zwei Auswahlfelder,
   `admin_cities.html` ein `summary`. Wer die hier vergisst, merkt es erst,
   wenn dort der Fokusring wieder hinter dem Kopf liegt. */
.th-glist-body .th-glist-row :is(a, button, select, summary, input, [tabindex]) {
    scroll-margin-top: var(--th-glist-head-h, 42px);
}
.th-glist-head {
    position: sticky;
    top: 0;
    /* Über den Zeilen, aber weit unter dem Kebab-Menü (60) und dem
       Filter-Panel — die liegen als Overlay über der ganzen Liste. */
    z-index: 2;
}
/* Der Kopf einer Zahlenspalte bleibt Kopf. `--num` setzt Farbe und Gewicht
   für Werte; im Kopf gewann das gegen die Kopfzeile, und „Preis" stand
   dunkler und dünner da als seine Nachbarn. Die Rechtsausrichtung bleibt. */
.th-glist-head .th-glist-cell--num {
    color: inherit;
    font-weight: inherit;
}

/* Zelle mit dem Aufklapp-Knopf der Spalten-Priorisierung (Regeln im
   Mobile-Block unten). Sie trägt `role="cell"`, und das ist keine Förmlichkeit:
   Ein Knopf als direktes Kind einer `role="row"` ist im ARIA-Tabellenmodell
   kein gültiges Kind — Screenreader bieten ihn dann nicht als Bedienelement
   der Zeile an, und damit wäre auf dem Handy nicht nur der Inhalt versteckt,
   sondern auch der Weg zurück.

   Auf Desktop hat sie nichts zu tun, dort stehen alle Spalten. Sie ist die
   EINZIGE Stelle, die den Knopf ein- und ausblendet — `display: none` nimmt
   ihn zugleich aus der Tab-Reihenfolge, und ein zweiter Schalter am Knopf
   selbst wäre eine zweite Wahrheit. */
.th-glist-morecell { display: none; }

/* Nebenaktionen im Menü statt als Knopfreihe. Bei vier Aktionen pro Zeile
   ist die Reihe breiter als jede Datenspalte und drängt den Inhalt weg. */
.th-kebab {
    width: 34px;
    height: 34px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
    font-size: 16px;
}
.th-menu {
    /* `fixed`, nicht `absolute`: Die Liste steht in einem Card mit
       `overflow: hidden` und in einem waagerechten Scroll-Container. Ein
       absolut positioniertes Menü wird von beiden abgeschnitten. Ein festes
       entkommt der Beschneidung — die Koordinaten setzt das JS beim Öffnen aus
       der Position des Knopfes, und ein Scroll schliesst es wieder. */
    position: fixed;
    z-index: 60;
    min-width: 210px;
    padding: 5px;
    background: var(--brand-surface);
    border: 1px solid var(--brand-line-strong);
    border-radius: var(--radius-chip);
    box-shadow: var(--shadow-card-hover);
    display: flex;
    flex-direction: column;
    gap: 2px;
    text-align: left;
}
.th-menu[hidden] { display: none; }
.th-menu form { margin: 0; }
.th-menu button,
.th-menu a {
    display: block;
    width: 100%;
    text-align: left;
    border: none;
    background: none;
    border-radius: var(--radius-input);
    padding: 9px 11px;
    font: inherit;
    font-size: 13px;
    font-weight: 600;
    color: var(--brand-ink);
    cursor: pointer;
}
.th-menu button:hover,
.th-menu a:hover { background: var(--brand-surface-tint); }
.th-menu .th-menu-danger { color: var(--danger); }
.th-menu .th-menu-danger:hover { background: var(--danger-bg); }
.th-menu-sep { border: none; height: 1px; background: var(--brand-line); margin: 4px 7px; }

@media (max-width: 768px) {
    /* Aus dem Gitter wird eine Karte pro Zeile — dieselbe Logik wie bei
       `.th-table--stack`, inklusive Label aus `data-label`. */
    /* Auf dem Handy keine erzwungene Mindestbreite — ausser der, die die
       gewaehlten Spalten wirklich brauchen. Das Skript setzt sie in
       `--th-glist-min-mobil`; darueber laeuft die Tabelle im Scroll-Container
       seitwaerts, statt die Wahl zu sperren.

       Sie gehoert an das GITTER, nicht an die Zeilen: Die Zeilen sind
       Block-Kinder und nehmen dessen Breite. Ohne sie waren sie 326px breit,
       ihr Gitter-Inhalt lief auf 760px ueber — und rechts vom Sichtfenster war
       nach dem Wischen kein Hintergrund mehr, weil dort gar kein Element lag.

       Ohne die Variable (kein Skript) bleibt es bei 0, also bei der
       Karten-Ansicht. */
    .th-glist { min-width: var(--th-glist-min-mobil, 0); }
    .th-glist-head { display: none; }
    .th-glist-body .th-glist-row {
        display: block;
        padding: 4px 12px;
        margin-bottom: 10px;
        background: var(--brand-surface);
        border: 1px solid var(--brand-line);
        border-radius: var(--radius-chip);
    }
    .th-glist-body .th-glist-row:hover { background: var(--brand-surface); }
    .th-glist-cell {
        display: flex;
        justify-content: space-between;
        align-items: baseline;
        gap: 16px;
        padding: 8px 0;
        border-bottom: 1px solid var(--brand-line);
        text-align: right;
    }
    .th-glist-cell:last-child { border-bottom: none; }
    .th-glist-cell::before {
        content: attr(data-label);
        flex: 0 0 auto;
        font-size: 11.5px;
        font-weight: 800;
        letter-spacing: .4px;
        text-transform: uppercase;
        color: var(--brand-ink-mute);
        text-align: left;
    }
    /* Zweizeilige Zelle: Label links, die Werte rechts UNTEREINANDER. Mit
       `flex-direction: column` landete das Label auf einer eigenen Zeile und
       stand damit anders als bei jeder einzeiligen Zelle. Als Gitter behält es
       seine Spalte und überspannt alle Wertzeilen. */
    .th-glist-cell--stack {
        display: grid;
        grid-template-columns: auto 1fr;
        align-items: baseline;
        gap: 2px 16px;
    }
    .th-glist-cell--stack::before { grid-column: 1; grid-row: 1 / -1; }
    /* `justify-self`, sonst zieht das Gitter die Status-Pille auf die volle
       Spaltenbreite und sie wirkt als Balken. */
    .th-glist-cell--stack > * { grid-column: 2; justify-self: end; text-align: right; }
    .th-glist-clip { white-space: normal; overflow: visible; overflow-wrap: anywhere; }
    .th-glist-cell--end { justify-content: space-between; }
    /* Kein Höhendeckel auf dem Handy: Aus den Zeilen sind Karten geworden,
       die im Seitenfluss stehen. Ein eigener Scrollbereich darin wäre ein
       zweites Scrollen im ersten.

       Der Fokusrand fällt mit: Der Listenkopf ist hier ausgeblendet, es gibt
       also keinen klebenden Kopf DIESER Liste mehr, vor dem er schützen
       könnte — und weil hier die Seite scrollt, hielte er sonst bei jedem
       Sprung eine Kopfhöhe Leerraum über dem Ziel frei. Die klebende
       Seiten-Topbar ist eine andere Baustelle: Sie deckt Fokusziele auf jeder
       Seite gleichermaßen ab, nicht nur in dieser Liste. */
    .th-glist-scroll { max-height: none; }
    .th-glist-body .th-glist-row :is(a, button, select, summary, input, [tabindex]) {
        scroll-margin-top: 0;
    }

    /* --- SPALTEN-PRIORISIERUNG -------------------------------------------
       Stehen bleiben nur die Zellen mit `data-prio`, der Rest klappt je Zeile
       auf. WELCHE Spalten das sind, entscheidet die Seite am `data-prio` der
       Zelle — die Komponente kennt die Bedeutung der Spalten nicht.

       Alles hängt an `is-prio`, und die Klasse setzt admin-list.js an der
       ZEILE, nicht das Template und nicht am Gitter. Zweierlei steckt darin:
       Ohne JavaScript gibt es keinen Knopf, der die Zellen zurückholt — sie per
       CSS allein zu verstecken machte die Daten unerreichbar (dieselbe Regel
       wie bei den Filter-Chips). Und die Bedingung muss dort hängen, wo der
       Knopf entsteht: am Gitter versteckte eine einzige Zeile ohne
       ausgezeichnete Zelle ihren ganzen Inhalt mit.

       Der `>`-Schritt hält die versteckte Menge deckungsgleich mit der, die
       `klappzeilen()` zählt — Begründung dort, nicht hier zweimal. */
    /* `.th-glist:not(.is-spalten)` davor, weil diese vier Regeln (0,4,0)
       wiegen und die Tabellen-Regel darunter (0,3,0): Ohne die Eingrenzung
       versteckte die Karten-Regel eine GEWAEHLTE Spalte, die kein `data-prio`
       traegt — und zwar unabhaengig davon, was die Spaltenwahl entscheidet. */
    .th-glist:not(.is-spalten) .th-glist-row.is-prio > .th-glist-cell:not([data-prio]) { display: none; }
    /* Kein `:not([hidden])` an den Aufklapp-Regeln: Es wäre wirkungslos. Der
       `[hidden]`-Eintrag am Anfang dieser Datei trägt ein `!important` und
       gewinnt damit gegen jede Klassenregel — eine per `hidden` verborgene
       Zelle holt das Aufklappen also ohnehin nicht zurück. */
    .th-glist:not(.is-spalten) .th-glist-row.is-prio.is-open > .th-glist-cell:not([data-prio]) { display: flex; }
    /* Muss NACH der Zeile darüber stehen und dieselbe Genauigkeit tragen: Die
       zweizeilige Zelle ist auf dem Handy ein Gitter, kein Flex (Begründung
       oben) — sonst rutscht ihr Label beim Aufklappen weg. */
    .th-glist:not(.is-spalten) .th-glist-row.is-prio.is-open > .th-glist-cell--stack:not([data-prio]) { display: grid; }
    .th-glist:not(.is-spalten) .th-glist-row.is-prio > .th-glist-morecell { display: block; }

    /* Aufklappbare Zeilen sind anklickbar — der Knopf am rechten Rand ist auf
       dem Handy die unbequemste Stelle. Der Zeiger sagt es; ohne ihn probiert
       es niemand.

       Gebunden an den KNOPF, nicht an `is-prio`: Die Klasse traegt auch die
       Zeile aus dem Leer-Rahmen-Zweig, die nur eine leere Spur bekommt (alle
       abgewaehlten Zellen per `hidden` verborgen). Die zeigte „anklickbar" und
       tat nichts. Kennt ein Browser `:has()` nicht, faellt der Zeiger weg —
       das ist der harmlose der beiden Fehler. */
    .th-glist-row.is-prio:has(.th-glist-more) { cursor: pointer; }
    /* Ausser dort, wo man etwas anderes trifft: Sonst verspricht der Zeiger ein
       Aufklappen, und der Klick tut etwas ganz anderes. */
    /* Dieselbe Liste wie im Waechter des Zeilenklicks (admin-list.js), sonst
       verspricht der Zeiger ein Aufklappen, das der Klick verweigert — und in
       der Gegenrichtung genauso. `form` und `[tabindex]` gehoeren dazu: Der
       Zwischenraum eines Formulars ist kein Aufklapp-Gebiet. */
    .th-glist-row.is-prio :is(a, button, input, select, textarea, label, form,
                              [tabindex]) {
        cursor: auto;
    }
    .th-glist-row.is-prio :is(a, button, label, summary) { cursor: pointer; }

    /* Der Knopf steht nur dort, wo er etwas tut: am Schreibtisch sind alle
       Spalten sichtbar, eine Wahl waere sinnlos. */
    .th-tools.is-spaltenwahl .th-spaltenwahl { display: block; }
    /* `.th-chips-toggle` ist `display:none` — der Filter-Knopf daneben wird
       erst vom Skript eingeblendet. Der Spalten-Knopf teilt die Klasse und
       braucht dieselbe Freischaltung; im Template stand dafuer ein
       Inline-`style`. */
    .th-tools.is-spaltenwahl .th-spaltenwahl .th-chips-toggle { display: inline-flex; }
    /* Der Knopf war gemessen 34px hoch: `.th-chips-toggle` hat Innenabstand,
       aber keinen Mindestwert. Gilt fuer den Filter-Knopf daneben mit — er hatte
       dasselbe Problem und niemandem ist es aufgefallen. */
    .th-chips-toggle { min-height: var(--touch-min); }
    /* Und die Bedienelemente in der Aktionszelle: Die 44px-Regel im
       Mobile-Block haengt an `.th-rowactions`, und die Bueroliste hat den
       Wrapper nicht (KAN-91). Sie haengt jetzt zusaetzlich an der Zelle selbst
       — dort kann sie nicht vergessen werden, weil die Zelle die Spalte IST. */
    .th-glist-cell--end :is(a, button) { min-height: var(--touch-min); }

    /* --- TABELLEN-MODUS (KAN-101) ---------------------------------------
       Mit `is-spalten` bleibt die Liste auch hier eine Tabelle: Kopf sichtbar,
       Identitaets- und Aktionsspalte immer dabei, dazwischen die gewaehlten
       Spalten. Die Klasse setzt admin-list.js, nicht das Template — ohne
       JavaScript gibt es keine Spaltenwahl, und dann muessen die Karten oben
       greifen, in denen jede Zelle erreichbar ist. Dieselbe Regel wie bei
       `is-prio`.

       Die Spaltenbreiten stehen in `--th-glist-cols-mobil` und werden vom
       Skript gesetzt: Welche Spalten sichtbar sind, weiss nur es. */
    .th-glist.is-spalten .th-glist-head {
        display: grid;
        grid-template-columns: var(--th-glist-cols-mobil);
        /* DERSELBE Innenabstand wie die Datenzeilen darunter. Der Kopf erbt
           sonst die 20px vom Schreibtisch: Gemessen stand er dann auf einem
           286px breiten Gitter und die Zeilen auf 306px — die `fr`-Spuren
           teilten also verschiedene Restbreiten auf, und die Spalten standen
           um 12px versetzt. */
        padding: 0 10px;
    }
    /* Die Hoehe steht bewusst NICHT hier: Der Kopf klebt auch in diesem Modus,
       und die Fokusraender der Zeilen halten `--th-glist-head-h` frei. Eine
       eigene Zahl liefe von ihr weg, sobald eine von beiden sich aendert. Ein
       langer Spaltenname wird deshalb gekuerzt statt umgebrochen — sonst waere
       der Kopf hoeher als der Abstand, den der Fokus einplant. */
    .th-glist.is-spalten .th-glist-head .th-glist-cell {
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }
    .th-glist.is-spalten .th-glist-body .th-glist-row {
        display: grid;
        grid-template-columns: var(--th-glist-cols-mobil);
        padding: 0 10px;
        margin-bottom: 0;
        border: none;
        border-bottom: 1px solid var(--brand-line);
        border-radius: 0;
        background: var(--brand-surface);
    }
    .th-glist.is-spalten .th-glist-body .th-glist-row:last-child { border-bottom: none; }
    .th-glist.is-spalten .th-glist-cell {
        display: flex;
        justify-content: flex-start;
        align-items: center;
        gap: 6px;
        padding: 9px 6px;
        border-bottom: none;
        text-align: left;
        min-height: var(--touch-min);
    }
    /* Kein Label in der Zelle: Dafuer ist der Kopf wieder da. */
    .th-glist.is-spalten .th-glist-cell::before { content: none; }
    /* Zweizeilige Zelle bleibt zweizeilig, aber ohne Label-Spalte: die zwei
       Werte stehen untereinander und linksbuendig wie am Schreibtisch. */
    .th-glist.is-spalten .th-glist-cell--stack {
        display: grid;
        grid-template-columns: minmax(0, 1fr);
        align-items: start;
        gap: 1px;
    }
    .th-glist.is-spalten .th-glist-cell--stack > * {
        grid-column: 1;
        justify-self: start;
        text-align: left;
    }
    /* Die Metazeilen einer zweizeiligen Zelle bleiben EINZEILIG. Gemessen
       wurde sonst eine Statuszelle mit 111px Hoehe: drei Metazeilen a 35px,
       weil jede in einer 59px-Spalte zweimal umbrach. Der Wert oben darf
       umbrechen — er ist der Grund, warum die Zeile existiert; die Zusatzzeile
       darunter wird gekuerzt, und die ganze Angabe steht am Schreibtisch und
       in der Detailansicht. */
    .th-glist.is-spalten .th-glist-cell--stack .th-glist-meta {
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        max-width: 100%;
    }
    .th-glist.is-spalten .th-glist-cell--end { justify-content: flex-end; }
    /* Abgewaehlte Spalte: das Skript setzt die Marke an Kopf- UND Datenzelle. */
    .th-glist.is-spalten [data-spalte-aus] { display: none; }
    /* Aufgeklappt: die abgewaehlten Zellen unter der Zeile, jede ueber die
       ganze Breite. `1 / -1` statt einer Spur — sonst stauchte sich der Wert in
       die Breite der ersten Spalte. Das Label kommt zurueck: In der Tabelle
       steht es im Kopf, hier unten gibt es keinen. */
    .th-glist.is-spalten .th-glist-row.is-open > .th-glist-cell[data-spalte-aus] {
        display: block;
        grid-column: 1 / -1;
        padding: 6px 0 8px;
        border-top: 1px dashed var(--brand-line);
    }
    .th-glist.is-spalten .th-glist-row.is-open > .th-glist-cell[data-spalte-aus]::before {
        content: attr(data-label);
        display: block;
        margin-bottom: 2px;
        font-size: 10.5px;
        font-weight: 800;
        letter-spacing: .04em;
        text-transform: uppercase;
        color: var(--brand-ink-mute);
    }
    /* Der Knopf bleibt: Er ist der einzige Weg zu einer abgewaehlten Spalte,
       und eine davon kann ein Formular enthalten (Rollenwechsel in der
       Benutzerliste). Seine Spur haengt im `--th-glist-cols-mobil`, das das
       Skript setzt — der Kopf hat dort eine Spur mehr als Zellen, das ist
       gewollt und im Gitter kein Fehler. */
    .th-glist.is-spalten .th-glist-row.is-prio > .th-glist-morecell {
        display: block;
        text-align: right;
    }
    /* Der Winkel-Knopf: quadratisch, damit die Spur schmal bleiben kann. Die
       44px Tippflaeche kommen von der Hoehe, nicht von der Breite. */
    .th-glist-more--winkel {
        width: 100%;
        min-height: var(--touch-min);
        padding: 0;
        font-size: 15px;
        line-height: 1;
    }
    .th-glist-more {
        display: flex;
        align-items: center;
        justify-content: center;
        gap: 8px;
        width: 100%;
        min-height: var(--touch-min);
        padding: 8px 0;
        border: none;
        background: none;
        font: inherit;
        font-size: 12.5px;
        font-weight: 700;
        color: var(--brand-green-700);
        cursor: pointer;
    }
    .th-glist-more::after { content: "▾"; }
    .th-glist-row.is-open .th-glist-more::after { content: "▴"; }

    .th-glist-toolbar,
    .th-glist-foot { padding: 14px 16px; }
}
/* Anker für das Kebab-Menü. Ohne `position: relative` würde das Menü am
   nächsten positionierten Vorfahren hängen — bei einer Liste im Scroll-Container
   ist das der falsche Bezug und es landet am Seitenrand. */
.th-menu-wrap { display: inline-flex; }

/* === LISTEN-WERKZEUGE ====================================================
   Suchfeld, Filter-Chips und sortierbare Spaltenköpfe. Das Verhalten steckt in
   app/static/js/admin-list.js, hier nur das Aussehen. */
.th-tools {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    padding: 14px 20px;
    border-bottom: 1px solid var(--brand-line);
}
.th-tools-search {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 280px;
    max-width: 100%;
    padding: 9px 12px;
    background: var(--brand-surface-tint);
    border: 1px solid var(--brand-line-strong);
    border-radius: var(--radius-input);
}
.th-tools-search svg { flex: 0 0 auto; color: var(--brand-ink-mute); }
.th-tools-search input {
    flex: 1;
    min-width: 0;
    border: none;
    outline: none;
    background: none;
    font: inherit;
    font-size: 13px;
    color: var(--brand-ink);
}
.th-chips { display: flex; gap: 6px; flex-wrap: wrap; }
.th-chip {
    padding: 7px 14px;
    border: 1px solid var(--brand-line-strong);
    border-radius: var(--radius-pill);
    background: var(--brand-surface);
    color: var(--brand-ink-soft);
    font: inherit;
    font-size: 12.5px;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
}
.th-chip:hover { background: var(--brand-surface-tint); }
.th-chip[aria-pressed="true"] {
    background: var(--brand-green-700);
    border-color: var(--brand-green-700);
    color: #ffffff;
}

/* Sortierbarer Spaltenkopf. Der Pfeil steht immer da — erscheint er erst beim
   Hover, ist nicht erkennbar, dass die Spalte überhaupt sortierbar ist. */
.th-sortable { cursor: pointer; user-select: none; }
.th-sortable:hover { color: var(--brand-green-700); }
.th-sortable:focus-visible {
    outline: 2px solid var(--brand-green-700);
    outline-offset: -2px;
    border-radius: var(--radius-input);
}
.th-sortable[aria-sort="ascending"],
.th-sortable[aria-sort="descending"] { color: var(--brand-green-700); }
.th-sort-arrow { color: var(--brand-ink-faint); font-size: 11px; margin-left: 4px; }
.th-sortable[aria-sort="ascending"] .th-sort-arrow,
.th-sortable[aria-sort="descending"] .th-sort-arrow { color: var(--brand-green-700); }

/* Kein Treffer: gehört in die Liste, nicht als Toast daneben. */
.th-nohits { padding: 48px 24px; text-align: center; color: var(--brand-ink-mute); font-size: 14px; }

/* Notiz über einer Liste: erklärt, warum eine Kennzahl kleiner ist als die
   Liste darunter. Eigene Klasse statt `th-pagesub`, weil die als letztes
   Element des Seitenkopfs ohne Abstand nach unten entworfen ist — hier klebte
   die Notiz dadurch am Karten-Rand darunter (gemessen: 0 px). */
.th-listnote {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 8px;
    margin: 0 0 16px;
    font-size: 13.5px;
    line-height: 1.45;
    color: var(--brand-ink-mute);
}

/* Filter-Chips auf schmalen Schirmen hinter einem Knopf zusammenfassen.
   Neun Chips standen dort auf vier Zeilen und machten die Leiste 297px hoch —
   ein Drittel Handybildschirm, bevor die erste Datenzeile kommt.

   Die Umschaltung haengt an `is-chips-collapsible`, und die Klasse setzt
   admin-list.js. Ohne JavaScript fehlt sie, die Chips bleiben also sichtbar:
   Wer die Leiste per CSS allein zusammenklappte, versteckte sie hinter einem
   Knopf, der ohne Skript nichts tut. */
.th-chips-toggle {
    display: none;
    align-items: center;
    gap: 8px;
    padding: 8px 14px;
    border: 1px solid var(--brand-line-strong);
    border-radius: var(--radius-pill);
    background: var(--brand-surface);
    color: var(--brand-ink);
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
}
.th-chips-toggle:hover { border-color: var(--brand-green-700); }
.th-chips-active {
    background: var(--g-100);
    color: var(--g-800);
    border-radius: var(--radius-pill);
    padding: 2px 9px;
    font-size: 11.5px;
    font-weight: 700;
    max-width: 132px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* --- SPALTENWAHL (KAN-101) ---------------------------------------------
   Der Knopf spricht die Sprache des Filter-Knopfs daneben: gleiche Form,
   gleiche Pille fuer den aktiven Zustand. Er startet im Markup `hidden` und
   wird von admin-list.js eingeblendet — ohne JavaScript bleibt die Liste in
   der Karten-Ansicht, in der jede Zelle erreichbar ist.

   Das Klappmenue liegt `absolute` und nicht `fixed` wie `.th-menu`: Die
   Werkzeugleiste sitzt OBEN in der Karte, das Menue klappt also nach unten in
   die Karte hinein und nicht aus ihr heraus. Der Grund fuer das `fixed` dort
   — `overflow: hidden` der Karte und der waagerechte Scroll-Container —
   trifft hier nicht zu. */
.th-spaltenwahl { position: relative; display: none; }
.th-spalten-menue {
    /* `fixed`, nicht `absolute`: Die Werkzeugleiste sitzt oben in einer langen
       Karte, und ein im Block geparktes Menue landet weit unter dem Bildschirm
       — gemessen bei 390x844 um 336px hinausragend, bei 390x640 vollstaendig
       ausserhalb. Position und Hoehe rechnet `panelImBild` in admin-list.js,
       dieselbe Rechnung wie beim Filter-Panel. Die Werte hier sind der
       Rueckfall, wenn kein Skript laeuft — dann ist das Menue aber auch nie
       sichtbar (`hidden` im Markup). */
    position: fixed;
    z-index: 60;
    min-width: 216px;
    max-height: 60vh;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 5px;
    background: var(--brand-surface);
    border: 1px solid var(--brand-line-strong);
    border-radius: var(--radius-chip);
    box-shadow: var(--shadow-card-hover);
}
.th-spalten-menue label {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 0 9px;
    min-height: var(--touch-min);
    border-radius: var(--radius-input);
    font-size: 13.5px;
    font-weight: 600;
    color: var(--brand-ink);
    cursor: pointer;
}
.th-spalten-menue label:hover { background: var(--brand-surface-tint); }
.th-spalten-menue input { width: 18px; height: 18px; accent-color: var(--g-700); }
/* Eine Spalte, die nicht abwaehlbar ist, steht mit ihrem Grund da statt zu
   fehlen: Wer sie sucht, soll nicht raten, warum es sie nicht gibt. */
.th-spalten-menue .th-spalten-fest {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 6px 9px;
    font-size: 12.5px;
    color: var(--brand-ink-mute);
}
.th-spalten-menue .th-spalten-fest b { color: var(--brand-ink); font-weight: 600; }
.th-spalten-fuss {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 8px;
    border-top: 1px solid var(--brand-line);
    margin-top: 4px;
    padding: 7px 9px 3px;
    font-size: 11.5px;
    color: var(--brand-ink-mute);
}
.th-spalten-reset {
    border: none;
    background: none;
    padding: 0;
    font: inherit;
    font-size: 11.5px;
    font-weight: 700;
    color: var(--g-700);
    cursor: pointer;
    text-decoration: underline;
}

@media (max-width: 768px) {
    /* Knopf statt Chips — aber nur, wenn das Skript ihn uebernommen hat. */
    .th-tools.is-chips-collapsible .th-chips-toggle { display: inline-flex; }
    .th-tools.is-chips-collapsible .th-chips { display: none; }

    /* Aufgeklappt wird ein DROPDOWN, keine aufklappende Chip-Reihe: Die
       Filter stehen untereinander, das Panel liegt UEBER dem Inhalt und
       schiebt die Seite nicht auseinander. Vorher war das Aufgeklappte
       dieselbe mehrzeilige Chip-Wand wie vor KAN-83, nur spaeter (KAN-84).

       `fixed` statt `absolute` — aus demselben Grund wie bei `th-menu`
       oben: Die Leiste sitzt in einer Karte mit `overflow: hidden` und in
       einem waagerechten Scroll-Container, ein absolut positioniertes Panel
       wuerde von beiden abgeschnitten. Die Koordinaten setzt das JS aus der
       Position des Knopfes, ein Scroll schliesst das Panel. */
    .th-tools.is-chips-collapsible.is-chips-open .th-chips {
        display: flex;
        position: fixed;
        z-index: 60;
        flex-direction: column;
        flex-wrap: nowrap;
        align-items: stretch;
        gap: 2px;
        /* Rueckfall: Die echte Deckelung rechnet das JS aus dem Platz unter
           bzw. ueber dem Knopf — ein Wert in vh kennt diesen Abstand nicht. */
        max-height: 60vh;
        overflow-y: auto;
        padding: 5px;
        background: var(--brand-surface);
        border: 1px solid var(--brand-line-strong);
        border-radius: var(--radius-chip);
        box-shadow: var(--shadow-card-hover);
    }
    /* Im Panel sind die Chips Menuezeilen, keine Pillen. */
    .th-tools.is-chips-collapsible.is-chips-open .th-chip {
        width: 100%;
        text-align: left;
        border: none;
        border-radius: var(--radius-input);
        padding: 10px 12px;
        font-size: 13.5px;
    }
    /* Der aktive Filter muss im Panel zu erkennen sein — sonst weiss man
       beim Oeffnen nicht, wo man gerade steht. */
    .th-tools.is-chips-collapsible.is-chips-open .th-chip[aria-pressed="true"] {
        background: var(--g-100);
        color: var(--g-800);
    }
    .th-tools.is-chips-collapsible.is-chips-open
        .th-chip[aria-pressed="true"]::after {
        content: "✓";
        float: right;
        font-weight: 700;
    }
}

@media (max-width: 480px) {
    .th-tools { padding: 14px 16px; }
    .th-tools-search { width: 100%; }
    .th-chips-toggle { flex-basis: 100%; justify-content: center; }
}

/* === LESBARE TEXTSPALTE ==================================================
   Auf einer Seite mit voller Fensterbreite werden Fließtext-Absätze sonst
   200 Zeichen lang und damit unlesbar — das Auge verliert die Zeile. Gilt für
   Anleitungen und Erklärungen, NICHT für Tabellen und Formulare. */
.th-prose { max-width: 78ch; }

/* === GEFAHRENZONE ========================================================
   Unumkehrbare Aktion, räumlich abgesetzt statt als weiterer Knopf in der
   Reihe. Rot umrandet, damit der Griff dorthin bewusst passiert. */
.th-danger-zone { border: 1px solid var(--danger); background: var(--danger-bg); }
.th-danger-zone .th-panel-title { color: var(--danger); }


/* === BENTO-GITTER (Admin-Uebersicht) =====================================
   Aufbau aus der Design-Vorgabe `admin-cockpit-bento-dark.html`: vier
   Kennzahl-Kacheln, darunter zwei halbbreite Bloecke und ein breiter. Die
   Vorgabe ist dunkel und bringt zwei eigene Schriften mit — uebernommen sind
   Raster, Groessenverhaeltnisse und Bausteine, die Farben kommen aus diesem
   Theme und die Schrift bleibt Inter.

   Zahlen stehen ueberall auf `tabular-nums`: sonst wandert das Komma zwischen
   zwei Betraegen, die untereinander stehen. */
.th-bento {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px;
    font-variant-numeric: tabular-nums;
}

.th-bento-tile {
    background: var(--brand-surface);
    border: 1px solid var(--brand-line);
    border-radius: var(--radius-card);
    padding: 16px;
    transition: border-color .16s ease, transform .16s ease;
}

a.th-bento-tile { display: block; text-decoration: none; color: inherit; }
a.th-bento-tile:hover { border-color: var(--g-400); transform: translateY(-2px); }

.th-bento-tile--half { grid-column: span 2; }
.th-bento-tile--full { grid-column: span 2; }

/* Kennzahl: Benennung oben, Zahl gross, Notiz unten. Die Notiz sitzt am
   unteren Rand, damit die Zahlen einer Reihe auf einer Linie stehen.

   Zwei Klassen im Selektor, nicht eine: `a.th-bento-tile` setzt oben
   `display: block` und hat damit die hoehere Spezifitaet. Die erste Fassung
   traf deshalb genau die vier verlinkten Kacheln NICHT — sie waren block,
   `gap` und `align-self` blieben wirkungslos. */
.th-bento-tile.th-kpi-tile {
    display: grid;
    grid-template-rows: auto auto 1fr;
    gap: 8px;
    min-height: 124px;
}

.th-bento-lab {
    font-size: 10.5px;
    font-weight: 800;
    letter-spacing: .13em;
    text-transform: uppercase;
    color: var(--ink-mute);
}

.th-kpi-tile-num {
    font-size: 36px;
    font-weight: 700;
    letter-spacing: -.04em;
    line-height: 1;
    color: var(--brand-ink);
}

.th-kpi-tile-note {
    font-size: 12px;
    font-weight: 600;
    color: var(--g-600);
    align-self: end;
}

.th-kpi-tile--akzent { background: var(--g-50); }
.th-kpi-tile--akzent .th-kpi-tile-num,
.th-kpi-tile--akzent .th-kpi-tile-note { color: var(--status-blocked); }

/* Kopf eines Bento-Blocks: Titel links, Verweis rechts. */
.th-bento-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 4px;
}
.th-bento-head h2 {
    font-size: 18px;
    font-weight: 800;
    letter-spacing: -.02em;
    margin: 0;
    color: var(--brand-ink);
}
.th-bento-head a { font-size: 13px; font-weight: 700; color: var(--g-700); }
.th-bento-sub {
    font-size: 13px;
    font-weight: 500;
    color: var(--ink-mute);
    margin: 0 0 20px;
}

/* Trichterstufe: Benennung, Balken, Zahl mit Differenz zur Stufe darueber. */
.th-step {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 78px;
    align-items: center;
    gap: 12px;
    padding: 12px 0;
}
.th-step + .th-step { border-top: 1px solid var(--brand-line); }
.th-step-label {
    font-size: 10.5px;
    font-weight: 800;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--ink-mute);
    margin-bottom: 8px;
}
.th-bar { height: 10px; border-radius: var(--radius-pill); background: var(--g-100); overflow: hidden; }
.th-bar > i {
    display: block;
    height: 100%;
    border-radius: var(--radius-pill);
    background: linear-gradient(90deg, var(--g-600), var(--g-400));
    width: var(--th-bar-w, 0);
}
.th-step-val { display: flex; align-items: baseline; justify-content: flex-end; gap: 8px; }
.th-step-num { font-size: 24px; font-weight: 700; letter-spacing: -.03em; color: var(--brand-ink); }
.th-delta { font-size: 12px; font-weight: 700; color: var(--status-blocked); min-width: 24px; text-align: right; }
.th-delta--null { color: var(--ink-mute); }
/* Ein PLUS zwischen zwei Stufen ist kein Verlust und darf nicht in der
   Warnfarbe stehen: `complete` (Leistung + Zeiten) haengt nicht an
   `activated` (Mail bestaetigt), die Reihe ist dort also nicht monoton.

   Die Farbe allein traegt das aber NICHT: gegen die Warnfarbe gemessen nur
   1,19:1 — beide unterscheiden sich im Ton, kaum in der Helligkeit. Das
   Vorzeichen im Text ist deshalb Pflicht, nicht Beigabe. */
.th-delta--plus { color: var(--status-confirmed); }

/* Hinweiskasten am Fuss eines Blocks — trägt eine Folgerung, keine Deko. */
.th-bento-note {
    display: flex;
    gap: 8px;
    margin: 16px 0 0;
    padding: 12px;
    background: var(--status-blocked-bg);
    border-radius: var(--radius-btn);
    font-size: 12.5px;
    font-weight: 600;
    color: var(--status-blocked-ink);
}

/* Zwei Geld-Zellen nebeneinander. */
.th-money-pair {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px;
    margin: 16px 0 12px;
}
.th-money-cell {
    border: 1px solid var(--brand-line);
    /* Wie die Notiz im selben Block: eine Flaeche IN einer Karte, nicht
       selbst eine Karte. */
    border-radius: var(--radius-btn);
    padding: 12px;
    background: var(--g-50);
}
.th-money-cell--jetzt { border-color: var(--g-400); }
.th-money-num {
    font-size: 26px;
    font-weight: 700;
    letter-spacing: -.035em;
    line-height: 1.05;
    margin-top: 8px;
    color: var(--brand-ink);
}
.th-money-cell--jetzt .th-money-num { color: var(--g-700); }
.th-money-foot { font-size: 12px; font-weight: 700; color: var(--ink-mute); margin-top: 6px; }

/* Segmentbalken: der Bestand nach Zustand, in einer Zeile. */
.th-split {
    display: flex;
    gap: 3px;
    height: 14px;
    border-radius: var(--radius-pill);
    overflow: hidden;
    background: var(--g-100);
}
.th-split > span { display: block; height: 100%; width: var(--th-seg-w, 0); }
/* Jedes Segment muss sich von der Spur (--g-100) abheben: Mindestwert 3:1
   wie fuer grafische Objekte. Gemessen wurden hier schon 1,0:1 (Segment in
   Spurfarbe), 1,18:1 und 1,57:1 — jedes Mal verschluckte der Balken sichtbar
   Bestand, den die Kachel darueber nannte. `--andere` und `--offen` liegen
   bewusst ausserhalb der Gruenskala: Sonderfaelle sollen auffallen. */
.th-seg--gratis { background: var(--g-500); }
.th-seg--founder { background: var(--g-700); }
.th-seg--standard { background: var(--g-900); }
.th-seg--andere { background: var(--status-completed); }
.th-seg--offen { background: var(--ink-mute); }
.th-seg--gesperrt { background: var(--status-blocked); }
.th-split-key { display: flex; flex-wrap: wrap; gap: 8px 16px; margin: 12px 0 16px; }
.th-split-key span {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    font-weight: 600;
    color: var(--ink-mute);
}
.th-key-box { width: 14px; height: 9px; border-radius: 2px; }

/* Zustandszeile: Punkt, Name mit Zusatz, rechts Betrag und Zeitraum. */
.th-tier {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    gap: 12px;
    padding: 12px 0;
}
.th-tier + .th-tier { border-top: 1px solid var(--brand-line); }
.th-dot { width: 11px; height: 11px; border-radius: 3px; }
.th-tier-name { font-size: 14.5px; font-weight: 700; color: var(--brand-ink); }
.th-tier-sub { font-size: 12.5px; font-weight: 500; color: var(--ink-mute); margin-top: 2px; }
.th-tier-sums { text-align: right; }
.th-tier-now { font-size: 15px; font-weight: 700; letter-spacing: -.02em; color: var(--brand-ink); }
.th-tier-now--keine { color: var(--ink-mute); }
.th-tier-foot { font-size: 12px; font-weight: 600; color: var(--ink-mute); margin-top: 2px; }

/* Verwalten-Zeile: Icon-Quadrat, Titel mit Unterzeile, rechts Zahl oder Pfeil. */
.th-row {
    display: grid;
    grid-template-columns: 38px minmax(0, 1fr) auto;
    align-items: center;
    gap: 12px;
    padding: 12px 8px;
    margin: 0 -8px;
    border-radius: var(--radius-btn);
    text-decoration: none;
    color: inherit;
    min-height: var(--touch-min);
    transition: background .14s ease;
}
.th-row:hover { background: var(--g-50); }
.th-row + .th-row { box-shadow: inset 0 1px 0 var(--brand-line); }
.th-row-icon {
    width: 38px;
    height: 38px;
    border-radius: var(--radius-chip);
    background: var(--g-100);
    display: grid;
    place-items: center;
    color: var(--g-700);
}
.th-row-title { font-size: 15px; font-weight: 700; letter-spacing: -.01em; color: var(--brand-ink); }
.th-row-sub { font-size: 12.5px; font-weight: 500; color: var(--ink-mute); margin-top: 2px; }
.th-count {
    font-size: 13px;
    font-weight: 700;
    color: #fbfdfb;
    background: var(--g-700);
    border-radius: var(--radius-pill);
    padding: 3px 10px;
    font-variant-numeric: tabular-nums;
}
/* Der gedaempfte Ton auf dem hellgruenen Feld ergab gemessen nur 3,68:1; bei
   13 px fett gilt 4,5:1 (Grosstext beginnt erst bei 18,66 px). Ausgerechnet
   die Null, die "hier ist nichts offen" sagt, war die schlechtest lesbare
   Zahl der Seite. --ink-soft haelt 7,59:1 und bleibt trotzdem zweitrangig. */
.th-count--null { color: var(--ink-soft); background: var(--g-100); }
/* In zwei der sechs Zeilen ist der Pfeil das einzige, was rechts steht — dann
   ist er kein Zierrat und haelt 3:1. Gemessen: --g-300 kam auf 1,84:1,
   --g-400 auf 2,78:1, --g-500 auf 4,34:1. */
.th-chev { color: var(--g-500); }

/* Breiter Abschluss-Knopf des Verwalten-Blocks. */
.th-bento-cta {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    width: 100%;
    margin-top: 20px;
    padding: 16px;
    background: var(--brand-gradient);
    color: #fbfdfb;
    border-radius: var(--radius-btn);
    font-size: 15px;
    font-weight: 800;
    text-decoration: none;
    box-shadow: var(--shadow-btn);
    min-height: var(--touch-min);
}
.th-bento-cta:hover { background: var(--brand-gradient-hover); transform: translateY(-2px); }

@media (min-width: 768px) {
    /* `--half` bleibt bei zwei Spalten, es sind hier nur andere zwei von
       vier — deshalb steht es nicht noch einmal. */
    .th-bento { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; }
    .th-bento-tile--full { grid-column: span 4; }
}

@media (prefers-reduced-motion: reduce) {
    a.th-bento-tile:hover, .th-bento-cta:hover { transform: none; }
}
