/*
 * shared/ui — components
 *
 * Framework-free: no build step, no external fonts, no CDNs. Every rule
 * references tokens from `tokens.css` and nothing else (directly, or through
 * `color-mix()` for shades) — no raw hex values and no mode queries. Light and
 * dark fall out of the tokens alone.
 *
 * Order: base → header → mobile nav → page layout → layout primitives →
 * buttons → cards → tables → labels → groups → forms → login → messages →
 * floating nav → charts → helpers → mobile tier.
 *
 * Responsive: each component handles its own reflow next to its own rules; the
 * closing "mobile tier" section carries only what is cross-cutting (touch
 * targets, safe-area insets, density). Breakpoints are 720px and 480px, and
 * there are no others.
 */

/* --- Base --- */

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
    margin: 0;
    font-family: var(--font-body);
    font-size: var(--font-size-base);
    line-height: 1.5;
    background: var(--bg);
    color: var(--text);
    -webkit-font-smoothing: antialiased;
}

h1 { font-size: 1.45rem; font-weight: 650; letter-spacing: -0.01em; margin: 0; }
h2 { font-size: 1.05rem; font-weight: 650; letter-spacing: -0.005em; }

a { color: var(--primary); }

:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
    border-radius: var(--r-s);
}

::selection { background: color-mix(in srgb, var(--primary) 25%, transparent); }

svg.icon {
    width: 1em;
    height: 1em;
    flex: none;
    vertical-align: -0.125em;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
    fill: none;
}

[hidden] { display: none !important; }

/* --- Header & navigation --- */

.topbar {
    position: sticky;
    top: 0;
    z-index: 100;
    display: flex;
    align-items: center;
    gap: 1.25rem;
    /* The insets are zero everywhere except a notched phone held sideways. */
    padding: 0 max(1.25rem, env(safe-area-inset-right)) 0 max(1.25rem, env(safe-area-inset-left));
    height: var(--topbar-h);
    background: color-mix(in srgb, var(--card) 82%, transparent);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border-bottom: 1px solid var(--border);
}

.brand {
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
    color: var(--text);
    text-decoration: none;
    font-weight: 700;
    font-size: 0.98rem;
    letter-spacing: -0.01em;
}
.brand-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.7rem;
    height: 1.7rem;
    border-radius: var(--r-s);
    background: var(--primary);
    color: var(--on-primary);
    font-size: 0.95rem;
}

.topnav {
    display: flex;
    align-items: center;
    gap: 0.15rem;
}
.topnav a {
    padding: 0.35rem 0.7rem;
    border-radius: 999px;
    color: var(--text-2);
    text-decoration: none;
    font-size: 0.88rem;
    font-weight: 500;
}
.topnav a:hover { color: var(--text); background: color-mix(in srgb, var(--text) 6%, transparent); }
.topnav a.is-active {
    color: var(--primary-ink);
    background: var(--primary-soft);
    font-weight: 600;
}

.topbar-user {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 0.6rem;
    font-size: 0.85rem;
    color: var(--muted);
}
.topbar-user .user { max-width: 16rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.topbar-user a {
    color: var(--text-2);
    text-decoration: none;
    padding: 0.3rem 0.65rem;
    border: 1px solid var(--border);
    border-radius: 999px;
    font-weight: 500;
}
.topbar-user a:hover { color: var(--text); border-color: var(--border-strong); }

/*
 * Mobile navigation.
 *
 * One markup shape serves both tiers — the bar is not rebuilt at the
 * breakpoint, it is re-flowed:
 *
 *   <header class="topbar">
 *     <a class="brand">…</a>
 *     <details class="nav-drawer">
 *       <summary class="nav-burger" aria-label="Menu"></summary>
 *       <div class="nav-panel">
 *         <nav class="topnav">…</nav>
 *         <div class="topbar-user">…</div>
 *       </div>
 *     </details>
 *   </header>
 *
 * Above 720px the wrapper dissolves: `display: contents` on the <details> and
 * on the panel drops both boxes out of the layout, so `.topnav` and
 * `.topbar-user` are flex children of `.topbar` exactly as they were before
 * the drawer existed. The desktop bar is unchanged by design — this section
 * costs it nothing.
 *
 * A closed <details> hides its content, which is the one thing that has to be
 * undone for that to work. `::details-content` is how the spec addresses it;
 * the bare `display: contents` covers engines that predate the pseudo. Both
 * are harmless where the other applies.
 *
 * That undo is exactly what the mobile tier must NOT inherit — a drawer whose
 * panel ignores the open state is just a permanently open drawer — so the
 * dissolve is scoped to the tier that wants it rather than written as a
 * default and countermanded below.
 *
 * Like `.menu`, it opens with no JavaScript. An app that already runs the
 * one-menu-at-a-time script should widen its selector to
 * `details.menu, details.nav-drawer` so outside-click and Escape close the
 * drawer too; without that it still closes on the burger, which is why the
 * burger morphs into an X rather than staying a burger.
 */
@media (min-width: 721px) {
    .nav-drawer { display: contents; }
    .nav-drawer::details-content { content-visibility: visible; display: contents; }
    .nav-panel { display: contents; }
    .nav-burger { display: none; }
}

@keyframes nav-panel-in {
    from { transform: translateX(100%); opacity: 0; }
}

@media (max-width: 720px) {
    /*
     * The panel is `position: fixed`, and `backdrop-filter` on an ancestor
     * makes that ancestor the containing block — a fixed child of the blurred
     * bar would be trapped inside its 3.5rem. Dropping the blur here is what
     * lets the drawer escape the header; an opaque bar is the better call on a
     * phone anyway, both for legibility and for the compositing cost.
     */
    .topbar {
        background: var(--card);
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
        gap: 0.75rem;
    }

    /* Pushed to the far end of the bar; the panel is positioned against the
       viewport, so the wrapper only has to place the burger. */
    .nav-drawer { margin-left: auto; }

    .nav-burger {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        position: relative;
        width: var(--tap-min);
        height: var(--tap-min);
        /* Pull the box past the bar's padding so the bars, not the hit area,
           line up with the content edge. */
        margin-right: -0.6rem;
        border-radius: var(--r-s);
        color: var(--text-2);
        cursor: pointer;
        list-style: none;
    }
    .nav-burger::-webkit-details-marker { display: none; }
    .nav-burger::marker { content: ''; }

    /* Three bars from two pseudo-elements — the middle one is a shadow of the
       top one — so the icon needs no markup and no icon font. Opening drops
       the middle bar and crosses the other two. */
    .nav-burger::before,
    .nav-burger::after {
        content: '';
        position: absolute;
        width: 1.15rem;
        height: 2px;
        border-radius: 2px;
        background: currentColor;
        transition: transform 0.18s ease, box-shadow 0.18s ease;
    }
    .nav-burger::before { transform: translateY(-6px); box-shadow: 0 6px currentColor; }
    .nav-burger::after { transform: translateY(6px); }
    .nav-drawer[open] .nav-burger { color: var(--text); }
    .nav-drawer[open] .nav-burger::before { transform: rotate(45deg); box-shadow: 0 0 transparent; }
    .nav-drawer[open] .nav-burger::after { transform: rotate(-45deg); }

    .nav-panel {
        display: flex;
        flex-direction: column;
        align-items: stretch;
        gap: 0.15rem;
        position: fixed;
        z-index: 120;
        top: var(--topbar-h);
        right: 0;
        width: var(--drawer-w);
        max-height: calc(100dvh - var(--topbar-h));
        padding: 0.75rem 1rem calc(0.75rem + env(safe-area-inset-bottom));
        overflow-y: auto;
        overscroll-behavior: contain;
        background: var(--card);
        border-left: 1px solid var(--border);
        border-bottom: 1px solid var(--border);
        border-radius: 0 0 0 var(--r-l);
        /* Second layer is the scrim: a shadow large enough to cover the page
           dims it without a scrim element the markup would have to carry. */
        box-shadow: var(--shadow-2), 0 0 0 100vmax var(--scrim);
    }
    .nav-drawer[open] .nav-panel { animation: nav-panel-in 0.18s ease-out; }

    /* Both clusters become full-width rows in the drawer. */
    .nav-panel .topnav,
    .nav-panel .topbar-user {
        display: flex;
        flex-direction: column;
        align-items: stretch;
        gap: 0.15rem;
        margin: 0;
        font-size: inherit;
    }
    /* The user cluster is the drawer's foot: account, language, theme, logout. */
    .nav-panel .topbar-user {
        margin-top: 0.5rem;
        padding-top: 0.5rem;
        border-top: 1px solid var(--border);
    }

    .nav-panel .topnav a,
    .nav-panel .topbar-user > a {
        display: flex;
        align-items: center;
        gap: 0.6rem;
        min-height: var(--tap-min);
        padding: 0 0.75rem;
        border: none;
        border-radius: var(--r-s);
        font-size: 0.95rem;
    }
    .nav-panel .topbar-user .user { display: block; max-width: none; }

    /* Menus flatten into accordions: an absolutely positioned panel would
       overflow a drawer this narrow, and there is room to just indent. */
    .nav-panel .menu > summary {
        display: flex;
        align-items: center;
        gap: 0.6rem;
        min-height: var(--tap-min);
        padding: 0 0.75rem;
        border: none;
        border-radius: var(--r-s);
        font-size: 0.95rem;
    }
    .nav-panel .menu-list {
        position: static;
        min-width: 0;
        margin: 0.1rem 0 0.3rem 0.75rem;
        padding: 0 0 0 0.6rem;
        background: none;
        border: none;
        border-left: 1px solid var(--border);
        border-radius: 0;
        box-shadow: none;
    }
    .nav-panel .menu-list a,
    .nav-panel .menu-list button {
        display: flex;
        align-items: center;
        gap: 0.6rem;
        min-height: var(--tap-min);
        padding: 0 0.6rem;
        font-size: 0.92rem;
        white-space: normal;
    }

    /* Icon buttons that live in the bar (search, theme) stay a row rather than
       becoming full-width entries — they read as controls, not destinations. */
    .nav-panel .topbar-user .icon-btn {
        width: var(--tap-min);
        height: var(--tap-min);
    }
}

/* Dropdown built on <details>/<summary>, so it opens with no JavaScript. */
.menu { position: relative; }
.menu > summary {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.3rem 0.65rem;
    border: 1px solid var(--border);
    border-radius: 999px;
    color: var(--text-2);
    font-size: 0.85rem;
    font-weight: 500;
    cursor: pointer;
    list-style: none;
    user-select: none;
}
.menu > summary::-webkit-details-marker { display: none; }
.menu > summary::marker { content: ''; }
.menu > summary:hover { color: var(--text); border-color: var(--border-strong); }
.menu > summary .user { max-width: 10rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.menu-list {
    position: absolute;
    right: 0;
    top: calc(100% + 0.4rem);
    z-index: 200;
    min-width: 11rem;
    /* A panel wider than the screen escapes it, because `right: 0` anchors the
       edge that is already on-screen and pushes the other one off. The clamp
       bounds the damage; `.menu-left` is the actual fix for a menu whose
       trigger sits near the left edge, where the overflow goes the other way. */
    max-width: min(18rem, calc(100vw - 1.5rem));
    padding: 0.3rem;
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: var(--r-m);
    box-shadow: var(--shadow-2);
}
/* Anchor to the trigger's left edge instead — for triggers on the left half. */
.menu-left .menu-list { right: auto; left: 0; }
/* Buttons as well as links: a logout entry has to be a POST form. */
.menu-list a,
.menu-list button {
    display: block;
    width: 100%;
    padding: 0.4rem 0.6rem;
    border: none;
    border-radius: var(--r-s);
    background: none;
    color: var(--text-2);
    text-align: left;
    text-decoration: none;
    font: inherit;
    font-size: 0.85rem;
    white-space: nowrap;
    cursor: pointer;
}
.menu-list a:hover,
.menu-list button:hover { background: var(--card-2); color: var(--text); }
.menu-list a.is-active { background: var(--primary-soft); color: var(--primary-ink); font-weight: 600; }

.menu-sep { height: 1px; margin: 0.3rem 0; background: var(--border); }

/* --- Page layout --- */

.container {
    max-width: 1180px;
    margin: 1.75rem auto;
    padding: 0 1.25rem;
}

.page-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 0.75rem;
    margin-bottom: 1.25rem;
}

/* Title cluster inside `.page-head`: optional icon, heading, optional subtitle. */
.page-titles { display: flex; align-items: center; gap: 0.9rem; min-width: 0; }
.page-titles .subtitle { margin: 0.15rem 0 0; font-size: 0.85rem; color: var(--muted); }
.page-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 2.75rem;
    height: 2.75rem;
    border-radius: var(--r-m);
    background: var(--primary-soft);
    color: var(--primary-ink);
    font-size: 1.25rem;
}

.actions { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }

.app-footer {
    margin-top: 2.5rem;
    padding: 1.1rem 1.25rem;
    text-align: center;
    background: var(--card);
    border-top: 1px solid var(--border);
    color: var(--muted);
    font-size: 0.85rem;
}
.app-footer .brand-dot { margin-right: 0.25rem; }

/* --- Layout primitives --- */

/*
 * The handful of arrangements every page in the fleet is built from. These are
 * named structures, not a utility scale: there is no `.p-4` here and there
 * should not be. Density is a token (`--gap-*`, `--col-min`), so an app tunes
 * the vocabulary in its theme rather than forking it in `app.css`.
 */

/* Vertical rhythm between the sections of a long page. */
.stack { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--gap-section); }
.stack-tight { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--gap-tight); }

/* Two columns that collapse to one when there is no room for both. */
.cols-2 {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(var(--col-min), 100%), 1fr));
    gap: var(--gap-grid);
}

/* …and the same with one dominant column. Unlike `.cols-2` these cannot use
   auto-fit — the ratio is the point — so they reflow at an explicit breakpoint. */
.cols-wide-narrow { display: grid; grid-template-columns: minmax(0, var(--col-wide)) minmax(0, 1fr); gap: var(--gap-grid); }
.cols-narrow-wide { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, var(--col-wide)); gap: var(--gap-grid); }
@media (max-width: 900px) {
    .cols-wide-narrow,
    .cols-narrow-wide { grid-template-columns: minmax(0, 1fr); }
}

/* Inline row of items that wraps: filter bars, tag lists, action clusters. */
.row-wrap { display: flex; flex-wrap: wrap; align-items: center; gap: var(--gap-inline); }
.row-between {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--gap-tight);
}

/* A single-column page that should not run the full container width — the
   one-thing-at-a-time workflows. */
.narrow-col { max-width: 44rem; margin: 0 auto; }
.narrow-col-wide { max-width: 58rem; }

/* --- Buttons --- */

.btn {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.45rem 0.85rem;
    border-radius: var(--r-s);
    border: 1px solid var(--border);
    background: var(--card);
    color: var(--text);
    text-decoration: none;
    font: inherit;
    font-size: 0.88rem;
    font-weight: 500;
    cursor: pointer;
    box-shadow: var(--shadow-1);
    transition: border-color 0.12s ease, background 0.12s ease;
}
.btn:hover { border-color: var(--border-strong); background: var(--card-2); }

.btn-primary {
    background: var(--primary);
    border-color: var(--primary);
    color: var(--on-primary);
}
.btn-primary:hover { background: var(--primary-hover); border-color: var(--primary-hover); }

/* Square icon-only button — row actions, dismiss controls. */
.icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.9rem;
    height: 1.9rem;
    padding: 0;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: none;
    color: var(--text-2);
    font: inherit;
    cursor: pointer;
}
.icon-btn:hover { color: var(--text); border-color: var(--border-strong); }
.icon-btn.danger:hover { color: var(--neg); border-color: var(--neg); }

/*
 * Mutually exclusive views of one list — past / upcoming / all. Two
 * treatments, because they solve different problems: `.segmented` is a single
 * joined strip and reads as one control, but cannot wrap; adding
 * `.segmented-pills` breaks it into separate pills that do, which is what a
 * set of year filters needs. The modifier is used alongside the base, as
 * `class="segmented segmented-pills"`.
 */
.segmented {
    display: inline-flex;
    border: 1px solid var(--border);
    border-radius: var(--r-s);
    overflow: hidden;
    background: var(--card);
    box-shadow: var(--shadow-1);
}
.segmented a {
    padding: 0.45rem 0.8rem;
    color: var(--text-2);
    text-decoration: none;
    font-size: 0.85rem;
    font-weight: 500;
    white-space: nowrap;
}
.segmented a + a { border-left: 1px solid var(--border); }
.segmented a:hover { background: var(--card-2); color: var(--text); }
.segmented a.is-active { background: var(--primary); color: var(--on-primary); font-weight: 600; }

.segmented-pills {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
    border: none;
    border-radius: 0;
    background: none;
    box-shadow: none;
}
.segmented-pills a {
    padding: 0.3rem 0.7rem;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--card);
    font-size: 0.83rem;
    font-variant-numeric: tabular-nums;
}
.segmented-pills a:hover { background: var(--card); border-color: var(--border-strong); color: var(--text); }
.segmented-pills a.is-active { background: var(--primary); border-color: var(--primary); }

/* --- Cards & key figures --- */

.cards {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: 1rem;
    margin: 1.25rem 0;
}

.card {
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: var(--r-l);
    padding: var(--card-pad-y) var(--card-pad-x);
    box-shadow: var(--shadow-1);
}

.card h2 { margin: 0 0 0.75rem; }

/* Full-bleed footer bonded to the bottom of a `.card`: it cancels the card's
   own padding to reach the edges, which is why that padding is a token. */
.card-foot {
    margin: 1rem calc(-1 * var(--card-pad-x)) calc(-1 * var(--card-pad-y));
    padding: 0.75rem var(--card-pad-x);
    border-top: 1px solid var(--border);
    background: var(--card-2);
    border-radius: 0 0 var(--r-l) var(--r-l);
    font-size: 0.85rem;
}

/* KPI head: one big number per card, details underneath. */
.kpi { margin: 0 0 0.9rem; }
.kpi-value {
    font-size: 1.9rem;
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1.15;
    white-space: nowrap;
}
.kpi-label {
    display: block;
    font-size: 0.72rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--muted);
    margin-top: 0.1rem;
}
.card .kpi + dl { border-top: 1px solid var(--border); padding-top: 0.75rem; }

/*
 * Stat tiles: a row of small figures, denser than `.cards` + `.kpi`. The
 * default centres the figure; `.tile-lead` turns it into a row with an icon
 * beside it and scales the figure up to suit.
 */
.tiles {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(var(--tile-min), 100%), 1fr));
    gap: var(--gap-tight);
}

.tile {
    padding: 0.85rem 0.9rem;
    text-align: center;
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: var(--r-m);
    box-shadow: var(--shadow-1);
}

.tile-lead {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    padding: 1rem 1.15rem;
    text-align: left;
}

.tile-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: var(--r-s);
    background: var(--primary-soft);
    color: var(--primary-ink);
    font-size: 1.05rem;
}

.tile-value {
    font-family: var(--font-display);
    font-size: 1.4rem;
    font-weight: 700;
    line-height: 1.2;
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
    color: var(--text);
}
.tile-lead .tile-value { font-size: 1.8rem; line-height: 1.15; letter-spacing: normal; }
.tile-value.is-date { font-size: 0.95rem; }

.tile-unit { font-size: 0.8rem; font-weight: 400; color: var(--muted); }

/* Long German compounds — "Veranstaltungsorte" — are a single unbreakable word
   wider than the tile's text column, so without the break they overrun it. */
.tile-label {
    display: block;
    margin-top: 0.15rem;
    color: var(--muted);
    font-size: 0.72rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    overflow-wrap: break-word;
    hyphens: auto;
}

dl { display: grid; grid-template-columns: 1fr auto; gap: 0.35rem 1rem; margin: 0; font-size: 0.9rem; }
dt { color: var(--muted); }
dd { margin: 0; text-align: right; font-variant-numeric: tabular-nums; color: var(--text-2); }
.strong, dd.strong { font-weight: 700; color: var(--text); }
.pos { color: var(--pos); }
.neg { color: var(--neg); }

.table-title { margin: 1.75rem 0 0.5rem; }

/* --- Tables --- */

.grid {
    width: 100%;
    border-collapse: collapse;
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: var(--r-m);
    overflow: hidden;
    font-size: 0.88rem;
    box-shadow: var(--shadow-1);
}

.grid th, .grid td { padding: 0.55rem 0.75rem; text-align: left; border-bottom: 1px solid var(--border); }
.grid tbody tr:last-child td { border-bottom: none; }
.grid th {
    background: var(--card-2);
    font-weight: 600;
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--muted);
}
.grid td { font-variant-numeric: tabular-nums; }

/* Denser rows, for tables that are read as a block rather than scanned. */
.grid-sm th, .grid-sm td { padding: 0.35rem 0.6rem; }

/* A table is the one child that will not shrink to fit its column, so it gets
   its own scroll frame. `.grid` already clips its corners — this must only
   supply the overflow, never a second border.

   A `.grid` outside one of these does not shrink either: it pushes the page
   wider than the viewport, and every other element on it inherits the sideways
   scroll. On a phone that is the single most visible layout failure there is,
   so treat the wrapper as required rather than decorative.

   `scrollbar-*` keeps a thin bar visible as the affordance — without it
   nothing says the table continues past the edge. `overscroll-behavior-x`
   stops a swipe that reaches the end of the table from turning into the
   browser's back gesture. */
.table-wrap {
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scrollbar-width: thin;
    scrollbar-color: var(--border-strong) transparent;
}
.table-wrap-tall { max-height: 26rem; overflow: auto; }

/* --- Labels --- */

/* Short colour-coded label. The app sets the accent per variant via
   --badge-accent (e.g. `.badge-U { --badge-accent: var(--type-U); }`). */
.badge {
    display: inline-block;
    min-width: 1.5rem;
    text-align: center;
    padding: 0.1rem 0.35rem;
    border-radius: 4px;
    font-weight: 700;
    font-size: 0.78rem;
    --badge-accent: var(--muted);
    color: color-mix(in srgb, var(--badge-accent) 75%, var(--text));
    background: color-mix(in srgb, var(--badge-accent) 16%, var(--card));
}

/* --- List head & collapsible groups --- */

.list-head {
    display: flex;
    align-items: baseline;
    gap: 0.6rem;
    margin: 1.75rem 0 0.6rem;
}
.list-head .table-title { margin: 0; }
.list-count {
    font-size: 0.82rem;
    color: var(--muted);
    background: color-mix(in srgb, var(--text) 7%, transparent);
    padding: 0.1rem 0.55rem;
    border-radius: 999px;
}

/* Collapsible section (<details>/<summary>) with a heading and a meta column. */
.group { margin-bottom: 0.6rem; }
.group-summary {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    padding: 0.65rem 0.95rem;
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: var(--r-m);
    cursor: pointer;
    list-style: none;
    user-select: none;
    box-shadow: var(--shadow-1);
}
.group-summary::-webkit-details-marker { display: none; }
.group-summary::marker { content: ''; }
.group-summary:hover { background: var(--card-2); }
.group[open] > .group-summary {
    border-radius: var(--r-m) var(--r-m) 0 0;
    border-bottom-color: transparent;
}
.group-chevron {
    color: var(--muted);
    font-size: 0.8rem;
    transition: transform 0.15s ease;
}
.group[open] > .group-summary .group-chevron { transform: rotate(90deg); }
.group-name { font-weight: 700; font-size: 1rem; }
.group-count { color: var(--muted); font-size: 0.83rem; }
.group-meta {
    margin-left: auto;
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
    font-size: 0.83rem;
    color: var(--muted);
}

@media (max-width: 720px) {
    .group-summary { flex-wrap: wrap; gap: 0.4rem 0.55rem; }
    .group-meta-lbl { display: none; }
}

/* --- Forms --- */

.form-card { max-width: 520px; }
.form-card label { display: block; font-weight: 600; font-size: 0.9rem; margin: 0.85rem 0 0.3rem; }
.form-card > form > div:first-child label,
.form-card form > label:first-child { margin-top: 0; }
.form-card input, .form-card select, .form-card textarea {
    width: 100%;
    padding: 0.5rem 0.65rem;
    border: 1px solid var(--border-strong);
    border-radius: var(--r-s);
    font: inherit;
    background: var(--card);
    color: var(--text);
    transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.form-card input:focus, .form-card select:focus, .form-card textarea:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 20%, transparent);
}
.form-card input:disabled, .form-card select:disabled {
    background: var(--card-2);
    color: var(--muted);
    cursor: not-allowed;
}
.form-card .help-text {
    display: block;
    margin: 0.3rem 0 0;
    font-size: 0.8rem;
    font-weight: 400;
    color: var(--muted);
}
.form-card ul {
    margin: 0.3rem 0 0;
    padding: 0;
    list-style: none;
    font-size: 0.82rem;
    color: var(--neg);
}
.form-actions { margin-top: 1.25rem; }

/* --- Login page --- */

.login-card { max-width: 380px; margin: 4rem auto; }
.login-brand {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.6rem;
    margin-bottom: 1.25rem;
    text-align: center;
}
.login-brand .brand-mark { width: 2.6rem; height: 2.6rem; font-size: 1.4rem; border-radius: var(--r-m); }
.login-card h1 { font-size: 1.2rem; }
.login-card .checkbox {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    font-weight: 400;
    font-size: 0.9rem;
    margin-top: 0.9rem;
}
.login-card .checkbox input { width: auto; accent-color: var(--primary); }
.login-card .btn { width: 100%; justify-content: center; }

/* --- Notices & messages --- */

.flash {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    padding: 0.7rem 1rem;
    border-radius: var(--r-m);
    border: 1px solid transparent;
    margin-bottom: 1rem;
    font-size: 0.9rem;
}
.flash-success {
    background: color-mix(in srgb, var(--pos) 10%, var(--card));
    border-color: color-mix(in srgb, var(--pos) 35%, transparent);
    color: var(--pos);
}
.flash-danger {
    background: color-mix(in srgb, var(--neg) 10%, var(--card));
    border-color: color-mix(in srgb, var(--neg) 35%, transparent);
    color: var(--neg);
}
.flash-warn {
    background: color-mix(in srgb, var(--warn) 10%, var(--card));
    border-color: color-mix(in srgb, var(--warn) 35%, transparent);
    color: var(--warn);
}
/* Informational, so it carries the accent rather than a semantic colour. */
.flash-info {
    background: var(--primary-soft);
    border-color: color-mix(in srgb, var(--primary) 35%, transparent);
    color: var(--primary-ink);
}

.legend { color: var(--muted); font-size: 0.85rem; margin-top: 1rem; max-width: 72ch; }
.legend code {
    background: color-mix(in srgb, var(--text) 7%, transparent);
    padding: 0.05rem 0.3rem;
    border-radius: 4px;
}

.empty-note {
    background: var(--card);
    border: 1px dashed var(--border-strong);
    border-radius: var(--r-m);
    padding: 1.25rem;
    color: var(--muted);
    text-align: center;
}

/* --- Floating navigation --- */

.floating-nav {
    position: fixed;
    right: 1.25rem;
    bottom: 1.25rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    z-index: 50;
}
.fab {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.35rem;
    padding: 0.55rem 1rem;
    border: none;
    border-radius: 999px;
    background: var(--primary);
    color: var(--on-primary);
    font: inherit;
    font-size: 0.88rem;
    font-weight: 600;
    text-decoration: none;
    cursor: pointer;
    box-shadow: var(--shadow-2);
}
.fab:hover { background: var(--primary-hover); }
.fab-top { background: var(--text-2); color: var(--card); }
.fab-top:hover { background: var(--text); }

/* --- Charts --- */

/* Vertical bar chart. The sign is encoded twice (colour + label) so it stays
   readable without colour perception. */
.chart {
    display: flex;
    align-items: flex-end;
    gap: 0.4rem;
    height: 220px;
    padding: 1rem 1.1rem 0;
    margin: 0.5rem 0 1.25rem;
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: var(--r-m);
    overflow-x: auto;
    box-shadow: var(--shadow-1);
}
.chart-col {
    flex: 1 1 0;
    min-width: 38px;
    display: flex;
    flex-direction: column;
    align-items: center;
    height: 100%;
}
.chart-bar-wrap {
    flex: 1;
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-end;
}
.chart-value {
    font-size: 0.7rem;
    font-variant-numeric: tabular-nums;
    color: var(--text-2);
    margin-bottom: 0.2rem;
}
.chart-bar {
    width: 60%;
    max-width: 28px;
    min-height: 2px;
    border-radius: 4px 4px 0 0;
    background: var(--primary);
    transition: height 0.2s ease;
}
.chart-bar.pos { background: var(--chart-pos); }
.chart-bar.neg { background: var(--chart-neg); }
.chart-label {
    margin-top: 0.35rem;
    font-size: 0.72rem;
    color: var(--muted);
}

/* Horizontal bars inside a table column: one magnitude → one colour. */
.grid .bar-col { width: 40%; vertical-align: middle; }
.hbar-track {
    background: color-mix(in srgb, var(--text) 8%, transparent);
    border-radius: 999px;
    height: 0.7rem;
    overflow: hidden;
}
.hbar {
    height: 100%;
    min-width: 2px;
    background: var(--primary);
    border-radius: 999px;
}

/* --- Helpers --- */

/*
 * The complete set. These six are single-purpose, carry no raw values and have
 * no scale behind them — that is the line. A `.p-4` or a `.text-sm` would make
 * this a utility framework and put raw values back at the call site, which is
 * the one thing the token rule exists to prevent. If a helper needs a number,
 * it wants to be a component instead.
 */
.push-right { margin-left: auto; }
.nowrap { white-space: nowrap; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.grow { flex: 1; min-width: 0; }
.num { font-variant-numeric: tabular-nums; }
/* A value meant to be copied verbatim — share URLs, mostly. */
.mono { font-family: var(--font-mono); font-size: 0.85rem; }

/* --- Mobile tier ---
 *
 * What is left after each component has handled its own reflow next to its own
 * rules: the cross-cutting changes that would otherwise be repeated a dozen
 * times. Two steps — 720px is "this is a touch screen", 480px is "and it is a
 * small one", where density drops via the tokens rather than per component.
 */

@media (max-width: 720px) {
    /* Touch targets. Every control the fleet ships reaches `--tap-min` in at
       least one axis here; the pointer-coarse guidance is 44px and most of
       these sit at 28-32px, which is the desktop density showing through. */
    .btn,
    .segmented a,
    .segmented-pills a,
    .menu-list a,
    .menu-list button {
        min-height: var(--tap-min);
        display: inline-flex;
        align-items: center;
    }
    .btn, .segmented a { justify-content: center; }
    .icon-btn { width: var(--tap-min); height: var(--tap-min); }
    .menu-list a, .menu-list button { padding-block: 0; }

    /*
     * iOS zooms the whole page when a focused field's text is under 16px, and
     * never zooms back out. The base scale is 15px, so every field in the
     * fleet triggers it. 16px here is not a design choice — it is the
     * threshold, and it applies to touch screens only so the desktop keeps
     * the intended scale.
     */
    input, select, textarea { font-size: 16px; }

    .container {
        margin: 1rem auto;
        padding: 0 max(1rem, env(safe-area-inset-right)) 0 max(1rem, env(safe-area-inset-left));
    }

    /* Actions read as a stack of full-width targets rather than a cramped row. */
    .actions > .btn { flex: 1 1 auto; }

    .floating-nav {
        right: max(1rem, env(safe-area-inset-right));
        bottom: max(1rem, env(safe-area-inset-bottom));
    }
    .fab { min-height: var(--tap-min); }

    .chart { height: 170px; padding: 0.75rem 0.75rem 0; }

    .login-card { margin: 1.5rem auto; }
}

@media (max-width: 480px) {
    /* Density is a token, so the small-phone step is mostly re-pricing the
       vocabulary rather than restyling the components that use it. */
    :root {
        --gap-section: 1.1rem;
        --gap-grid: 0.9rem;
        --gap-tight: 0.6rem;
        --card-pad-x: 1rem;
        --card-pad-y: 0.9rem;
        --tile-min: 8.5rem;
    }

    h1 { font-size: 1.25rem; }

    .page-icon { width: 2.25rem; height: 2.25rem; font-size: 1.05rem; }
    .page-titles { gap: 0.65rem; }

    .kpi-value { font-size: 1.6rem; }
    .tile-lead .tile-value { font-size: 1.5rem; }

    /* Narrower cells fit more columns on screen before the wrapper has to
       scroll, which is cheaper than scrolling for the reader. */
    .grid th, .grid td { padding: 0.5rem 0.55rem; }
    .grid-sm th, .grid-sm td { padding: 0.3rem 0.45rem; }

    /* A term and its value stop sharing a line once the value needs the room. */
    dl { grid-template-columns: minmax(0, 1fr) auto; column-gap: 0.6rem; }

    .cards { grid-template-columns: minmax(0, 1fr); }

    .app-footer { margin-top: 1.75rem; padding: 0.9rem 1rem; }
}

/* The drawer's slide and the burger's morph are the only motion the system
   adds; both are decoration over a state change that is already legible. */
@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    .nav-drawer[open] .nav-panel { animation: none; }
    .nav-burger::before,
    .nav-burger::after { transition: none; }
}
