/* Base styles shared across every authenticated page. Served static
 * from the jar at /static/app.css; see SharedUI.page which loads it
 * via <link rel="stylesheet">. Previously lived as a Kotlin raw-string
 * under SharedUI.BASE_STYLES.
 *
 * Organisation:
 *   1. Reset + body
 *   2. Header (desktop + mobile drawer)
 *   3. Mobile media query
 *   4. Footer
 *   5. Typography + cards + badges
 *   6. Form controls + buttons
 *   7. Tables + sticky-scroll
 *   8. Team overview specifics
 *   9. Layout helpers + result messages
 *  10. Admin hub styles
 */

* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #f8fafc; color: #334155; }
/* Use the full main-column width — no horizontal whitespace beside
 * cards/charts. Per user feedback 2026-05-02 nachmittags: the old
 * max-width: 1400px left empty margins on wide displays and made the
 * Aktivität event-row width depend on which event-type was selected.
 * Cards bring their own padding, so this is just the page-level gutter.
 */
.page { padding: 16px; box-sizing: border-box; width: 100%; }

/* Sidebar layout (Variante C). Two-column on desktop:
 *   <aside id="ss-sidebar"> (250px) | <div id="ss-main"> (rest)
 * On mobile (<900px) the sidebar becomes an off-canvas overlay that
 * slides in from the left when body.sidebar-open is set; the
 * <div id="ss-backdrop"> dims the main column and dismisses the
 * sidebar on tap. The hamburger button lives in the top bar.
 */

:root {
    --ss-sidebar-width: 250px;
    /* Welle CLS-Re (2026-05-17): App-Shell-Dimensions als CSS-Variables.
       Hardcoded 56px / 62px (Topbar / Footer) wurden 1:1 in
       calc()-Expressions kopiert, was beim Resize / Mobile-Breakpoint
       leicht aus dem Sync lief. Variables zentralisieren das + erlauben
       per-Breakpoint-Override (siehe Mobile-Block). */
    --topbar-h: 56px;
    --footer-h: 62px;
}

/* Body ist die App-Shell: flex row mit Sidebar + Content-Wrap. min-height
   prefers 100dvh wo verfügbar (Mobile-iOS — die addressbar-Subtraktion
   beim Scrollen sonst CLS-Trigger), fallback auf 100vh. */
body { display: flex; min-height: 100vh; min-height: 100dvh; }
body.no-chrome { display: block; }  /* login + standalone pages opt out */

/* Sidebar (desktop: static left column). */
.ss-sidebar {
    width: var(--ss-sidebar-width); flex-shrink: 0;
    background: #1e293b; color: #fff;
    position: sticky; top: 0; height: 100vh;
    overflow-y: auto; -webkit-overflow-scrolling: touch;
    display: flex; flex-direction: column;
    padding: 18px 0 24px;
    z-index: 50;
}
.ss-sidebar.admin-mode { background: #7f1d1d; }
.ss-brand {
    display: block;
    color: #fff; text-decoration: none; font-weight: 700;
    font-size: 1.1em;
    padding: 0 20px 4px;
}
.ss-env {
    align-self: flex-start; margin: 4px 20px 12px;
    background: #fbbf24; color: #78350f;
    font-size: 0.7em; font-weight: 700;
    padding: 2px 8px; border-radius: 4px;
    text-transform: uppercase; letter-spacing: 0.05em;
}
.ss-tier { padding: 8px 0; }
.ss-tier + .ss-tier { border-top: 1px solid rgba(255,255,255,0.08); margin-top: 4px; }
.ss-tier-header {
    font-size: 0.7em; text-transform: uppercase; letter-spacing: 0.08em;
    color: #64748b; padding: 8px 20px 4px; margin: 0;
    font-weight: 700;
}
.ss-tier-leadership .ss-tier-header { color: #93c5fd; }
.ss-tier-admin .ss-tier-header { color: #fca5a5; }
.ss-sidebar.admin-mode .ss-tier-leadership .ss-tier-header { color: #bfdbfe; }
.ss-sidebar.admin-mode .ss-tier-admin .ss-tier-header { color: #fecaca; }
.ss-tier-header-icon { font-size: 0.85em; opacity: 0.6; vertical-align: -1px; margin-left: 2px; }
.ss-nav { display: flex; flex-direction: column; }
.ss-nav a {
    color: #cbd5e1; text-decoration: none;
    padding: 8px 20px; font-size: 0.92em;
    border-left: 3px solid transparent;
}
.ss-nav a:hover { color: #fff; background: rgba(255,255,255,0.06); }
.ss-nav a.active {
    color: #fff; background: rgba(255,255,255,0.1);
    border-left-color: #60a5fa;
}
.ss-tier-leadership .ss-nav a { color: #bfdbfe; }
.ss-tier-leadership .ss-nav a.active { border-left-color: #60a5fa; }
.ss-tier-admin .ss-nav a { color: #fecaca; }
.ss-tier-admin .ss-nav a.active { border-left-color: #f87171; }

/* Main column. min-width:0 lets flex children clamp instead of forcing
 * the row wider. Without this, a wide table inside .page would push
 * the main column past the viewport and create a horizontal scrollbar
 * on the body.
 *
 * Welle CLS-Re (2026-05-17): min-height füllt das viewport, damit der Footer
 * auch auf kurzen Pages am Bottom kleben bleibt — sonst springt er beim
 * async-loaded Content nach oben/unten und triggert CLS. */
.ss-main {
    flex: 1; min-width: 0;
    display: flex; flex-direction: column;
    background: #f8fafc;
    min-height: 100vh;
    min-height: 100dvh;
}

/* Page-content-area: reserviert per min-height den vollen sichtbaren
   Bereich zwischen Topbar und Footer. Cards die initial leer sind
   bleiben damit am Anfang der Page; Footer wird nicht hochgezogen. */
.ss-main > .page {
    flex: 1 0 auto;
    min-height: calc(100vh - var(--topbar-h) - var(--footer-h));
    min-height: calc(100dvh - var(--topbar-h) - var(--footer-h));
}

/* Top bar (desktop: above main column).
   Welle CLS-Re (2026-05-17): height = var(--topbar-h) (default 56px,
   Mobile-Override 52px) damit calc()-Expressions zur main-Height
   konsistent bleiben. */
.ss-topbar {
    background: #fff; border-bottom: 1px solid #e2e8f0;
    padding: 0 16px;
    display: flex; align-items: center;
    height: var(--topbar-h);
    position: sticky; top: 0; z-index: 30;
}
.ss-topbar-spacer { flex: 1; }
.ss-topbar-right { display: flex; align-items: center; gap: 10px; }

.ss-hamburger {
    display: none;  /* mobile-only; revealed in the @media block */
    background: transparent; color: #1e293b; border: 1px solid #e2e8f0;
    width: 40px; height: 40px; border-radius: 8px;
    cursor: pointer; font-size: 1.3em; line-height: 1;
    padding: 0; margin-right: 8px;
}
.ss-hamburger:hover { background: #f1f5f9; }
.ss-hamburger:focus { outline: 2px solid #60a5fa; outline-offset: 2px; }

.ss-tenant {
    background: #2563eb; color: #fff; border: 1px solid #3b82f6;
    border-radius: 6px; padding: 6px 10px;
    font-size: 0.85em; font-weight: 600; cursor: pointer; height: 32px;
}
.ss-tenant:focus { outline: none; border-color: #60a5fa; box-shadow: 0 0 0 2px rgba(96,165,250,0.3); }
.ss-tenant option { background: #1e293b; color: #e2e8f0; }
.ss-tenant.ss-tenant-invalid {
    border-color: #dc2626; background: #fef2f2; color: #991b1b; font-weight: 700;
}
.ss-tenant-name {
    color: #475569; font-size: 0.9em; font-weight: 600;
    background: #f1f5f9; border-radius: 6px; padding: 4px 10px;
}

.ss-icon-btn {
    background: transparent; color: #475569;
    border: 1px solid transparent;
    width: 36px; height: 36px; border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 1.1em; line-height: 1;
    cursor: pointer; padding: 0;
}
.ss-icon-btn:hover { background: #f1f5f9; color: #1e293b; }
.ss-icon-btn:focus { outline: 2px solid #60a5fa; outline-offset: 2px; }

/* Welle 1B.2: quick-upload <dialog>. Native dialog ships with no
   padding/border-radius by default; this matches the rest of the
   card/modal-style on the app. ::backdrop dims the page behind it
   so the focus is unambiguous. */
.ss-quick-upload-dialog {
    width: 380px;
    max-width: 92vw;
    border: 1px solid #cbd5e1;
    border-radius: 8px;
    padding: 20px;
    background: #fff;
    color: #1e293b;
    box-shadow: 0 10px 30px rgba(15, 23, 42, 0.2);
}
.ss-quick-upload-dialog::backdrop {
    background: rgba(15, 23, 42, 0.35);
}
.ss-quick-upload-dialog select,
.ss-quick-upload-dialog input[type="file"] {
    box-sizing: border-box;
}

.ss-avatar {
    display: inline-flex; align-items: center; justify-content: center;
    width: 36px; height: 36px; flex-shrink: 0;
    aspect-ratio: 1;  /* prevents the "Ei"-shape when the topbar flex row
                         squeezes the right group on first render before
                         layout stabilises */
    border-radius: 50%;
    color: #fff; text-decoration: none;
    font-size: 0.78em; font-weight: 700; letter-spacing: 0.02em;
    text-shadow: 0 1px 1px rgba(0,0,0,0.2);
    user-select: none;
}
.ss-avatar:hover { filter: brightness(1.05); }
.ss-avatar:focus { outline: 2px solid #60a5fa; outline-offset: 2px; }

.ss-back-admin {
    background: #f59e0b; color: #78350f; border: none;
    padding: 6px 12px; border-radius: 6px; cursor: pointer;
    font-size: 0.85em; font-weight: 600; height: 32px; white-space: nowrap;
}
.ss-back-admin:hover { background: #fbbf24; }

/* Backdrop is the dimmed overlay that appears behind the sidebar on
 * mobile. Always in the DOM (so transitions work) but invisible by
 * default. */
#ss-backdrop {
    position: fixed; inset: 0;
    background: rgba(15, 23, 42, 0.45);
    opacity: 0; pointer-events: none;
    transition: opacity 0.22s ease-out;
    z-index: 150;
}
body.sidebar-open #ss-backdrop { opacity: 1; pointer-events: auto; }

/* Mobile breakpoint. 1024px so phones in landscape (up to 932px on
 * large iPhones) still get the off-canvas, and small tablets in
 * portrait (768-1024px) too. iPads in landscape (≥1180px) keep the
 * static sidebar. Earlier 899px left phone-landscape on the desktop
 * layout, which the user reported as "Menü bleibt aufgeklappt wie auf
 * Desktop" 2026-05-02 nachmittags. */
@media (max-width: 1024px) {
    /* Welle CLS-Re (2026-05-17): Mobile-Topbar ist 4px schmaler (52px statt
       56px); --topbar-h override damit calc(100vh - var(--topbar-h) -
       var(--footer-h)) konsistent bleibt. */
    :root { --topbar-h: 52px; }
    body { display: block; }
    .ss-sidebar {
        position: fixed; top: 0; left: 0; bottom: 0;
        width: 280px; height: 100vh;
        transform: translateX(-100%);
        transition: transform 0.22s ease-out;
        box-shadow: 4px 0 16px rgba(0,0,0,0.25);
        z-index: 200;
    }
    body.sidebar-open .ss-sidebar { transform: translateX(0); }
    body.sidebar-open { overflow: hidden; }  /* lock background scroll */
    .ss-main { display: block; min-height: 100vh; min-height: 100dvh; }
    .ss-topbar { height: var(--topbar-h); }
    .ss-hamburger { display: inline-flex; align-items: center; justify-content: center; }
    .ss-topbar { padding: 0 12px; }
    .ss-topbar-right { gap: 6px; }
    /* On phones the tenant selector eats too much space; collapse to a
     * smaller pill but keep it visible (multi-tenant assistants still
     * need a way to switch). */
    .ss-tenant { font-size: 0.78em; padding: 4px 8px; height: 30px; max-width: 110px; }
    .ss-tenant-name { font-size: 0.78em; padding: 3px 8px; max-width: 110px;
        white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    .ss-back-admin { padding: 4px 8px; font-size: 0.75em; height: 30px; }
    .page { padding: 12px 8px; }
    .card { padding: 14px; border-radius: 10px; }
    /* Single-column forms on phone widths. Page-local CSS can override. */
    .row { grid-template-columns: 1fr; }
    /* 16px min prevents iOS Safari zoom-on-focus + roomier tap target. */
    input, select, textarea { font-size: 16px; padding: 10px 12px; min-height: 44px; }
    .page > button, .card > button, form > button, .page .login-btn { padding: 12px 18px; font-size: 1em; }
    th, td { padding: 6px 8px; font-size: 0.82em; min-width: 60px; }
    /* Note columns wrap, everything else nowrap is set per-page. The
     * 60px min-width above prevents date / time / value cells from
     * collapsing below readability when the table horizontally scrolls
     * inside a .sticky-scroll wrapper. */
    td.note-col, th.note-col { min-width: 120px; }
    th.rotated { height: 70px !important; font-size: 0.75em !important; }
    /* Sticky-thead-Offset folgt der --topbar-h-Variable automatisch
     * (siehe Outer-Block) — auf Mobile schrumpft --topbar-h auf 52px,
     * tr:1 und tr:2 wandern dann automatisch mit. Frueher hatte hier
     * eine eigene Override-Regel (`table thead th { top: var(--topbar-h); }`)
     * gestanden, die durch die Outer-Regeln (`tr:first-child th` /
     * `tr:nth-child(2) th`) wegen Spezifitaet eh nie gewirkt hat —
     * jetzt redundant geloescht (Welle C 2026-05-17 deepnight).
     *
     * .sticky-scroll-h-Override bleibt: dort ist der Container der
     * scrolling ancestor, nicht das Window — top:0 + top:36px ist
     * Container-relativ. */
    .sticky-scroll-h table thead tr:first-child th { top: 0; }
    .sticky-scroll-h table thead tr:nth-child(2) th { top: 36px; }
}

/* Welle B (2026-05-17 deepnight): KPI-Info-Tooltip generisch — kleiner
 * "i"-Kreis als Info-Button auf einer KPI-Karte, Hover/Focus zeigt
 * einen Slate-Tooltip mit Tabelle/Prose. Genutzt auf /uebersicht
 * (Stundenkonto-Karte: Konto-Berechnung) + /betrieb (Aktive AKs:
 * Namen-Liste). Vorher lebte das nur in OverviewRoutes lokal — jetzt
 * zentral, damit weitere Karten dieselbe Optik kriegen können ohne
 * CSS-Drift. */
.kpi-info-trigger {
    position: relative; display: inline-flex; align-items: center;
    justify-content: center;
    width: 18px; height: 18px; border-radius: 50%;
    background: #e2e8f0; color: #475569;
    /* User-Wunsch 2026-05-17 sehr-spaetabends round 2: text-transform:
     * none zwingt das "i" klein zu bleiben — auf /betrieb hatte die
     * .betrieb-card-label uppercase geerbt, womit das "i" als "I" (ohne
     * Punkt) gerendert wurde. Die serif+italic-Variante aus runde 1
     * bleibt als typografisch klares "Info"-Glyph. */
    text-transform: none;
    font-family: 'Times New Roman', Georgia, serif;
    font-style: italic;
    font-size: 12px; font-weight: 700; line-height: 1;
    cursor: help; user-select: none;
    outline: none; transition: background 0.15s, color 0.15s;
}
.kpi-info-trigger:hover, .kpi-info-trigger:focus,
.kpi-info-trigger:focus-within { background: #2563eb; color: #fff; }
.kpi-info-tooltip {
    display: none; position: absolute; bottom: calc(100% + 8px); left: 0;
    z-index: 1000; min-width: 280px; max-width: min(360px, 92vw);
    background: #1e293b; color: #f8fafc;
    padding: 12px 14px; border-radius: 8px;
    box-shadow: 0 6px 20px rgba(15, 23, 42, 0.18);
    font-size: 0.92em; line-height: 1.45; text-align: left;
    font-weight: 400; cursor: default;
    white-space: normal;
    /* Erbt sonst text-transform:uppercase aus dem Label-Vorfahren
     * (z.B. .betrieb-card-label), was den Tooltip-Text komplett gross-
     * geschrieben darstellt. */
    text-transform: none;
    font-style: normal;
}
.kpi-info-tooltip::after {
    content: ''; position: absolute; top: 100%; left: 6px;
    border: 6px solid transparent; border-top-color: #1e293b;
}
.kpi-info-trigger:hover .kpi-info-tooltip,
.kpi-info-trigger:focus .kpi-info-tooltip,
.kpi-info-trigger:focus-within .kpi-info-tooltip { display: block; }
.kpi-info-tooltip table { width: 100%; border-collapse: collapse;
    font-variant-numeric: tabular-nums; }
.kpi-info-tooltip td { padding: 3px 0; }
.kpi-info-tooltip td.label { color: #cbd5e1; padding-right: 14px; }
.kpi-info-tooltip td.val { text-align: right; color: #f8fafc; }
.kpi-info-tooltip tr.sum td { border-top: 1px solid #475569;
    padding-top: 6px; font-weight: 600; }
.kpi-info-tooltip tr.sum td.label { color: #f8fafc; }
.kpi-info-hint { margin: 10px 0 0; padding-top: 8px;
    border-top: 1px solid #334155;
    font-size: 0.92em; color: #cbd5e1; }
@media (max-width: 600px) {
    .kpi-info-tooltip { left: auto; right: 0; }
    .kpi-info-tooltip::after { left: auto; right: 6px; }
}

/* App footer; pinned to the bottom of the main column. Carries the
 * build hash + buildtime + policy links. Quiet by default.
 *
 * Welle CLS-Re (2026-05-17): flex-shrink:0 + explizite min-height stabilisiert
 * den Footer beim Async-Load. Höhe = --footer-h (siehe :root). */
.app-footer {
    padding: 16px 24px 28px;
    display: flex; gap: 16px;
    font-size: 0.8em; color: #94a3b8;
    flex-wrap: wrap; align-items: center;
    border-top: 1px solid #e2e8f0;
    background: #fff;
    flex: 0 0 auto;
    min-height: var(--footer-h);
    box-sizing: border-box;
}
.app-footer a { color: #64748b; text-decoration: none; }
.app-footer a:hover { color: #2563eb; }
.app-footer .ss-build-meta {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.95em;
    color: #94a3b8;
}
.app-footer .ss-build-meta-sep { color: #cbd5e1; margin: 0 4px; }

/* Welle CLS-Re (2026-05-17) — Per-Card min-heights für die P0-Pages.
   Halten den Card-Slot vor dem async API-Load aufgespannt; sonst
   springt der Layout-Footer beim Replace-Content nach unten (CLS).
   Werte als Defaults; pro Page-Card per Inline-Style override-bar. */
#hours-self-stats-card { min-height: 230px; }
#payroll-self-card { min-height: 172px; }
#balance-chart-card { min-height: 320px; }
#my-hours-container { min-height: 480px; }
#episodes-timeline-card { min-height: 555px; }

/* V14: family-helper badge — small inline marker rendered next to a
   user's display name in any team listing. Tooltip carries the
   semantic ("Familienmitglied"). Diskret aber durch das Family-Icon
   sofort erkennbar. */
.ss-participation-badge {
    display: inline-block;
    font-size: 0.95em;
    margin-right: 2px;
    cursor: help;
    user-select: none;
}

/* Common */
h1 { color: #1e293b; margin-bottom: 8px; }
h2 { color: #475569; margin: 24px 0 12px; font-size: 1.1em; }
/* Layout-Stability (Welle B, 2026-05-16): min-height auf .card als
   Floor reduziert Cumulative-Layout-Shift wenn async-Daten in einen
   bisher leeren Container reinpoppen. Inline `style="height: ..."` auf
   einzelnen Cards überschreibt diesen Floor weiterhin (höhere CSS-
   Spezifität); der Floor gilt nur für Cards die KEINE explizite Höhe
   haben (was der Standardfall ist). */
.card { background: #fff; border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px; margin-bottom: 16px; min-height: 80px; }
/* .kpi-card + .kpi-card-link konsolidiert in der Welle-E4-Region weiter unten. */
/* Skeleton-Loader (Welle B, 2026-05-16): graue Pulse-Box statt
   "Laden..."-Text. Verwendung: <div class="skeleton" style="height:60px;
   margin-bottom:8px;"></div>. Pages reservieren damit die Endgröße der
   Card und tauschen den Skeleton beim Final-Render durch echten Content
   aus. */
.skeleton {
    background: linear-gradient(90deg, #f1f5f9 0%, #e2e8f0 50%, #f1f5f9 100%);
    background-size: 200% 100%;
    animation: skeleton-pulse 1.4s ease-in-out infinite;
    border-radius: 4px;
}
@keyframes skeleton-pulse {
    0% { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}
.badge { display: inline-block; padding: 1px 8px; border-radius: 10px; font-size: 0.8em; font-weight: 600; }
.badge-admin { background: #dbeafe; color: #1e40af; }
.badge-assistant { background: #f0fdf4; color: #166534; }
.badge-team_lead { background: #fef3c7; color: #92400e; }
.badge-hr_readonly { background: #f1f5f9; color: #475569; }
.badge-platform_admin { background: #e0e7ff; color: #3730a3; }
.badge-work { background: #d1fae5; color: #065f46; }
.badge-sick { background: #fee2e2; color: #991b1b; }
.badge-sick_no_note { background: #fef3c7; color: #92400e; }
.badge-cancelled_by_assistant { background: #ffedd5; color: #9a3412; }
.badge-released_from_work { background: #dbeafe; color: #1e40af; }
.badge-no_show { background: #fce7f3; color: #9d174d; }
.badge-vacation { background: #dbeafe; color: #1e40af; }
.badge-training { background: #fef3c7; color: #92400e; }
.badge-on_call { background: #e0e7ff; color: #3730a3; }
.badge-holiday { background: #f3e8ff; color: #7c3aed; }
.badge-weekend { background: #fef3c7; color: #92400e; }
/* Plan v3 Phase C (2026-05-13): the Multi-Contract shorthandle is
   rendered as plain "(Kürzel)" text directly after the user name now,
   not as a coloured pill. Free-case input (user keeps their typed
   capitalisation), no .ss-shorthandle-pill class anymore. */
.shift-weekend { background: #fffbeb; }
.shift-holiday { background: #faf5ff; }
/* "Läuft gerade" — voller blauer Rahmen für die zwei sichtbaren Kanten
 * der Tabellenzeile (Inner-Borders kollabieren bei <tr>-Borders sonst).
 * Plus eine Inline-Badge ".shift-active-label" im Notiz-TD, weil <tr>
 * mit ::after-Pseudo-Element nicht zuverlässig rendert (display:table-row).
 * UI-Polish 2026-05-13. */
.shift-current {
    background: #eff6ff;
    box-shadow: inset 0 2px 0 #2563eb, inset 0 -2px 0 #2563eb;
}
.shift-current > td:first-child {
    box-shadow: inset 2px 0 0 #2563eb;
}
.shift-current > td:last-child {
    box-shadow: inset -2px 0 0 #2563eb;
}
/* Phase A2 (2026-05-14): "läuft gerade" / "nächste Schicht" badges
 * moved out of the notes cell on the right (where they competed with
 * actual shift notes) into a dedicated narrow column on the LEFT —
 * a hochkant ("vertical") label that signals state at the first
 * possible reading position. The .shift-state-col is the column
 * itself (mostly empty), .shift-state-label is the vertical-rl pill,
 * and .shift-state-active / .shift-state-next pick its colour. */
.shift-state-col {
    width: 28px;
    padding: 0;
    vertical-align: top;
    text-align: center;
    /* U5 Iteration 7 (2026-05-16): position:relative bleibt — es wird
     * nicht mehr als containing-block für einen absolute wrap genutzt
     * (Iteration 5+6), sondern damit der :first-child-Inset-Shadow von
     * .shift-current weiterhin funktioniert. */
    position: relative;
}
.shift-state-wrap {
    /* U5 Iteration 7 (2026-05-16): zurück zum normal Flow.
     *
     * Regression-Report: das rotierte Label braucht ~70px Höhe
     * (vertical-rl + nowrap), aber Iteration 5+6 hatten den wrap auf
     * `position:absolute; inset:0` — out of flow, td-content-box
     * bleibt 0, tr-Höhe wird von den horizontalen Cells (~38px)
     * diktiert, Label wird oben/unten abgeschnitten. JS-set td-height
     * aus Iteration 6 löste das nur scheinbar (setzte die td-height,
     * aber die anderen Cells in der tr blieben klein und das Label
     * stand mittig in einem zu kleinen Visible-Bereich).
     *
     * Fix: position:relative (im Flow) + flex. Das wrap nimmt
     * intrinsisch die Label-Höhe ein (~70px) und drückt die td-Höhe
     * (und damit die tr-Höhe) hoch. Bei past-rowspan setzt JS
     * `wrap.style.minHeight = rowspan-sum + 'px'` (siehe
     * SS.syncStateColHeights), damit das wrap auch dort die volle
     * rowspan-Höhe einnimmt — die Label-intrinsische 70px wären
     * sonst zu klein für N Rows.
     *
     * align-items:stretch (flex default) sorgt dafür dass das innere
     * .shift-state-label die volle wrap-Höhe einnimmt. */
    position: relative;
    display: flex;
    padding: 2px;
    box-sizing: border-box;
}
/* 2026-05-16: das gelaufen-Label togglet die Past-Shifts beim Klick
 * (SS.togglePastFromLabel). Cursor + Hover-Hint signalisieren die
 * Klickbarkeit; current/next/absent-now wraps sind nicht clickbar
 * und kriegen die Klasse nicht. */
.shift-state-wrap-clickable {
    cursor: pointer;
}
.shift-state-wrap-clickable:hover .shift-state-label {
    filter: brightness(0.92);
}
.shift-state-label {
    /* flex:1 — das Label streckt sich auf die volle Wrap-Höhe (= volle
     * effektive Cell-Höhe inkl. rowspan). writing-mode + rotate
     * machen es hochkant; text-align:end im rotierten Mode bringt den
     * Text-Anfang ans obere Pixel. align-self:stretch ist redundant
     * (default in flex), aber explizit damit zukünftige flex-Overrides
     * den vertikalen Stretch nicht versehentlich abschalten. */
    flex: 1;
    align-self: stretch;
    box-sizing: border-box;
    writing-mode: vertical-rl;
    transform: rotate(180deg);
    text-align: end;
    padding: 4px 3px;
    font-size: 0.7em;
    font-weight: 600;
    letter-spacing: 0.02em;
    border-radius: 3px;
    white-space: nowrap;
}
/* U4 (2026-05-15): diagonal-Schraffierung über die ganze Zeile signa-
 * lisiert auf einen Blick "diese Schicht ist ein Ausfall" — auch wenn
 * der Operator das hochkant-Pill rechts nicht scannt. User-Wunsch:
 * stärkere Signalwirkung als nur die "Ausfall jetzt"-Pill bei
 * laufenden Krank-Schichten. Slate-rot-Akzent dezent damit Wochenende-
 * und Holiday-Hintergründe nicht überschrieben wirken (background-
 * IMAGE statt -color → layering ohne paint-collision). */
tr.shift-row-absence > td {
    background-image: repeating-linear-gradient(
        135deg,
        rgba(220, 38, 38, 0.07) 0,
        rgba(220, 38, 38, 0.07) 5px,
        transparent 5px,
        transparent 10px
    );
}
.shift-state-active {
    background: #2563eb;
    color: #fff;
}
/* U3 (2026-05-15): laufende Absence-Schichten (sick / sick_no_note /
 * no_show / cancelled_by_assistant / vacation) sind zwar zeitlich
 * "current", aber nicht echte Aktivität — der Operator soll auf den
 * ersten Blick sehen welche Schicht gerade WIRKLICH läuft und welche
 * gerade als Ausfall im Plan steht. Slate-Hintergrund + roter Border
 * trennt visuell von der blauen "läuft gerade"-Pill, ohne als KPI-
 * Alarm zu schreien (das wäre unangemessen — Krankheit ist nichts
 * Negatives, nur ein anderer Status). */
.shift-state-absent-now {
    background: #f1f5f9;
    color: #991b1b;
    border: 1px solid #fca5a5;
}
.shift-state-next {
    background: #fff;
    color: #2563eb;
    border: 1px dashed #2563eb;
}
/* Phase A2/C (2026-05-14 abends): past-Schichten bekommen jetzt auch
 * ein hochkant-Label damit die Mini-Spalte konsistent durchlesbar ist.
 * Gedeckte Slate-Optik mit dashed-border passt zur abgesofteten
 * past-Optik (opacity 0.60 + italic auf den td's). */
.shift-state-past {
    background: #f8fafc;
    color: #64748b;
    border: 1px dashed #94a3b8;
}
/* "Als nächstes" row-frame — gestrichelter Rahmen + dezenter Badge im
 * selben Blau-Akzent. Border-style:dashed direkt auf <tr> rendert nicht,
 * daher box-shadow-Pattern wie shift-current, hier mit zweifarbigem
 * Inset für gepunkteten Eindruck. UI-Polish 2026-05-13. */
.shift-next {
    background: rgba(239, 246, 255, 0.4);
    box-shadow:
        inset 0 1px 0 #93c5fd,
        inset 0 2px 0 #2563eb,
        inset 0 -1px 0 #93c5fd,
        inset 0 -2px 0 #2563eb;
}
.shift-next > td:first-child {
    box-shadow: inset 1px 0 0 #93c5fd, inset 2px 0 0 #2563eb;
}
.shift-next > td:last-child {
    box-shadow: inset -1px 0 0 #93c5fd, inset -2px 0 0 #2563eb;
}
/* Trennlinie zwischen gelaufenen und geplanten Schichten im Dienstplan.
 * "▼ ab hier geplant" zwischen letzter past- und erster non-past-Schicht
 * (current zählt als Anfang der "geplant"-Phase). Render als <tr> mit
 * single <td colspan=N>. Verwendet auch von /stundenkonten als
 * .forecast-divider für "▼ ab hier Prognose". UI-Polish 2026-05-13. */
.dienstplan-divider td, .forecast-divider td {
    /* Phase A2/D (2026-05-14 abends): User-Decision — divider-Text
     * linksbündig statt zentriert, plus klein padding-left damit's
     * nicht direkt am Tabellenrand klebt. Konsistent mit dem
     * past-toggle-Label darunter. */
    text-align: left;
    padding: 8px 0 4px 12px;
    color: #94a3b8;
    font-size: 0.8em;
    font-style: italic;
    /* Phase A2 (2026-05-14): User wanted the divider line to read as
     * an unambiguous cut across the whole table. Going from 2px to
     * 3px and from Slate-300 (cbd5e1) to Slate-400 (94a3b8) bumps the
     * weight enough that it survives a passing glance, while dashed
     * keeps the "temporary, here's-the-boundary" feel solid would
     * imply. The td has colspan=N so the border-top spans the full
     * table width automatically — no per-cell trickery needed. */
    border-top: 3px dashed #94a3b8;
    border-bottom: 0;
    letter-spacing: 0.04em;
    background: transparent;
}
/* Past-Schichten default eingeklappt: Toggle-Zeile oben zeigt
 * "▶ X vergangene Schichten anzeigen", Klick toggle-t die .expanded-
 * Klasse + inline style.display auf alle .past-shift-<tr>s (inkl. dem
 * dienstplan-divider, der auch nur sinn macht wenn past expanded ist).
 *
 * Phase A2/B (2026-05-14): Der initiale display:none kommt jetzt als
 * inline-style aus dem Backend (DashboardRoutes / TeamDashboardRoutes
 * render), weil sich .past-shift{display:none} bei manchen Browser-
 * Konstellationen nicht zuverlässig durchgesetzt hat. Die CSS-Regeln
 * unten bleiben als Fallback und für die visuelle Differenzierung. */
.past-shift { display: none; }
.past-shift.expanded { display: table-row; }
/* Expandierte past-Schichten werden visuell als untergeordnet
 * markiert — leicht abgesoftet (opacity 0.60), italic, plus ein
 * dezenter Slate-200-Strich an der Mini-State-Spalte ganz links als
 * Anker. So sieht der Operator beim Scrollen, dass die Zeilen unter
 * dem ▼-Toggle "gelaufene Vergangenheit" sind und der Live-Block
 * darunter beginnt. Divider-Zeilen sind ausgenommen — die rendern
 * eigene Optik via .dienstplan-divider.
 * Phase A2/D (abends): die shift-state-col ist jetzt ein rowspan-Td
 * mit dem hochkanten "gelaufen"-Label und MUSS voll lesbar bleiben —
 * der Opacity-Filter auf den Zeilen-tds darf das Label nicht
 * abdunkeln. Selektor präzisiert auf :not(.shift-state-col). */
.past-shift.expanded:not(.dienstplan-divider) > td:not(.shift-state-col) {
    opacity: 0.60;
    font-style: italic;
}
.past-shift.expanded:not(.dienstplan-divider) > td.shift-state-col {
    border-left: 3px solid #cbd5e1;
}
/* Welle 4 (2026-05-15): .past-toggle und .collapse-toggle teilen sich
 * dasselbe visuelle Pattern — kursiver hellgrauer Hintergrund, linksbündig,
 * dashed-Trennlinie unten. .past-toggle bleibt für Backwards-Compat in
 * der Vergangenheits-Sektion; .collapse-toggle ist das neue generic
 * Pattern für Multi-Activity-Sektionen und ähnliche "+N weitere
 * anzeigen"-Use-Cases.
 *
 * Zwei Varianten:
 *  - In Tabellen (<tr class="collapse-toggle"><td colspan="N">...</td></tr>)
 *    → tr-Variante: <td> trägt die styles.
 *  - Außerhalb Tabellen (<div class="collapse-toggle">...</div>)
 *    → div-Variante: das Element selbst trägt die styles.
 * Selectors decken beide Cases ohne :has-Abhängigkeit. */
.past-toggle td,
.collapse-toggle td {
    text-align: left;
    padding: 8px 0 8px 12px;
    color: #64748b;
    font-size: 0.85em; font-weight: 500;
    background: #f8fafc;
    cursor: pointer;
    user-select: none;
    border-bottom: 3px dashed #94a3b8;
}
.past-toggle td:hover,
.collapse-toggle td:hover { background: #f1f5f9; color: #2563eb; }

div.collapse-toggle {
    text-align: left;
    padding: 8px 12px;
    color: #64748b;
    font-size: 0.85em; font-weight: 500;
    background: #f8fafc;
    cursor: pointer;
    user-select: none;
    border-top: 1px dashed #cbd5e1;
    border-bottom: 1px dashed #cbd5e1;
    margin: 4px 0 16px;
    border-radius: 4px;
}
div.collapse-toggle:hover { background: #f1f5f9; color: #2563eb; }

/* Tab-Pattern (Welle 2, 2026-05-16): Multi-Activity Reiter auf Self-Pages.
 * AKs mit mehreren Tätigkeiten sehen pro Tätigkeit einen Reiter; das
 * ersetzt die alte Collapse-Toggle-Variante (Welle 4b, 2026-05-15) auf
 * Self-Pages. Aktiver Reiter trägt unten einen kräftigeren Sky-Border;
 * inaktive Panels werden via .hidden komplett aus dem Layout genommen.
 * Helper: SS.initTabGroups(rootEl, options). */
.tab-group { display: flex; flex-direction: column; gap: 0.75rem; }
.tab-headers {
    display: flex;
    gap: 0.25rem;
    border-bottom: 1px solid #e2e8f0;
    flex-wrap: wrap;
}
.tab-header {
    background: transparent;
    border: none;
    padding: 0.5rem 1rem;
    cursor: pointer;
    font-size: 0.95em;
    color: #64748b;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    border-radius: 4px 4px 0 0;
    font-family: inherit;
}
.tab-header:hover { color: #334155; background: #f8fafc; }
.tab-header.active {
    color: #0f172a;
    border-bottom-color: #0ea5e9;
    font-weight: 600;
}
.tab-panel.hidden { display: none; }

.shift-future { opacity: 0.55; font-style: italic; }
.shift-no-contract { background: #fef2f2; border-left: 3px solid #dc2626; }
/* Self-View only: shift belongs to a different tenant than the active
 * one. Dezent gefadet plus diagonales Hatch-Pattern als Sekundär-Signal,
 * damit auch bei kontrastreichen Schicht-Titeln klar bleibt: das ist ein
 * anderer Betrieb. Hauptaufmerksamkeit bleibt auf den aktuellen Tenant
 * (UI-Polish 2026-05-13: opacity 0.78 → 0.40 plus Hatch nach User-Test).
 * Tenant identity is shown via the .badge-tenant in the notes column. */
.shift-cross-tenant {
    opacity: 0.40;
    background: repeating-linear-gradient(
        45deg,
        #f8fafc, #f8fafc 6px,
        #f1f5f9 6px, #f1f5f9 12px
    );
}
.shift-cross-tenant td { font-style: italic; }
.badge-tenant {
    display: inline-block; padding: 2px 6px; border-radius: 3px;
    /* Slate-600 statt Cyan-700 für besseren Kontrast bei reduzierter
     * Hintergrund-Opacity der cross-tenant-Zeile (UI-Polish 2026-05-13). */
    background: #f1f5f9; color: #475569;
    font-size: 0.7em; font-weight: 600;
}
/* Self-View: two shifts of this user overlap across tenants — operative
 * conflict, prominent red regardless of which tenant. Overrides the
 * cross-tenant fade so a conflict cannot be missed. */
.shift-cross-tenant-conflict {
    background: #fef2f2; border-left: 3px solid #dc2626;
    opacity: 1; font-style: normal;
}
.shift-cross-tenant-conflict td { font-style: normal; }
.badge-conflict {
    display: inline-block; padding: 2px 6px; border-radius: 3px;
    background: #fee2e2; color: #991b1b;
    font-size: 0.7em; font-weight: 600;
}
label { display: block; font-weight: 600; margin: 10px 0 4px; font-size: 0.9em; color: #475569; }
input, select { padding: 8px 12px; border: 1px solid #cbd5e1; border-radius: 6px; font-size: 0.95em; }
input:focus, select:focus { outline: none; border-color: #2563eb; box-shadow: 0 0 0 2px rgba(37,99,235,0.15); }
button { background: #2563eb; color: #fff; border: none; padding: 10px 20px; border-radius: 6px; font-size: 0.95em; cursor: pointer; }
button:hover { background: #1d4ed8; }
button.secondary { background: #64748b; }
button.secondary:hover { background: #475569; }
button.success { background: #16a34a; }
button.success:hover { background: #15803d; }

/* Welle E1 (2026-05-18): generischer Toggle-Group-Pattern aus Reliability
 * Routes promotioniert. Segmented-button-Optik fuer Filter-Toggles
 * (z.B. "Letzte 12 Monate / Gesamte Zeit"). Wird auf /zuverlaessigkeit
 * + /team-zuverlaessigkeit verwendet, kann jederzeit auf weiteren Pages
 * wiederverwendet werden. JS-Bind via SS.bindToggleGroups in ss-app.js. */
.toggle-group { display:inline-flex; border:1px solid #e2e8f0; border-radius:6px; overflow:hidden; background:#f8fafc; }
.toggle-group button {
    background:transparent; border:0; padding:5px 11px; font-size:0.82em; color:#64748b;
    cursor:pointer;
}
.toggle-group button.active { background:#fff; color:#1e293b; font-weight:600; box-shadow:0 1px 2px rgba(15,23,42,0.08); }
.toggle-group button:hover:not(.active) { color:#1e293b; }

/* Welle E2 (2026-05-18): generischer Self-Stats-Grid-Pattern aus
 * AbsencesRoutes + ReliabilityRoutes + HoursRoutes promotioniert.
 * Drei-Spalten-Grid mit Label/Value/Sub-Tiles fuer die "Meine Werte"-
 * Karte oben auf Self-Pages. Hours erweitert lokal um die page-
 * spezifische .self-stat .detail-Subklasse (Detail-Zeile zwischen
 * value und sub) und einen .self-stats-grid margin-bottom-Override. */
.self-stats-grid { display:grid; grid-template-columns:repeat(3, 1fr); gap:12px; }
@media (max-width:600px) { .self-stats-grid { grid-template-columns:1fr; } }
.self-stat { background:#f8fafc; border:1px solid #e2e8f0; border-radius:8px; padding:14px 16px; }
.self-stat .label { font-size:0.75em; text-transform:uppercase; letter-spacing:0.04em; color:#64748b; }
.self-stat .value { font-size:1.5em; font-weight:700; color:#1e293b; margin-top:4px; font-variant-numeric:tabular-nums; }
/* value-coloring uses .val-good / .val-warn / .val-bad below */
.self-stat .sub { font-size:0.8em; color:#94a3b8; margin-top:2px; }

/* Welle E3 (2026-05-18): Reliability/Absences-Cluster nach app.css —
 * vier byte-identisch ueber AbsencesRoutes + ReliabilityRoutes +
 * TeamAbsencesRoutes + TeamReliabilityRoutes duplizierte Klassen
 * fuer die Self/Team-Reliability-Pages. AbsencesRoutes erweitert
 * lokal um eine .always-fit-badge.peers-only Variante (neutral-grau
 * wenn nur peers, nicht der Caller selbst, ein clean record haben). */
.reliability-chart-card { margin-bottom:16px; }
.reliability-back-link {
    display:inline-block; margin-bottom:8px; color:#64748b;
    font-size:0.9em; text-decoration:none;
}
.reliability-back-link:hover { color:#1e293b; text-decoration:underline; }
.always-fit-badge {
    margin-top: 12px; padding: 10px 14px; border-radius: 8px;
    background: rgba(22,163,74,0.10); border-left: 4px solid #16a34a;
    color: #166534; font-size: 0.9em; font-weight: 500;
}

/* Welle E4 (2026-05-18): /uebersicht + /team-uebersicht KPI-Grid-Layout
 * nach app.css promotioniert. Beide Files hielten den 25-Zeilen-Block
 * byte-identisch dupliziert. Konsolidiert mit den vorherigen
 * .kpi-card + .kpi-card-link min-height-Defaults (Welle CLS-Re). */
.kpi-grid { display:grid; grid-template-columns:repeat(3, 1fr); gap:12px; margin-bottom:16px; }
@media (max-width:768px) { .kpi-grid { grid-template-columns:1fr; } }
.kpi-card {
    min-height: 120px;
    position:relative;
    background:#fff; border:1px solid #e2e8f0; border-radius:12px;
    padding:16px 16px 30px;
    transition:border-color 0.15s, box-shadow 0.15s, transform 0.15s;
}
.kpi-card-link {
    display:block; min-height: 120px;
    text-decoration:none; color:inherit;
    outline:none;
}
.kpi-card-link:hover .kpi-card,
.kpi-card-link:focus-visible .kpi-card {
    border-color:#2563eb;
    box-shadow:0 4px 12px rgba(15,23,42,0.08);
    transform:translateY(-1px);
}
.kpi-card-details {
    position:absolute; right:12px; bottom:8px;
    color:#94a3b8; font-size:0.75em; font-weight:500;
    transition:color 0.15s;
}
.kpi-card-link:hover .kpi-card-details,
.kpi-card-link:focus-visible .kpi-card-details { color:#2563eb; }

/* Welle E5 (2026-05-18): Stundenkonto-Tabelle + Chart-Year-Nav-Buttons
 * nach app.css. .hours-table + Subklassen byte-identisch ueber Hours
 * Routes + TeamHoursRoutes; HoursRoutes erweitert lokal um tr.avg-row
 * Klassen (Ø-Monat-Zeile, nur Self-Pages). .team-overview-table 3 Re-
 * geln gleich. .chart-year-nav top + button byte-identisch ueber 3
 * Files (DashboardRoutes + HoursRoutes + TeamHoursRoutes). .chart-
 * heading bleibt lokal (Drift margin-top:0 in DashboardRoutes) und
 * .chart-year-nav .X-year-sync bleibt lokal (page-spezifische
 * Klassen-Naming hours-year-sync vs team-year-sync). */
.hours-table { font-size: 0.85em; }
.hours-table th { text-align: right; padding: 6px 8px; }
.hours-table td { text-align: right; padding: 6px 8px; }
.hours-table td:first-child, .hours-table th:first-child { text-align: left; }
.hours-table .balance-col { font-weight: 700; }
.team-overview-table td { padding: 8px; }
.team-overview-table th { padding: 8px; text-align: right; }
.team-overview-table th:first-child, .team-overview-table td:first-child { text-align: left; }
.chart-year-nav { display: flex; gap: 6px; align-items: center; font-size: 0.85em; }
.chart-year-nav button { padding: 2px 8px; }

/* Welle E6 (2026-05-18): Ledger-Booking-Form-Layout aus HoursRoutes +
 * TeamHoursRoutes promotioniert (byte-identisch). Eine flex-Row mit
 * gleich-breiten Spalten + min-width 120 fuer Wrap auf engen Screens. */
.ledger-form { display: flex; gap: 8px; align-items: flex-end; flex-wrap: wrap; margin-top: 12px; }
.ledger-form > div { flex: 1; min-width: 120px; }

table { width: 100%; border-collapse: collapse; font-size: 0.9em; }
th { text-align: left; padding: 8px; background: #f1f5f9; border-bottom: 2px solid #e2e8f0; }
td { padding: 8px; border-bottom: 1px solid #e2e8f0; }

/* Sticky table headers — per-<th> sticky with row-specific top values.
 *
 * Why per-<th> and not per-<thead>: Tables wrapped in .sticky-scroll
 * (most wide tables) have `overflow-x: auto`, which Chromium upgrades
 * to `overflow-y: auto` automatically — making the wrapper a vertical
 * scrolling context. A `<thead>` with `position: sticky; top: 56px`
 * inside such a wrapper sticks relative to the wrapper, not the
 * viewport, and visually appears between data rows when the wrapper's
 * own top scrolls past the viewport top. Per-<th> sticky greats the
 * same behaviour but, because the cells participate in normal table
 * layout, Chromium handles them more reliably across the wrapper
 * boundary (verified 2026-05-03 — user report "Tabellen-Header hängt
 * zwischen Zeile 1 und 2" persisted with thead-level sticky).
 *
 * Multi-row <thead> (e.g. /dienstplan "Erwirtschaftete Stunden"-grouping
 * with two <tr>s): the second row gets a higher `top` so it stacks
 * directly under the first row instead of overlapping it. 36px is the
 * standard row height with the project's 8px-padding rule above.
 *
 * z-index stays under the topbar (z:30) and the sidebar (z:50) so
 * neither gets covered. */
/* Welle C (2026-05-17 deepnight): sticky-thead-top via --topbar-h-Variable
 * (war hardcoded 56px/92px). Auf Mobile (52px Topbar) verschob sich
 * die zweite thead-Zeile sonst nicht mit, weshalb sie auf manchen
 * Pages die oberste Datenzeile verdeckte. 36px Zeilen-Offset bleibt
 * statisch — das ist die intrinsische thead-Zeilenhoehe. */
table thead tr:first-child th { position: sticky; top: var(--topbar-h); z-index: 5; background: #f1f5f9; }
table thead tr:nth-child(2) th { position: sticky; top: calc(var(--topbar-h) + 36px); z-index: 5; background: #f1f5f9; }
/* Override für Tabellen die in .sticky-scroll-h sitzen — der Wrapper
 * hat overflow-x: auto, ist damit selbst der nearest scrolling
 * ancestor für sticky-Kinder. `top: 56px` (gegenüber Window) heißt
 * dann "56px unterhalb Container-Top" — die erste Datenzeile rutscht
 * über den Header. Mit `top: 0` klebt der Header am Container-Top;
 * die Topbar (z-index 30) überdeckt ihn beim Scrollen sauber, weil
 * <th> nur z-index 5 hat.
 *
 * Multi-row <thead>: Zeile 2 bekommt `top: 36px` (= Höhe von Zeile
 * 1 mit Standard-padding 8px), damit beide Zeilen direkt
 * untereinander stehen. Vorher war es `top: 92px` (= 56px Topbar
 * + 36px Zeilenhöhe), das war der Window-Anker-Wert. */
.sticky-scroll-h table thead tr:first-child th { top: 0; }
.sticky-scroll-h table thead tr:nth-child(2) th { top: 36px; }
/* Solid border on the bottom of the LAST sticky row separates the
 * pinned header from the scrolling body when content scrolls past.
 * Without this, body cells appear to merge into the header during
 * scroll because table cells have transparent backgrounds at the
 * <tr>-container level (only individual <th>/<td> get backgrounds).
 * The user report 2026-05-03 mittags ("Header hängt zwischen Zeile 1
 * und 2") was the visible side of this — body content scrolling
 * underneath the sticky <th>s with no opaque separator. */
table thead tr:last-child th { box-shadow: 0 2px 0 #e2e8f0; }
/* Click-through over the sticky thead. Without this, Playwright's
 * scrollIntoView (which uses block:'center', not block:'start') can
 * leave a clickable row sitting underneath the rotated header column
 * even when scroll-margin-top is set on the row — block:'center'
 * ignores scroll-margin-top entirely. Allowing thead to be passed
 * through is safe today because no <th> in the codebase has its own
 * click handler (verified 2026-05-03 via grep). If a future header
 * needs to be clickable (e.g. column-sort), that specific cell must
 * opt back in with `pointer-events: auto`. */
table thead { pointer-events: none; }
table thead a, table thead button, table thead input, table thead select, table thead label { pointer-events: auto; }
/* The horizontal "sticky-col" (e.g. Datum / Monat / Assistenzkraft)
 * needs its own sticky for left-pinning while the table scrolls
 * horizontally. Higher z-index keeps the top-left corner cell on top
 * of the intersection of the two stickies. */
table thead th.sticky-col { position: sticky; left: 0; z-index: 6; }

/* Table wrapper — `.sticky-scroll` is the default, used by every
 * table on every page, so it has `overflow: visible`: this leaves
 * the body / window as the nearest scrolling ancestor for `<th>`s,
 * which is what `position: sticky; top: 56px` needs to actually
 * pin the header to the topbar bottom.
 *
 * Earlier versions used `overflow-x: auto` here as a one-stop
 * "wraps any width" affordance. That created a scroll-context per
 * wrapper and broke sticky-thead in every wrapped table. User
 * report 2026-05-03 mittags ("Header scrollt einfach mit") +
 * browser-side analysis confirmed: `position: sticky` only fires
 * relative to the nearest scrolling ancestor; with overflow-x:auto
 * on the wrapper that ancestor is the wrapper itself, which never
 * actually scrolls vertically (its height equals its content),
 * so the header just rides along.
 *
 * Tables that genuinely need horizontal scrolling (team-table with
 * rotated columns, team-overview-table with the full hours grid)
 * use `.sticky-scroll-h` below — those tables are usually short
 * enough that not having sticky-thead is acceptable.
 *
 * Tag the first <th>/<td> per row with class="sticky-col" to keep
 * the row-header column visible. For tables with multiple sticky
 * columns at different left offsets, page-local CSS adds
 * .sticky-col-2 etc. */
.sticky-scroll { overflow: visible; }

/* Wide-table variant — for tables that exceed the viewport
 * horizontally (rotated multi-column matrices, full hours grid).
 *
 * `overflow-y: clip` is the key bit: paired with `overflow-x: auto`
 * it gives us horizontal scroll without creating a vertical scroll
 * context (which is what `auto`/`hidden`/`scroll` would do). The
 * sticky-thead inside therefore engages relative to the body /
 * window — exactly like in plain `.sticky-scroll` tables — and pins
 * to the topbar bottom on scroll.
 *
 * Browser support for `overflow: clip`: Chrome 90+, Firefox 81+,
 * Safari 16+ — covers everything we deploy to. Older browsers fall
 * back to the implicit-auto behaviour and lose sticky-thead, but
 * keep the horizontal scroll. */
.sticky-scroll-h {
    overflow-x: auto;
    overflow-y: clip;
    -webkit-overflow-scrolling: touch;
    background:
        linear-gradient(to left, #fff, rgba(255,255,255,0)) right top / 28px 100% no-repeat local,
        linear-gradient(to left, rgba(15,23,42,0.12), rgba(15,23,42,0)) right top / 16px 100% no-repeat scroll;
}
.sticky-col { position: sticky; left: 0; background: #fff; z-index: 2; }
th.sticky-col { background: #f1f5f9; }
tr:hover > td.sticky-col { background: #f8fafc; }

/* Team overview specifics — used only on /team but kept here so
 * the styles aren't injected dynamically on every loadTeamOverview
 * call (and so the .rotated media-query override above actually
 * has a base definition to override). */
.team-table th.rotated { writing-mode: vertical-lr; transform: rotate(180deg); text-align: left; padding: 8px 4px; font-size: 0.8em; height: 90px; }
.team-table .sticky-col-1 { width: 28px; }
.team-table .sticky-col-2 { left: 28px; min-width: 180px; }
/* Phase A1 / Sub-Welle 3c (2026-05-13): Tätigkeit-Spalte zwischen
 * Name und Werte-Spalten. Sticky-Offset: 28px (col-1) + 180px (col-2)
 * = 208px. min-width 120px ist genug für "Karla Alltagsassistenz (ASS)". */
.team-table .sticky-col-3 { left: 208px; min-width: 120px; max-width: 200px; font-size: 0.85em; }
.row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.subtitle { color: #64748b; margin-bottom: 24px; }
.loading { color: #64748b; padding: 20px; text-align: center; }

/* Result messages */
.result { margin-top: 12px; padding: 12px; border-radius: 6px; font-family: monospace; font-size: 0.85em; white-space: pre-wrap; word-break: break-all; display: none; }
.result.ok { display: block; background: #f0fdf4; border: 1px solid #bbf7d0; color: #166534; }
.result.err { display: block; background: #fef2f2; border: 1px solid #fecaca; color: #991b1b; }

/* Tenant/user list (admin) */
.tenants-list { margin-top: 12px; }
.tenant-item { display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; border: 1px solid #e2e8f0; border-radius: 6px; margin: 4px 0; background: #fff; }
.tenant-item .name { font-weight: 600; }
.tenant-item .meta { color: #64748b; font-size: 0.85em; }
.tenant-item button { margin: 0; padding: 4px 12px; font-size: 0.8em; }
.users-list { margin-top: 8px; padding-left: 16px; }
.user-item { padding: 4px 0; font-size: 0.9em; color: #475569; display: flex; justify-content: space-between; align-items: center; }
.hidden { display: none; }

/* Import page */
.badge-match { background: #d1fae5; color: #065f46; }
.badge-skip { background: #f1f5f9; color: #64748b; }
.badge-error { background: #fee2e2; color: #991b1b; }
.badge-create { background: #dbeafe; color: #1e40af; }
.badge-existing { background: #d1fae5; color: #065f46; }
.summary { background: #f0fdf4; border: 1px solid #bbf7d0; border-radius: 8px; padding: 16px; margin: 16px 0; }
.summary.dry { background: #eff6ff; border-color: #bfdbfe; }
.cal-row { display: flex; align-items: center; gap: 8px; padding: 6px 0; border-bottom: 1px solid #f1f5f9; }
.cal-row input[type=checkbox] { width: 18px; height: 18px; }
.cal-name { flex: 1; }
.cal-assistant { color: #16a34a; font-weight: 600; }
.cal-nomatch { color: #94a3b8; font-style: italic; }
.actions { margin-top: 16px; display: flex; gap: 8px; }

/* Admin hub card links */
a.card-link { display: block; background: #fff; border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px; margin-bottom: 12px; text-decoration: none; color: inherit; transition: border-color 0.15s; }
a.card-link:hover { border-color: #2563eb; }
a.card-link h2 { font-size: 1.05em; color: #1e293b; margin-bottom: 4px; }
a.card-link p { color: #64748b; font-size: 0.9em; margin: 0; }

/* Threshold legend for ranking-bar charts — shows the colour bands
 * (e.g. < 5 % grün / 5–10 % orange / > 10 % rot). Sits next to the
 * chart heading so the reader can decode the colours without
 * guessing. */
.threshold-legend {
    display: inline-flex; flex-wrap: wrap; gap: 12px;
    font-size: 0.82em; color: #64748b;
    margin: 4px 0 10px;
}
.threshold-legend-item { display: inline-flex; align-items: center; gap: 5px; }
.threshold-swatch {
    display: inline-block; width: 12px; height: 12px;
    border-radius: 3px; border: 1px solid #cbd5e1;
}

/* Multi-user chart filter controls. Pinned next to a chart heading,
 * lets a leadership user bulk-toggle every dataset (Alle / Keine) or
 * isolate a single person via the dropdown. The default Chart.js
 * legend (one click per person) stays as the secondary path —
 * useful for fine-tuning after Alle/Keine, but not the only way. */
.chart-legend-toggle {
    display: inline-flex; align-items: center; gap: 6px;
    flex-wrap: wrap;
    font-size: 0.82em;
}
.chart-legend-toggle-label { color: #64748b; }
.chart-legend-toggle-btn {
    background: #fff; border: 1px solid #e2e8f0; border-radius: 4px;
    padding: 3px 10px; font-size: 0.82em; color: #475569;
    cursor: pointer; margin: 0; height: auto;
}
.chart-legend-toggle-btn:hover { background: #f8fafc; color: #1e293b; }
.chart-legend-toggle-only {
    border: 1px solid #e2e8f0; border-radius: 4px;
    padding: 3px 8px; font-size: 0.82em; color: #475569;
    background: #fff;
}

/* Full-width empty-state card. Used on every self-only page when the
 * caller has no data in the relevant timeframe — replaces the cluster
 * of empty KPI cards / charts with a single explanatory block. The
 * intent is that promoting somebody to team-lead never leaves them on
 * a page full of dashes; instead they see one clear card pointing at
 * the team-tier counterpart. */
.empty-state-card {
    background: #fff;
    border: 1px solid #e2e8f0;
    border-radius: 12px;
    padding: 32px 24px;
    margin: 16px 0;
    text-align: center;
}
.empty-state-card .empty-state-headline {
    color: #475569;
    font-size: 1.15em;
    font-weight: 600;
    margin: 0 0 8px;
}
.empty-state-card .empty-state-sub {
    color: #94a3b8;
    font-size: 0.95em;
    margin: 0;
    max-width: 520px;
    margin-left: auto;
    margin-right: auto;
}
.empty-state-card .empty-state-link {
    margin-top: 16px;
    font-size: 0.95em;
}
.empty-state-card .empty-state-link a {
    color: #2563eb;
    font-weight: 500;
    text-decoration: none;
}
.empty-state-card .empty-state-link a:hover { text-decoration: underline; }

/* Value-coloring utility classes — used everywhere a numeric value
 * carries a value-judgment (good / warning / bad). Three rules apply
 * across the app, captured by SS.valClass(metric, value):
 *
 *   metric "balance"  → val-bad if v<0, val-good if v>0, neutral if v=0
 *                       (Konto/Bilanz: only the sign matters)
 *   metric "ratio"    → val-bad if v>0.10, val-warn if v>0.05, val-good
 *                       otherwise (Ausfall-Quote: 5/10 % thresholds)
 *   metric "absence"  → val-bad if v>0, val-good if v=0
 *                       (Krank-/Ausfall-Stunden + Anzahl: the user
 *                        explicitly asked for green-on-zero here as a
 *                        signal that "never absent" is an achievement.)
 *
 * Bonus columns (Arbeit, Urlaub, Feiertag, Weihnachtsgeld) and pure
 * informational values (settled, totals) stay neutral — no class.
 *
 * The classes are kept intentionally generic so a page can also apply
 * them directly with a hand-rolled rule when the metric type doesn't
 * fit the three categories above.
 */
.val-good { color: #166534; }
.val-warn { color: #ea580c; }
.val-bad  { color: #991b1b; }

/* Dienstplan-Spalten — alle Tabellen die Schicht-Reihen rendern.
 * Date / num / time-Spalten kollabieren auf Min-Content
 * (`width: 1%` plus `white-space: nowrap`); damit kriegt die
 * Notiz/Tags-Spalte den gesamten Restplatz. Die Von/Bis-Triade
 * ("Von | – | Bis") wird optisch als ein Block geführt, indem Von
 * rechtsbündig und Bis linksbündig steht und die padding-Lücken
 * zwischen den drei Cells auf 2px reduziert sind — "Mi 14:00 – 16:30"
 * liest sich als gemeinsamer Lesesschritt statt drei lose Spalten.
 */
.col-shift-date,
.col-shift-num  { white-space: nowrap; width: 1%; }
.col-shift-from { white-space: nowrap; width: 1%; text-align: right;  padding-right: 2px; }
.col-shift-sep  { white-space: nowrap; width: 1%; text-align: center; padding-left: 2px; padding-right: 2px; color: #94a3b8; }
.col-shift-to   { white-space: nowrap; width: 1%; text-align: left;   padding-left: 2px; }
/* Mitarbeiter-Spalte (Team-Dienstplan o.ä.): kollabiert wie die anderen
 * col-shift-* auf Min-Content, aber capped auf 140px und ellipsed —
 * sonst zog sich die Spalte über den längsten Namen auf und fraß
 * Notiz-Platz. Body-Cells SOLLTEN ein title="<voller Name>" Attribut
 * tragen, damit der ellipsed Name on-hover einsehbar ist. */
.col-shift-name { white-space: nowrap; width: 1%; max-width: 140px; overflow: hidden; text-overflow: ellipsis; }

/* Generic toast notifications — see SS.toast in /static/ss-app.js.
 * Container is created lazily on first SS.toast() call and stacks toasts
 * top-center, sliding down from just below the topbar (user feedback
 * 2026-05-04: corner-toasts on the right + buried inline-status messages
 * deep on the page were too easy to miss, especially when the page
 * reloads right after success). pointer-events:none on the container
 * lets clicks fall through everywhere except on the toast bodies
 * themselves so a sticky toast doesn't block UI underneath.
 */
#ss-toasts {
    position: fixed;
    /* Welle CLS-Re (2026-05-17): an --topbar-h gehängt, damit der
       Toast-Stack beim Mobile-Topbar-Resize automatisch mit-springt. */
    top: var(--topbar-h); left: 50%; transform: translateX(-50%);
    z-index: 9999;
    display: flex; flex-direction: column; align-items: center;
    gap: 8px;
    max-width: 480px; width: max-content;
    pointer-events: none;
}
.ss-toast {
    pointer-events: auto;
    padding: 10px 14px;
    border-radius: 0 0 6px 6px;
    font-size: 0.92em;
    box-shadow: 0 4px 12px rgba(0,0,0,0.12);
    display: flex;
    gap: 10px;
    align-items: flex-start;
    animation: ss-toast-slide-in 0.22s ease-out;
}
.ss-toast-success { background: #f0fdf4; border: 1px solid #bbf7d0; border-top: none; color: #166534; }
.ss-toast-info    { background: #eff6ff; border: 1px solid #bfdbfe; border-top: none; color: #1e3a8a; }
.ss-toast-error   { background: #fef2f2; border: 1px solid #fecaca; border-top: none; color: #991b1b; }
.ss-toast-msg     { flex: 1; line-height: 1.35; }
.ss-toast-dismiss {
    cursor: pointer;
    opacity: 0.6;
    line-height: 1;
    user-select: none;
    font-size: 1.15em;
    padding: 0 2px;
}
.ss-toast-dismiss:hover { opacity: 1; }
.ss-toast-leaving {
    opacity: 0;
    transform: translateY(-12px);
    transition: opacity 0.18s ease-in, transform 0.18s ease-in;
}
@keyframes ss-toast-slide-in {
    from { opacity: 0; transform: translateY(-100%); }
    to   { opacity: 1; transform: translateY(0); }
}
@media (max-width: 600px) {
    #ss-toasts { left: 16px; right: 16px; transform: none; max-width: none; width: auto; align-items: stretch; }
}

/* Welle H4 (2026-05-16) — Value-Labels-Toggle-Button. Klein, dezent
   rechts im Chart-Card-Header. aria-pressed="true" zeigt "an" via
   blauen Hintergrund/Border, "false" als neutrale Outline. */
.value-labels-toggle {
    border: 1px solid #cbd5e1;
    background: #fff;
    border-radius: 6px;
    padding: 2px 8px;
    font-size: 0.95em;
    line-height: 1.4;
    cursor: pointer;
    color: #475569;
    transition: background-color 0.12s ease, border-color 0.12s ease;
}
.value-labels-toggle:hover { background: #f1f5f9; }
.value-labels-toggle[aria-pressed="true"] {
    background: #e0f2fe;
    border-color: #0ea5e9;
    color: #0369a1;
}
.chart-card-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin: 0 0 12px;
}
.chart-card-header h2 { margin: 0; font-size: 1.1em; }

/* === KI-Assistent Chat-Drawer (Welle 2B) ============================
   Bottom-right docked panel, toggled from the topbar 💬 button. Fixed
   position like #ss-toasts; .open flips display to flex. Narrow screens
   go full-width and the conversation list overlays via the ☰ toggle. */
/* KI-Toggle-Redesign: the chat launcher is a fixed bottom-right FAB (was a
   topbar icon). The FAB is created hidden and _loadAiStatus reveals it only when
   chat is on; while the drawer is open the body gets .ss-chat-open so the FAB
   ducks out of the way. Feedback moved to its own topbar icon + the dialog. */
.ss-chat-fab {
    position: fixed; right: 20px; bottom: 20px; z-index: 9990;
    width: 52px; height: 52px; border-radius: 999px;
    border: none; background: #2563eb; color: #fff;
    font-size: 1.5em; line-height: 1; cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    box-shadow: 0 4px 16px rgba(37, 99, 235, 0.4);
}
.ss-chat-fab:hover { background: #1d4ed8; }
.ss-chat-fab:focus { outline: 2px solid #60a5fa; outline-offset: 2px; }
body.ss-chat-open .ss-chat-fab { display: none; }

/* Jobs page (W2) + header badge (W3) — persistent background jobs. */
.jobs-list { display: flex; flex-direction: column; gap: 10px; margin-top: 12px; }
.job-card { border: 1px solid #e2e8f0; border-radius: 8px; padding: 10px 12px; background: #fff; }
.job-card-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.job-badge { font-size: 0.78em; padding: 1px 8px; border-radius: 999px; background: #e2e8f0; color: #334155; }
.job-status-running .job-badge, .job-status-pending .job-badge { background: #dbeafe; color: #1e40af; animation: ss-job-pulse 1.4s ease-in-out infinite; }
.job-status-done .job-badge { background: #dcfce7; color: #166534; }
.job-status-failed .job-badge, .job-status-interrupted .job-badge { background: #fee2e2; color: #991b1b; }
.job-detail { color: #475569; font-size: 0.9em; margin-top: 4px; }
.job-error { color: #991b1b; font-size: 0.9em; margin-top: 4px; }
.job-meta { color: #94a3b8; font-size: 0.82em; margin-top: 4px; }
.job-retry { margin-top: 8px; font-size: 0.85em; padding: 4px 10px; border: 1px solid #2563eb; border-radius: 6px; background: #2563eb; color: #fff; font-weight: 500; cursor: pointer; }
.job-retry:hover { background: #1d4ed8; border-color: #1d4ed8; }
/* Job-control queue actions: pause/cancel a running job, resume a stopped one.
   The container owns the top spacing so the buttons line up in one row. */
.job-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
.job-actions button { margin-top: 0; }
.job-pause, .job-cancel { font-size: 0.85em; padding: 4px 10px; border-radius: 6px; font-weight: 500; cursor: pointer; }
.job-pause { border: 1px solid #cbd5e1; background: #f1f5f9; color: #334155; }
.job-pause:hover { background: #e2e8f0; }
.job-cancel { border: 1px solid #dc2626; background: #fff; color: #dc2626; }
.job-cancel:hover { background: #fef2f2; }
.job-status-paused .job-badge { background: #fef3c7; color: #92400e; }
.job-status-cancelled .job-badge, .job-status-deferred .job-badge { background: #e2e8f0; color: #475569; }
.job-results-box { margin-top: 8px; }
.job-results-box > summary { cursor: pointer; color: #475569; font-size: 0.85em; }
.job-results { list-style: none; margin: 6px 0 0; padding-left: 0.2em; display: flex; flex-direction: column; gap: 3px; }
.job-result { font-size: 0.88em; color: #334155; }
.job-result a { color: #2563eb; text-decoration: none; }
.job-result a:hover { text-decoration: underline; }
/* ✓ for a complete entry; a plain bullet otherwise. Incomplete real entries
   (missing date/assistant) are highlighted amber so follow-up is obvious. */
.job-result-bullet { display: inline-block; width: 1.1em; }
.job-result-check { color: #16a34a; font-weight: 700; }
.job-result-incomplete { color: #b45309; font-weight: 600; }
.job-result-incomplete .job-result-bullet { color: #b45309; }
.job-result-skipped { color: #94a3b8; }
.job-steps { margin-top: 8px; }
.job-steps > summary { cursor: pointer; color: #475569; font-size: 0.85em; }
.job-steps ul { list-style: none; margin: 6px 0 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.job-step { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 0.85em; }
.job-step-label { color: #334155; }
.job-step-meta { color: #94a3b8; font-size: 0.92em; }
.ss-jobs-badge { display: inline-flex; align-items: center; justify-content: center; min-width: 18px; height: 18px; padding: 0 5px; margin-left: 4px; border-radius: 999px; background: #2563eb; color: #fff; font-size: 0.7em; font-weight: 600; animation: ss-job-pulse 1.4s ease-in-out infinite; }
/* The running/pending badges + the nav count pulse so an active job is visibly
   "alive" (the nav badge only exists while jobs run). Opacity-only so nothing
   reflows; off entirely when the user prefers reduced motion. */
@keyframes ss-job-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.45; } }
@media (prefers-reduced-motion: reduce) {
    .ss-jobs-badge, .job-status-running .job-badge, .job-status-pending .job-badge { animation: none; }
}

.ss-feedback-dialog {
    border: none; border-radius: 12px; padding: 20px;
    width: 440px; max-width: calc(100vw - 32px);
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.22);
}
.ss-feedback-dialog::backdrop { background: rgba(15, 23, 42, 0.4); }
.ss-feedback-dialog textarea {
    border: 1px solid #cbd5e1; border-radius: 8px; padding: 10px;
    font: inherit; resize: vertical;
}

.ss-chat-drawer {
    position: fixed;
    right: 16px; bottom: 16px;
    width: 420px; max-width: calc(100vw - 32px);
    height: 560px; max-height: calc(100vh - 90px);
    background: #fff;
    border: 1px solid #cbd5e1;
    border-radius: 12px;
    box-shadow: 0 12px 32px rgba(0,0,0,0.18);
    z-index: 9998;
    display: none;
    flex-direction: column;
    overflow: hidden;
}
.ss-chat-drawer.open { display: flex; }
/* Maximize toggle (⤢): near-fullscreen with a small margin. */
.ss-chat-drawer.maximized {
    inset: 12px;
    width: auto; height: auto;
    max-width: none; max-height: none;
}
.ss-chat-header {
    display: flex; align-items: center; gap: 8px;
    padding: 10px 12px;
    background: #2563eb; color: #fff;
    flex: 0 0 auto;
}
.ss-chat-title { flex: 1; font-weight: 600; }
.ss-chat-header button {
    background: transparent; border: none; color: #fff;
    cursor: pointer; font-size: 0.95em; padding: 2px 6px; border-radius: 4px;
}
.ss-chat-header button:hover { background: rgba(255,255,255,0.18); }
.ss-chat-new { font-size: 0.85em !important; }
.ss-chat-close { font-size: 1.3em !important; line-height: 1; }
/* Formatting-help "?" button + popover (hover shows it; SS.chatToggleHelp
   adds .open for a sticky click on touch devices). */
.ss-chat-help-wrap { position: relative; display: inline-flex; }
.ss-chat-help { font-weight: 700; }
.ss-chat-help-pop {
    display: none; position: absolute; top: 100%; right: 0; z-index: 6;
    margin-top: 6px; width: 240px; padding: 10px 12px;
    background: #fff; color: #1e293b; border: 1px solid #e2e8f0;
    border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,0.18);
    font-size: 0.82em; font-weight: 400; text-align: left; white-space: normal;
}
.ss-chat-help-wrap:hover .ss-chat-help-pop,
.ss-chat-help-wrap.open .ss-chat-help-pop { display: block; }
.ss-chat-help-pop table { border-collapse: collapse; width: 100%; margin-top: 6px; }
.ss-chat-help-pop td { padding: 2px 6px; vertical-align: middle; }
.ss-chat-help-pop td:first-child {
    color: #64748b; font-family: ui-monospace, "SF Mono", Menlo, monospace; white-space: nowrap;
}
/* Welle (2026-05-24): the conversation list is an overlay panel toggled
   by the ☰ button for ALL widths, not a permanent sidebar. Default =
   chat only at full width; the old 132px sidebar ate horizontal space
   and made the chat feel cramped (user feedback 2026-05-24). chatOpen()
   removes .show-list after a pick, so selecting a conversation closes
   the panel automatically. */
.ss-chat-list-toggle { display: inline-block; }
.ss-chat-body { flex: 1; display: flex; min-height: 0; position: relative; }
.ss-chat-list {
    position: absolute; top: 0; bottom: 0; left: 0; width: 210px; max-width: 80%;
    border-right: 1px solid #e2e8f0; overflow-y: auto; background: #f8fafc;
    display: none; z-index: 2; box-shadow: 2px 0 10px rgba(0,0,0,0.15);
}
.ss-chat-drawer.show-list .ss-chat-list { display: block; }
.ss-chat-list-empty { padding: 10px; color: #94a3b8; font-size: 0.82em; }
.ss-chat-list-item {
    display: flex; align-items: center; gap: 4px;
    width: 100%; text-align: left; background: transparent; border: none;
    border-bottom: 1px solid #eef2f7; padding: 8px 10px; cursor: pointer;
    font-size: 0.84em; color: #334155;
}
.ss-chat-list-item:hover { background: #eef2f7; }
.ss-chat-list-item.active { background: #e0e7ff; font-weight: 600; }
.ss-chat-list-title { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ss-chat-list-del { opacity: 0.5; padding: 0 2px; }
.ss-chat-list-del:hover { opacity: 1; color: #b91c1c; }
.ss-chat-main { flex: 1; display: flex; flex-direction: column; min-width: 0; }
.ss-chat-messages {
    flex: 1; overflow-y: auto; padding: 12px;
    display: flex; flex-direction: column; gap: 8px;
}
.ss-chat-empty, .ss-chat-loading { color: #94a3b8; font-size: 0.86em; text-align: center; margin: auto; }
.ss-chat-msg {
    max-width: 85%; padding: 8px 11px; border-radius: 12px;
    font-size: 0.9em; line-height: 1.4; white-space: pre-wrap; overflow-wrap: anywhere;
}
.ss-chat-msg-user { align-self: flex-end; background: #2563eb; color: #fff; border-bottom-right-radius: 4px; }
.ss-chat-msg-assistant { align-self: flex-start; background: #f1f5f9; color: #1e293b; border-bottom-left-radius: 4px; }
/* Inline Markdown rendered by SS.md inside chat bubbles. */
.ss-chat-msg code {
    background: rgba(15,23,42,0.08); padding: 0 4px; border-radius: 4px;
    font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 0.92em;
}
.ss-chat-msg-user code { background: rgba(255,255,255,0.22); }
.ss-chat-msg ul, .ss-chat-msg ol { margin: 4px 0; padding-left: 1.35em; }
.ss-chat-msg li { margin: 1px 0; }
.ss-chat-thinking { opacity: 0.6; font-style: italic; }
.ss-chat-pill {
    align-self: flex-start; background: #fefce8; border: 1px solid #fde68a;
    color: #854d0e; font-size: 0.8em; padding: 4px 9px; border-radius: 10px;
}
.ss-chat-notice {
    align-self: center; color: #991b1b; background: #fef2f2; border: 1px solid #fecaca;
    font-size: 0.82em; padding: 6px 10px; border-radius: 8px;
}
.ss-chat-input-row {
    flex: 0 0 auto; display: flex; gap: 6px; padding: 8px;
    border-top: 1px solid #e2e8f0;
}
.ss-chat-input-row textarea {
    flex: 1; resize: none; border: 1px solid #cbd5e1; border-radius: 8px;
    padding: 8px 10px; font: inherit; font-size: 0.9em; max-height: 120px;
}
.ss-chat-input-row textarea:focus { outline: 2px solid #60a5fa; outline-offset: -1px; }
.ss-chat-send {
    flex: 0 0 auto; width: 40px; border: none; border-radius: 8px;
    background: #2563eb; color: #fff; cursor: pointer; font-size: 1em;
    /* Center the send glyph in both axes. The old ▶ text glyph sat
       off-centre (right-heavy side bearing); an inline SVG in a flex-
       centred box is reliably centred. */
    display: inline-flex; align-items: center; justify-content: center;
}
.ss-chat-send:hover { background: #1d4ed8; }
.ss-chat-send:disabled { opacity: 0.5; cursor: default; }
@media (max-width: 560px) {
    /* On phones the assistant takes the whole screen by default — a
       560px-wide drawer in a corner is unusable on mobile. */
    .ss-chat-drawer, .ss-chat-drawer.maximized {
        inset: 0; right: 0; bottom: 0;
        width: 100vw; max-width: 100vw;
        height: 100dvh; max-height: 100dvh;
        border-radius: 0;
    }
    /* The list overlay (default rules above) is wider on a narrow
       screen so the titles stay readable. */
    .ss-chat-list { width: 78%; max-width: none; }
}

/* Screen-reader-only: visually removed but kept in the accessibility tree
   and the DOM text flow. Used by SS.chartDesc to attach a textual caption +
   data table next to each <canvas> chart (charts are otherwise invisible to
   screen readers and to the chat assistant's page snapshot). Standard
   clip-rect technique — NOT display:none (which would hide it from assistive
   tech too). .ss-chart-desc is just the marker class on the wrapper. */
.sr-only {
    position: absolute !important;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* W-D6: inline Pflegetagebuch entry preview under a Dienstplan shift. The
   toggle sits in the shift's note cell; the expanded entry hangs indented
   below it (left accent border) so it reads as belonging to the shift. */
.diary-shift-links { margin-top: 3px; }
.diary-shift-toggle {
    background: none; border: 0; padding: 0; cursor: pointer;
    font-size: 0.82em; color: #2563eb; margin-right: 10px;
}
.diary-shift-toggle:hover { text-decoration: underline; }
.diary-shift-toggle.open { font-weight: 600; }
/* Feedback 2: the expanded entry is its own full-width row under the shift (the
   notes column is too narrow for the rich view on mobile). */
.diary-inline-row > .diary-inline-cell {
    padding: 2px 12px 10px; background: #f8fafc; border-bottom: 1px solid #e2e8f0;
}
.diary-inline-wrap {
    margin: 6px 0 4px; padding: 6px 0 6px 12px;
    border-left: 3px solid #c7d2fe; font-size: 0.88em;
}
.diary-inline-loading { color: #94a3b8; font-size: 0.9em; }
.diary-inline-title { font-weight: 600; color: #1e293b; }
.diary-inline-meta { color: #64748b; font-size: 0.9em; margin: 1px 0 5px; }
.diary-inline-body { color: #334155; line-height: 1.5; word-break: break-word; }
.diary-inline-body p { margin: 0 0 6px; }
.diary-inline-open {
    display: inline-block; margin-top: 6px;
    font-size: 0.85em; color: #2563eb; text-decoration: none;
}
.diary-inline-open:hover { text-decoration: underline; }
.diary-inline-fig { margin: 6px 0; max-width: 320px; }
.diary-inline-img {
    display: block; max-width: 100%; height: auto;
    border-radius: 6px; cursor: zoom-in; background: #f1f5f9;
}
.diary-fig-cap { font-size: 0.85em; color: #64748b; margin-top: 2px; }
.diary-sources { margin-top: 8px; }
.diary-sources-label { font-size: 0.85em; color: #64748b; margin-right: 6px; }
.diary-thumbs { display: inline-flex; flex-wrap: wrap; gap: 6px; vertical-align: middle; }
.diary-thumb { position: relative; }
.diary-thumb-img {
    width: 56px; height: 56px; object-fit: cover; border-radius: 6px;
    cursor: zoom-in; background: #f1f5f9; border: 1px solid #e2e8f0;
}
.diary-thumb-err { opacity: 0.5; }
.ss-diary-lightbox {
    display: none; position: fixed; inset: 0; z-index: 1000;
    background: rgba(15, 23, 42, 0.85);
    align-items: center; justify-content: center; cursor: zoom-out;
}
.ss-diary-lightbox img {
    max-width: 92vw; max-height: 92vh;
    border-radius: 8px; box-shadow: 0 8px 40px rgba(0, 0, 0, 0.5);
}
