:root {
    --bg: #eef4fa;
    --panel: #ffffff;
    --ink: #102a43;
    --ink-medium: #324d6b;
    --ink-soft: #486581;
    --rule: #d9e6f3;
    --accent: #6f95bb;
    --accent-soft: #e3eef7;
    --accent-hover: #557fa9;
    --error-bg: #fdecea;
    --error-border: #f5c6c0;
    --error-text: #8a1f1f;
    --mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    --display: 'Fraunces', Georgia, 'Times New Roman', serif;
    --faint: #829ab1;

    /* R/A/G semantic palette - the canonical out-of-tolerance red,
       amber-warning, and in-tolerance green. Used by every balance
       cell, pill, badge, lozenge, top-mover delta and audit chip.
       Single source of truth so a future palette tweak is one block.
       (BalanceCss.For returns the .balance-red / -amber / -green
       class names against the ±11.5 h tolerance band.) */
    --rag-red:                 #a85252;  /* text colour */
    --rag-amber:               #a47236;  /* text colour */
    --rag-green:               #3f7a4a;  /* text colour */
    --rag-red-strong:          #b85959;  /* solid pill / lozenge fill */
    --rag-green-strong:        #4a8b56;  /* solid pill / lozenge fill */
    --rag-red-soft-bg:         #f6eded;  /* badge / pill background */
    --rag-red-soft-bg-hover:   #fbeded;  /* hover variant */
    --rag-red-soft-border:     #e1c8c8;
    --rag-green-soft-bg:       #eef6f1;
    --rag-green-soft-border:   #c8e1cf;

    /* Activity-type palette, used everywhere a duty bucket / NERG sub-type
       is shown (chip, daily strip, 52-week chart). Maps to the table in
       sql/schema/ob_shifts.md → "Activity-type derivation". Pastel tier -
       low saturation, mid lightness, designed to read as a coherent system
       without overwhelming the eye. Keep these in sync with the C# palette
       in BalancesService.GetWeeklyHoursAsync / GetUnitWeeklyHoursAsync. */
    --duty-local:      #8aafce;  /* Local substantive */
    --duty-bank:       #dfb480;  /* Bank */
    --duty-agency:     #c89d6e;  /* Agency */
    --duty-workingday: #a8c4dc;  /* WorkingDay = Management Time, paler blue */
    --duty-annual:     #e9d075;  /* AnnualLeave, soft yellow */
    --duty-sickness:   #d4928a;  /* Sickness, soft red */
    --duty-study:      #8cbf9f;  /* StudyLeave, soft green */
    --duty-parenting:  #dcaebd;  /* Parenting, soft pink */
    --duty-other:      #bd97a3;  /* OtherLeave / Unknown, muted mauve */

    /* Inline UI arrows - disclosure carets (▾/▸), drill chevrons (›/‹),
       column-header sort indicators (▲/▼), and the account-menu caret.
       Sized + weighted consistently so every arrow-like glyph in the app
       reads as the same family. Standalone glyph-button content (e.g.
       the prev/next chevrons on the Top movers scroller, which fill a
       full button) is NOT bound to this - those have their own size. */
    --ui-arrow-size: 25px;
    --ui-arrow-weight: 700;

    /* Mini-icon sizing. SVG icons reference a 24x24 viewBox and
       inherit currentColor from the surrounding text, so the only
       thing the call site has to set is the display size (and
       optionally an override colour on hover / active). */
    --icon-size: 12px;
    --icon-size-sm: 9px;
    --icon-size-lg: 15px;

    /* Height of the bottom-fixed activity strip - referenced by
       body { padding-bottom } + every scroll container's max-
       height calc so the sticky <tfoot> totals row sits above it.
       Matches the rendered strip: 21 px lozenge + 0.45 rem × 2
       vertical padding + 1 px border-top on .activity-panel. */
    --activity-strip-h: 36px;
}

/* ── All-units override toggle ────────────────────────────────────
   Pill button now lives at the bottom of the Units search dropdown
   (#unit-results), below the picker and Apply button. Visible only
   when ob_sentinel.CanChooseAllLocation = 1 for the current Trust.
   OFF state is a quiet outline pill; ON state highlights with the
   accent so the manager can see at a glance the override is active. */

.unit-results-override {
    border-top: 1px solid var(--rule);
    padding: 0.75rem 0.9rem;
    background: var(--bg);
    display: flex;
}
.unit-results-override .all-units-toggle {
    width: 100%;
    justify-content: center;
}
.all-units-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0 0.85rem;
    height: 31px;
    border-radius: 3px;
    border: 1px solid var(--rule);
    background: var(--panel);
    color: var(--ink-soft);
    font-size: 9px;
    font-weight: 500;
    cursor: pointer;
    white-space: nowrap;
    transition: background 140ms ease, border-color 140ms ease, color 140ms ease;
}
.all-units-toggle:hover {
    background: var(--accent-soft);
    border-color: var(--accent);
    color: var(--ink);
}
.all-units-toggle:focus-visible {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}
.all-units-toggle.is-on {
    background: var(--accent-soft);
    border-color: var(--accent);
    color: var(--ink);
}
.all-units-dot {
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--accent);
    display: inline-block;
}
@media (prefers-reduced-motion: reduce) {
    .all-units-toggle { transition: none; }
}

/* ── Trust multi-select (header) ──────────────────────────────────
   Replaces the old single-select Trust dropdown when the user has
   access to more than one Trust. <details>/<summary> disclosure
   pattern; each ob_sentinel-allowed Trust is a checkbox; toggling
   posts to /switch-trusts which redirects via HX-Redirect. */
.trust-multi { position: relative; }
.trust-multi-summary {
    list-style: none;
    cursor: pointer;
    padding: 0.4rem 0.75rem;
    border-radius: 3px;
    border: 1px solid var(--rule);
    background: var(--panel);
    color: var(--ink);
    font-size: 11px;
    line-height: 1.2;
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    user-select: none;
    transition: background 140ms ease, border-color 140ms ease;
}
.trust-multi-summary::-webkit-details-marker { display: none; }
.trust-multi-summary:hover { background: var(--accent-soft); border-color: var(--accent); }
.trust-multi-summary:focus-visible {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}
.trust-multi[open] .trust-multi-summary {
    background: var(--accent-soft);
    border-color: var(--accent);
}
.trust-multi-label { font-weight: 500; }
.trust-multi-caret {
    font-size: var(--ui-arrow-size);
    font-weight: var(--ui-arrow-weight);
    color: var(--ink-soft);
    line-height: 1;
    transition: transform 140ms ease;
}
.trust-multi[open] .trust-multi-caret { transform: rotate(180deg); }
/* Popover is a flex column - actions row stays at the top, the
   long list of Trust rows below scrolls independently. Keeps the
   Apply / Clear icon buttons in view however far the user scrolls. */
.trust-multi-popover {
    position: absolute;
    right: 0;
    top: calc(100% + 3px);
    min-width: 214px;
    max-height: 241px;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    background: var(--panel);
    border: 1px solid var(--rule);
    border-radius: 4px;
    box-shadow: 0 5px 13px rgba(16, 42, 67, 0.12);
    padding: 0.4rem 0;
    z-index: 100;
    margin: 0;
}
.trust-multi-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.9rem;
    border-bottom: 1px solid var(--rule);
    margin-bottom: 0.25rem;
    font-size: 9px;
    flex: 0 0 auto;  /* keep at top, never compress */
}
.trust-multi-actions-hint {
    flex: 1 1 auto;
    font-size: 8px;
    color: var(--ink-soft);
    font-style: italic;
}
.trust-multi-rows {
    display: flex;
    flex-direction: column;
    /* Only this section scrolls - actions above stay anchored. */
    flex: 1 1 auto;
    overflow-y: auto;
    min-height: 0;
}
.trust-multi-row {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.45rem 0.9rem;
    cursor: pointer;
    font-size: 10px;
    color: var(--ink);
}
.trust-multi-row:hover { background: var(--accent-soft); }
.trust-multi-row input[type=checkbox] {
    accent-color: var(--accent);
    width: 11px;
    height: 11px;
}
/* Hard cap visual - when 3 Trusts are selected, the remaining
   unchecked rows go semi-transparent + non-interactive so the user
   sees the limit without having to read the hint. Already-ticked
   rows stay enabled so the user can swap their selection. */
.trust-multi-row.is-disabled {
    opacity: 0.4;
    cursor: not-allowed;
}
.trust-multi-row.is-disabled input[type=checkbox] { pointer-events: none; }
@media (prefers-reduced-motion: reduce) {
    .trust-multi-summary, .trust-multi-caret { transition: none; }
}

/* Trust column on the multi-Trust Staff grid. Only emitted when
   Model.ShowTrustColumn is true. Slight ink-soft tint to distinguish
   it from the per-row data. */
.balance-grid td.trust-cell {
    font-weight: 500;
    color: var(--ink);
    white-space: nowrap;
}

/* ── Account menu (header top-right) ──────────────────────────────
   Native <details>/<summary> disclosure popover. Trust selector stays
   on the strip; this captures user email, name, the optional Admin
   link, and Sign out. Click-outside closes via a small JS hook in
   _Layout.cshtml. */
.user-menu {
    position: relative;
}
.user-menu-trigger {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    list-style: none;
    cursor: pointer;
    padding: 0.4rem 0.75rem;
    border-radius: 3px;
    border: 1px solid var(--rule);
    background: var(--panel);
    color: var(--ink);     /* user's email/name is identity - primary text */
    font-size: 11px;
    line-height: 1.2;
    user-select: none;
    transition: background 140ms ease, border-color 140ms ease;
}
.user-menu-trigger::-webkit-details-marker { display: none; }
.user-menu-trigger:hover {
    background: var(--accent-soft);
    border-color: var(--accent);
}
.user-menu-trigger:focus-visible {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}
.user-menu[open] .user-menu-trigger {
    background: var(--accent-soft);
    border-color: var(--accent);
}
.user-menu-trigger .user-email {
    font-weight: 500;
}
.user-menu-caret {
    font-size: var(--ui-arrow-size);
    font-weight: var(--ui-arrow-weight);
    color: var(--ink-soft);
    line-height: 1;
    transition: transform 140ms ease;
}
.user-menu[open] .user-menu-caret { transform: rotate(180deg); }
.user-menu-popover {
    position: absolute;
    right: 0;
    top: calc(100% + 3px);
    min-width: 214px;
    background: var(--panel);
    border: 1px solid var(--rule);
    border-radius: 4px;
    box-shadow: 0 5px 13px rgba(16, 42, 67, 0.12);
    padding: 0.35rem 0;
    z-index: 100;
}
.user-menu-name {
    padding: 0.55rem 0.9rem 0.4rem;
    font-size: 11px;
    font-weight: 600;
    color: var(--ink);
    border-bottom: 1px solid var(--rule);
    margin-bottom: 0.25rem;
}
.user-menu-item {
    display: block;
    width: 100%;
    text-align: left;
    background: transparent;
    border: 0;
    padding: 0.55rem 0.9rem;
    font-size: 11px;
    color: var(--ink);
    text-decoration: none;
    cursor: pointer;
}
.user-menu-item:hover { background: var(--accent-soft); }
.user-menu-item:focus { outline: none; background: var(--accent-soft); }
.user-menu-form { margin: 0; padding: 0; }
.user-menu-signout { color: var(--ink-soft); border-top: 1px solid var(--rule); margin-top: 0.25rem; padding-top: 0.55rem; }
@media (prefers-reduced-motion: reduce) {
    .user-menu-caret { transition: none; }
}

/* ── Admin: users & access ──────────────────────────────────────── */
.admin-page {
    padding: 1.25rem 1.5rem;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}
/* Default banner shape - info-blue family. Used on every admin
   page above the form / grid. Two semantic variants live further
   down: .admin-banner-success (green), .admin-banner-error (red).
   Originally defined twice in this file at conflicting positions;
   merged here so the rule is one block. */
.admin-banner {
    background: var(--accent-soft);
    border: 1px solid var(--accent);
    border-radius: 4px;
    padding: 0.7rem 1rem;
    margin: 0 0 1rem;
    color: var(--ink);
    font-size: 10px;
}
.admin-form {
    background: var(--panel);
    border: 1px solid var(--rule);
    border-radius: 4px;
    padding: 1rem 1.25rem 1.25rem;
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
}
.admin-form-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
}
.admin-form-header h2 {
    margin: 0;
    font-size: 1.1rem;
    font-weight: 600;
}
.admin-form-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(174px, 1fr));
    gap: 0.85rem 1rem;
}
.admin-field {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    /* Bumped from 13 px to 15 px to match the Staff grid toolbar
       label sizing - the admin pages were rendering noticeably
       tinier and the field labels were the worst offender. */
    font-size: 10px;
    color: var(--ink-soft);
}
.admin-field > span,
.admin-field > legend {
    font-weight: 600;
    font-size: 9px;
    color: var(--ink-soft);
}
.admin-field input[type=text],
.admin-field input[type=email],
.admin-field input[type=search],
.admin-field select {
    height: 31px;
    padding: 0 0.85rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    font-size: 11px;
    color: var(--ink);
    background: var(--panel);
    transition: border-color 140ms ease, box-shadow 140ms ease;
}
.admin-field input:hover,
.admin-field select:hover { border-color: var(--accent); }
.admin-field input:focus-visible,
.admin-field select:focus-visible {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}
.admin-field-hint {
    font-size: 8px;
    color: var(--faint);
}
.admin-flags-field {
    border: 1px solid var(--rule);
    border-radius: 3px;
    padding: 0.5rem 0.75rem;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}
.admin-checkbox {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 9px;
    color: var(--ink);
    cursor: pointer;
}
.admin-checkbox input { accent-color: var(--accent); }

.admin-units {
    border-top: 1px solid var(--rule);
    padding-top: 0.85rem;
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}
.admin-units-header h3 {
    margin: 0;
    font-size: 10px;
    font-weight: 600;
    color: var(--ink);
}
.admin-unit-search { position: relative; }
.admin-unit-search input[type=search] {
    width: 100%;
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    font-size: 9px;
}
.admin-unit-search input[type=search]:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}
.admin-unit-results:empty { display: none; }
.admin-unit-results {
    margin-top: 0.4rem;
    max-height: 188px;
    overflow-y: auto;
    border: 1px solid var(--rule);
    border-radius: 3px;
    background: var(--panel);
    padding: 0.5rem 0.75rem;
}
.admin-unit-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    min-height: 17px;
}
.admin-chip { background: var(--accent-soft); border: 1px solid var(--accent); }
.admin-chip-empty { font-size: 9px; color: var(--ink-soft); }

.admin-form-actions {
    display: flex;
    gap: 0.6rem;
    align-items: center;
}

.admin-list { display: flex; flex-direction: column; gap: 0.5rem; }
.admin-grid td { vertical-align: middle; }
.admin-person-first td { border-top: 1px solid var(--rule); }
.admin-row-inactive td { color: var(--faint); }
.admin-trust-id { color: var(--faint); font-size: 8px; margin-left: 0.25rem; }
.admin-flags { display: flex; flex-wrap: wrap; gap: 0.3rem; }
.admin-flag {
    background: var(--bg);
    border: 1px solid var(--rule);
    border-radius: 2px;
    padding: 0.1rem 0.4rem;
    font-size: 7px;
    color: var(--ink-soft);
}
.admin-flag-strong {
    background: var(--accent-soft);
    border-color: var(--accent);
    color: var(--ink);
}
.admin-pill {
    display: inline-block;
    padding: 0.15rem 0.55rem;
    border-radius: 7px;
    font-size: 8px;
    font-weight: 600;
}
.admin-pill-active   { background: var(--rag-green-soft-bg); color: var(--rag-green); border: 1px solid var(--rag-green-soft-border); }
.admin-pill-inactive { background: var(--rag-red-soft-bg); color: var(--rag-red); border: 1px solid var(--rag-red-soft-border); }
.admin-actions-col { white-space: nowrap; }
.admin-inline-form { display: inline; margin: 0; padding: 0; }

/* Login-audit row badges - colour-coded by event type. Pastel only so
   the grid still reads at a glance; pair colour with the textual label
   for colour-blind users. */
.audit-badge {
    display: inline-block;
    padding: 0.1rem 0.5rem;
    border-radius: 7px;
    font-size: 8px;
    font-weight: 600;
    border: 1px solid var(--rule);
    background: var(--bg);
    color: var(--ink-soft);
}
.audit-badge-ok    { background: var(--rag-green-soft-bg); border-color: var(--rag-green-soft-border); color: var(--rag-green); }
.audit-badge-err   { background: var(--rag-red-soft-bg); border-color: var(--rag-red-soft-border); color: var(--rag-red); }
.audit-badge-muted { background: var(--bg);  border-color: var(--rule); color: var(--ink-soft); }
.audit-badge-info  { background: var(--accent-soft); border-color: var(--accent); color: var(--ink); }
.audit-ip { font-family: var(--mono); font-size: 9px; color: var(--ink-soft); }
.audit-ua { font-size: 8px; color: var(--ink-soft); max-width: 214px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ── Activity bar (always-visible compact strip) ─────────────────
   One thin row at the bottom of every signed-in page. Lozenge on
   the left, most-recent action inline next to it, Clear on the
   right. Clicking anywhere on the strip expands the bar UPWARDS
   to show the previous 4 rows above the strip (5 rows total).
   Esc or any click outside collapses it back. */
.activity-panel {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 1499;
    background: var(--panel);
    border-top: 1px solid var(--rule);
    display: flex;
    flex-direction: column;
    transition: max-height 180ms ease;
}
.activity-history {
    overflow-y: auto;
    max-height: 0;
    transition: max-height 180ms ease;
    border-bottom: 1px solid var(--rule);
}
/* Expanded panel reserves up to 40% of the viewport for history
   rows and always shows a vertical scrollbar so the user can
   scroll back through everything that's been logged - the strip
   below only ever shows the latest, the rest live here. */
.activity-panel.is-expanded .activity-history {
    max-height: 40vh;
    overflow-y: scroll;
}
.activity-strip {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    padding: 0.45rem 0.95rem;
    cursor: pointer;
    min-height: 29px;
    user-select: none;
}
.activity-lozenge {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.25rem 0.85rem;
    height: 21px;
    border-radius: 669px;
    background: var(--accent-soft);
    border: 1px solid var(--accent);
    color: var(--ink);
    font-weight: 600;
    font-size: 9px;
    flex: 0 0 auto;
}
.activity-lozenge-count {
    display: inline-block;
    min-width: 12px;
    padding: 0 4px;
    background: var(--accent);
    color: white;
    border-radius: 669px;
    font-size: 8px;
    font-weight: 700;
    line-height: 12px;
    text-align: center;
}
.activity-lozenge-arrow {
    font-size: 16px;
    font-weight: var(--ui-arrow-weight);
    color: var(--accent-hover);
    line-height: 1;
    margin-left: 0.2rem;
}
.activity-panel.is-expanded .activity-lozenge-arrow { transform: rotate(180deg); }
.activity-latest {
    flex: 1 1 auto;
    display: grid;
    grid-template-columns: 47px minmax(94px, 147px) 1fr 47px;
    gap: 0.85rem;
    align-items: center;
    font-size: 9px;
    color: var(--ink);
    overflow: hidden;
    min-width: 0;
}
.activity-latest-empty {
    grid-column: 1 / -1;
    color: var(--ink-soft);
    font-style: italic;
}
.activity-latest .activity-time {
    font-family: var(--mono);
    color: var(--ink-soft);
    font-size: 9px;
    font-variant-numeric: tabular-nums;
}
.activity-latest .activity-level {
    font-weight: 600;
    color: var(--ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.activity-latest .activity-msg {
    color: var(--ink-soft);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.activity-latest .activity-duration {
    font-family: var(--mono);
    color: var(--ink-soft);
    font-size: 9px;
    text-align: right;
    font-variant-numeric: tabular-nums;
}
.activity-strip-actions {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    flex: 0 0 auto;
}
.activity-strip-action {
    background: transparent;
    border: 1px solid var(--rule);
    border-radius: 3px;
    padding: 0 0.85rem;
    height: 21px;
    color: var(--ink-soft);
    font: inherit;
    font-size: 9px;
    font-weight: 600;
    cursor: pointer;
}
.activity-strip-action:hover {
    background: var(--accent-soft);
    border-color: var(--accent);
    color: var(--ink);
}
/* History list above the strip - one row per entry, same grid
   shape as the latest-row inline so columns line up. */
.activity-history-row {
    display: grid;
    grid-template-columns: 64px 54px minmax(94px, 147px) 1fr 47px 19px;
    gap: 0.85rem;
    align-items: center;
    padding: 0.4rem 1rem;
    border-bottom: 1px solid var(--rule);
    font-size: 9px;
    color: var(--ink);
}
.activity-history-row:last-child { border-bottom: none; }
.activity-history-row .activity-time {
    color: var(--ink-soft);
    font-family: var(--mono);
    font-size: 9px;
    font-variant-numeric: tabular-nums;
}
.activity-history-row .activity-pad { visibility: hidden; }
.activity-history-row .activity-level {
    font-weight: 600;
    color: var(--ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.activity-history-row .activity-msg {
    color: var(--ink-soft);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.activity-history-row .activity-duration {
    color: var(--ink-soft);
    font-family: var(--mono);
    font-size: 9px;
    text-align: right;
    font-variant-numeric: tabular-nums;
}
.activity-panel-header {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 0.75rem 1.25rem;
    border-bottom: 1px solid var(--rule);
    background: var(--bg);
}
.activity-panel-title { font-weight: 600; color: var(--ink); font-size: 13px; }
.activity-panel-sub   { font-size: 11px; color: var(--ink-soft); flex: 1; }
.activity-panel-action {
    background: var(--panel);
    border: 1px solid var(--rule);
    border-radius: 3px;
    padding: 0.55rem 1.1rem;
    font: inherit;
    font-size: 11px;
    color: var(--ink);
    cursor: pointer;
    min-height: 28px;
    transition: background 120ms ease, border-color 120ms ease;
}
.activity-panel-action:hover { background: var(--accent-soft); border-color: var(--accent); }
.activity-panel-action:focus-visible {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}
.activity-panel-close {
    background: transparent;
    border: 1px solid transparent;
    color: var(--ink-soft);
    font-size: 20px;
    line-height: 1;
    cursor: pointer;
    width: 28px;
    height: 28px;
    border-radius: 3px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.activity-panel-close:hover { background: var(--accent-soft); color: var(--ink); border-color: var(--rule); }
.activity-panel-close:focus-visible {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}

.activity-list {
    overflow-y: auto;
    flex: 1 1 auto;
    font-size: 11px;
    color: var(--ink);
}
.activity-empty {
    padding: 1.25rem;
    color: var(--ink-soft);
    font-style: italic;
    font-size: 12px;
}
/* Columns: time · action title · detail · duration · replay.
   Body text uses --ink (primary), only the secondary metadata
   (time + duration) drops to --ink-soft. The .activity-history-row
   selector above carries the grid layout for the actually-used
   row variant; the original .activity-row was removed once the
   history strip became the only consumer. */
.activity-time {
    color: var(--ink-soft);
    font-family: var(--mono);
    font-size: 10px;
    font-variant-numeric: tabular-nums;
}
.activity-level {
    font-weight: 600;
    color: var(--ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.activity-msg {
    color: var(--ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.activity-duration {
    color: var(--ink-soft);
    font-family: var(--mono);
    font-variant-numeric: tabular-nums;
    text-align: right;
    font-size: 10px;
}
.activity-error .activity-level { color: var(--rag-red); }
.activity-warn  .activity-level { color: var(--rag-amber); }
.activity-info  .activity-level { color: var(--ink); }

/* Replay control - only emitted on rows that map to a re-runnable
   GET (currently /balances/grid). Clicking it re-syncs the toolbar
   inputs and re-fires the same request. */
.activity-replay {
    background: transparent;
    border: 1px solid var(--rule);
    border-radius: 50%;
    width: 24px;
    height: 24px;
    color: var(--ink-soft);
    cursor: pointer;
    font-size: 13px;
    line-height: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background 120ms ease, border-color 120ms ease, color 120ms ease, transform 200ms ease;
}
.activity-replay:hover {
    background: var(--accent-soft);
    border-color: var(--accent);
    color: var(--ink);
    transform: rotate(90deg);
}
.activity-replay:focus-visible {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}
.activity-replay-placeholder { display: inline-block; width: 24px; height: 24px; }
@media (prefers-reduced-motion: reduce) {
    .activity-replay { transition: none; }
    .activity-replay:hover { transform: none; }
}
@media (prefers-reduced-motion: reduce) {
    .activity-panel { animation: none; }
}

/* Screen-reader-only utility - visible labels for headings/buttons that
   are graphically obvious to sighted users (icons, empty drill columns)
   but otherwise opaque to assistive tech. */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* ── Staff details page ──────────────────────────────────────────── */

.staff-page {
    padding: 1.25rem 1.5rem;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.staff-header {
    background: var(--panel);
    border: 1px solid var(--rule);
    border-radius: 4px;
    padding: 1.25rem 1.5rem;
}

.staff-header-top {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
}

.staff-identity { min-width: 0; }

.staff-back {
    color: var(--accent);
    text-decoration: none;
    font-size: 9px;
}

.staff-back:hover { text-decoration: underline; }

.staff-name {
    font-family: var(--display);
    font-weight: 600;
    font-size: 19px;
    margin: 0.35rem 0 0.15rem 0;
    color: var(--ink);
    letter-spacing: -0.01em;
}

.staff-subtitle {
    margin: 0;
    color: var(--ink-soft);
    font-size: 10px;
}

.staff-subtitle .sep { margin: 0 0.5rem; color: var(--faint); }

.stat-strip {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 0.75rem;
    margin-top: 1.1rem;
    padding-top: 1.1rem;
    border-top: 1px solid var(--rule);
}

.stat {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}

.stat-label {
    color: var(--ink-soft);
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-weight: 600;
}

.stat-value {
    color: var(--ink);
    font-size: 15px;
    font-weight: 600;
}

.stat-value.num {
    font-family: var(--mono);
    font-variant-numeric: tabular-nums;
}

.stat-meta {
    color: var(--ink-soft);
    font-size: 9px;
}

.stat-value.balance-red { color: var(--rag-red); }
.stat-value.balance-green { color: var(--rag-green); }

/* Reconciliation lozenges in the Units grid. Default to grey ("pending");
   the wired-up version flips to green on approval. */
.lozenge-col { text-align: center; width: 80px; }

/* Sign-off button on the Units grid - shaped like the Staff grid's
   Generate button so the two read as the same kind of row-level
   action. Two states: pending (accent outlined, clickable) and
   signed (filled green, opens audit). Replaces the old round
   lozenge. */
.signoff-button {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    background: transparent;
    border: 1px solid var(--accent);
    color: var(--accent);
    cursor: pointer;
    font: inherit;
    font-size: 9px;
    padding: 0.35rem 0.7rem;
    border-radius: 3px;
    white-space: nowrap;
    transition: background 140ms ease, color 140ms ease, border-color 140ms ease, box-shadow 140ms ease;
}
.signoff-button:hover {
    background: var(--accent);
    color: #fff;
}
.signoff-button:focus-visible {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}
.signoff-button-signed {
    /* Pastel-blue "signed" state - same visual family as the
       identity column headers. Reads as "done + audit trail
       available" rather than as a status colour. */
    border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 15%, var(--panel));
    color: var(--accent);
    font-weight: 600;
}
.signoff-button-signed:hover {
    background: var(--accent);
    color: #fff;
}
.signoff-button.just-signed { animation: lozenge-just-signed 600ms cubic-bezier(0.2, 0.8, 0.2, 1); }

/* Optimistic-flip state - applied by JS the instant a sign-off
   POST goes out (one-click lozenge OR modal Submit). Looks the
   same as a real signed lozenge so the user sees confirmation
   without waiting for the server response. The grid swap that
   comes back via balances-refresh replaces this wholesale; on
   failure the class is removed and the lozenge reverts to its
   pending state. */
.signoff-button.is-optimistically-signed {
    border-color: var(--rag-green-strong);
    color: var(--rag-green-strong);
    /* Subtle pulse confirms the click registered. Override the
       generic .is-button-busy dim so the flash reads clearly. */
    animation: lozenge-just-signed 500ms cubic-bezier(0.2, 0.8, 0.2, 1);
    opacity: 1;
}
.signoff-button.is-optimistically-signed.is-button-busy {
    opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
    .signoff-button.is-optimistically-signed { animation: none; }
}

.lozenge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 15px;
    height: 15px;
    border-radius: 50%;
    font-size: 7px;
    font-weight: 700;
    color: white;
    line-height: 1;
}

/* Pending = needs the ward manager's attention (red); approved = signed
   off for the current year (green). Rejected reserved for future use. */
.lozenge-pending { background: var(--rag-red-strong); }
.lozenge-approved { background: var(--rag-green-strong); }
.lozenge-rejected { background: var(--rag-red-strong); }

.lozenge-button {
    border: none;
    cursor: pointer;
    padding: 0;
    transition: filter 140ms ease, transform 140ms ease, box-shadow 140ms ease;
}

.lozenge-button:hover {
    filter: brightness(1.1);
}
.lozenge-button:focus-visible {
    outline: none;
    box-shadow: 0 0 0 2px var(--accent-soft);
    border-radius: 50%;
}

/* Brief pulse the first time a pending lozenge transitions to signed
   via the quick-action POST - visual confirmation the click landed. */
.lozenge.just-signed { animation: lozenge-just-signed 600ms cubic-bezier(0.2, 0.8, 0.2, 1); }
@keyframes lozenge-just-signed {
    0%   { transform: scale(0.85); box-shadow: 0 0 0 0 rgba(30, 91, 39, 0.55); }
    45%  { transform: scale(1.18); box-shadow: 0 0 0 5px rgba(30, 91, 39, 0); }
    100% { transform: scale(1);    box-shadow: 0 0 0 0 rgba(30, 91, 39, 0); }
}

/* Sign-off modal */
.signoff-form {
    position: relative;
    padding: 1.4rem 1.5rem 1.2rem;
    min-width: min(375px, 92vw);
    max-width: 482px;
}

.signoff-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 1rem;
}

.signoff-eyebrow {
    display: block;
    color: var(--ink-soft);
    font-size: 9px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.signoff-title {
    font-family: var(--display);
    margin: 0.15rem 0 0 0;
    font-size: 15px;
    font-weight: 600;
}

.signoff-status { margin-bottom: 1rem; }

.signoff-current {
    padding: 0.75rem 1rem;
    border-radius: 4px;
    border: 1px solid var(--rule);
    font-size: 10px;
}

.signoff-current-signed   { background: var(--rag-green-soft-bg); border-color: var(--rag-green-soft-border); color: var(--rag-green); }
.signoff-current-pending  { background: var(--bg); }

/* Sign-off preview - shows the manager what the per-unit totals look
   like before they commit. Identical aggregates land in the snapshot. */
.signoff-preview {
    margin: 1rem 0;
    padding: 0.85rem 1rem;
    border: 1px solid var(--rule);
    border-radius: 4px;
    background: var(--bg);
}
.signoff-preview-title {
    font-size: 9px;
    color: var(--ink);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-weight: 600;
    margin-bottom: 0.5rem;
}
.signoff-preview-stats {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 0.5rem 0.75rem;
}
.signoff-preview-stat {
    display: flex;
    flex-direction: column;
}
.signoff-preview-label {
    font-size: 9px;
    color: var(--ink-soft);
    font-weight: 500;
}
.signoff-preview-value {
    font-size: 15px;
    font-weight: 600;
}
.signoff-preview-meta {
    font-size: 9px;
    color: var(--ink-soft);
}
.signoff-preview-hint {
    margin: 0.6rem 0 0;
    font-size: 8px;
    color: var(--ink-soft);
    font-style: italic;
}

.signoff-note {
    margin-top: 0.4rem;
    font-style: italic;
    color: var(--ink);
}

.signoff-form-row {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    margin-bottom: 0.75rem;
}

.signoff-form-row label {
    font-size: 9px;
    color: var(--ink-soft);
    font-weight: 500;
}

.signoff-note-input {
    width: 100%;
    padding: 0.6rem 0.8rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    font: inherit;
    background: var(--panel);
    resize: vertical;
}

/* Attestation block - the wording the Ward Manager accepts when
   they sign off. Sits just above the action buttons and keeps the
   prose visible without dominating the modal. The text uses
   white-space: pre-line so the source string's newlines + leading
   ' - ' bullets render naturally. */
.signoff-attestation {
    margin: 0.85rem 0 0.5rem 0;
    padding: 0.75rem 0.9rem;
    border: 1px solid var(--accent);
    border-radius: 4px;
    background: var(--accent-soft);
}
.signoff-attestation-text {
    font-size: 9px;
    line-height: 1.45;
    color: var(--ink);
    white-space: pre-line;
    margin-bottom: 0.6rem;
}
.signoff-attestation-ack {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    cursor: pointer;
    font-size: 9px;
    font-weight: 600;
    color: var(--ink);
}
.signoff-attestation-ack input[type=checkbox] {
    width: 12px;
    height: 12px;
    accent-color: var(--accent);
    cursor: pointer;
}
/* Type-name e-signature - the second factor on top of the tick.
   Visually subordinate to the attestation text but unmistakeable
   when its match state flips, so the manager knows when they can
   submit. .is-ok = name typed and matches; .is-bad = typed but
   doesn't match yet. */
.signoff-typed-name-row {
    margin-top: 0.85rem;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}
.signoff-typed-name-label {
    font-size: 10px;
    font-weight: 600;
    color: var(--ink);
}
.signoff-typed-name-input {
    height: 31px;
    padding: 0 0.85rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    background: var(--panel);
    color: var(--ink);
    font: inherit;
    font-size: 11px;
    transition: border-color 140ms ease, box-shadow 140ms ease;
}
.signoff-typed-name-input:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}
.signoff-typed-name-hint {
    font-size: 10px;
    color: var(--ink-soft);
}
.signoff-typed-name-hint.is-ok {
    color: var(--rag-green-strong);
    font-weight: 600;
}
.signoff-typed-name-hint.is-ok::before { content: "✓ "; }
.signoff-typed-name-hint.is-bad {
    color: var(--rag-red-strong);
}
.signoff-typed-name-hint.is-bad::before { content: "✗ "; }
/* Disabled primary button reads as inert - so the manager has a
   clear visual cue that the box must be ticked first. */
.primary-button[disabled] {
    background: var(--rule);
    border-color: var(--rule);
    color: var(--ink-soft);
    cursor: not-allowed;
}

.signoff-actions {
    display: flex;
    justify-content: flex-end;
    gap: 0.75rem;
    margin-top: 1rem;
    padding-top: 0.75rem;
    border-top: 1px solid var(--rule);
}

.signoff-history { margin-top: 1.25rem; }

.signoff-history summary {
    cursor: pointer;
    color: var(--ink-soft);
    font-size: 9px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: 0.4rem 0;
}

/* Inline edit-in-place for the Note column on /sign-offs. View mode is
   the persisted text + a small ✎ button that swaps the TD for the edit
   form. Edit form: input + Save (✓) + Cancel (✕). Escape on the input
   also cancels via an hx-on:keydown handler in the partial. */
.signoff-note-view {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    min-height: 15px;
}
.signoff-note-text {
    flex: 1;
    color: var(--ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.signoff-note-edit-btn { opacity: 0.55; flex: 0 0 auto; }
.signoff-note-cell:hover .signoff-note-edit-btn,
.signoff-note-edit-btn:focus-visible { opacity: 1; }
.signoff-note-edit {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    margin: 0;
    padding: 0;
}
.signoff-note-input-inline {
    flex: 1;
    min-width: 0;
    padding: 0.25rem 0.5rem;
    border: 1px solid var(--accent);
    border-radius: 3px;
    font: inherit;
    font-size: 10px;
    background: var(--panel);
    color: var(--ink);
    box-shadow: 0 0 0 2px var(--accent-soft);
}
.signoff-note-input-inline:focus {
    outline: none;
}
.signoff-note-cell.is-editing { background: var(--accent-soft); }
.signoff-note-save  { color: var(--rag-green); }
.signoff-note-cancel { color: var(--ink-soft); }

dialog#detail-modal {
    border: 1px solid var(--rule);
    border-radius: 5px;
    padding: 0;
    background: var(--panel);
    color: var(--ink);
    box-shadow: 0 8px 21px rgba(16, 42, 67, 0.18);
    max-width: 92vw;
}

/* Prominent loading badge anchored to the right end of the
   filter row. Hidden until .is-busy is toggled on by the same
   inflight tracker that drives the page-progress bar; provides
   a far more visible 'something is happening' cue for parm
   changes that otherwise look like a frozen toolbar for 1-3 s.
   Pulse on the accent dot draws the eye without being noisy. */
.filter-loading-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
    margin-left: auto;
    padding: 0.5rem 0.95rem;
    background: var(--accent);
    border: 1px solid var(--accent);
    border-radius: 669px;
    color: #fff;
    font-weight: 600;
    font-size: 10px;
    opacity: 0;
    transform: translateY(-1px);
    pointer-events: none;
    transition: opacity 160ms ease, transform 160ms ease;
}
.filter-loading-badge.is-busy {
    opacity: 1;
    transform: translateY(0);
}
.filter-loading-spinner {
    display: inline-block;
    width: 11px;
    height: 11px;
    border: 1px solid rgba(255, 255, 255, 0.35);
    border-top-color: #fff;
    border-right-color: #fff;
    border-radius: 50%;
    animation: spinner-spin 0.7s linear infinite;
    flex: 0 0 auto;
}
.filter-loading-label {
    letter-spacing: 0.02em;
}
@media (prefers-reduced-motion: reduce) {
    .filter-loading-badge { transition: none; }
    .filter-loading-spinner { animation: none; }
}

/* ── Column-header trigger + shared context menu ────────────────
   Each sortable/filterable column header is a single button with
   a small caret hint; clicking opens #col-header-menu (Sort A->Z /
   Sort Z->A / Filter... / Clear filter) positioned below it. The
   approach scales to narrow columns where a per-column chevron
   would overflow. Filtered state tints the whole header cell so a
   glance shows which columns are constraining the visible rows. */
.col-header-trigger {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    background: transparent;
    border: none;
    padding: 3px 4px;
    margin: 0;
    color: inherit;
    font: inherit;
    text-transform: inherit;
    letter-spacing: inherit;
    cursor: pointer;
    min-height: 24px;
    width: 100%;
    text-align: inherit;
    border-radius: 3px;
    transition: background 140ms ease;
}
.balance-grid thead th.num .col-header-trigger {
    justify-content: flex-end;
}
.col-header-trigger:focus-visible {
    outline: none;
}
/* Hover/focus background sits on the parent <th> via :has() further
   down so the WHOLE header cell darkens (rgba overlay), not just the
   button's content box. Without that the th's 0.85rem horizontal
   padding leaves an unhighlighted gutter, painfully visible on narrow
   columns like Contract and Roster Hrs. */
/* Filtered-state visual cue. Pale tint fills the whole header
   cell; a 3px accent-coloured bottom stripe is the strong glanceable
   signal. No icons (Greg's rule: never small icons). */
.balance-grid thead th.col-filtered {
    background: var(--accent-soft);
    box-shadow: inset 0 -3px 0 var(--accent);
}

.col-header-menu {
    position: fixed;
    margin: 0;
    padding: 4px;
    border: 1px solid var(--rule);
    border-radius: 4px;
    background: var(--panel);
    box-shadow: 0 5px 16px rgba(0, 0, 0, 0.18);
    min-width: 134px;
    z-index: 1000;
    list-style: none;
    display: flex;
    flex-direction: column;
}
.col-header-menu[hidden] { display: none; }
.col-header-menu-item {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 5px 8px;
    min-height: 27px;
    background: transparent;
    border: none;
    text-align: left;
    font: inherit;
    font-size: 10px;
    color: var(--ink);
    cursor: pointer;
    border-radius: 3px;
    transition: background 120ms ease;
}
.col-header-menu-item[hidden] { display: none; }
.col-header-menu-item:hover:not(:disabled),
.col-header-menu-item:focus-visible:not(:disabled) {
    background: var(--accent-soft);
    outline: none;
}
.col-header-menu-item:disabled {
    color: var(--faint);
    cursor: default;
    opacity: 0.6;
}
.col-header-menu-glyph {
    flex: 0 0 12px;
    width: 12px;
    height: 12px;
    text-align: center;
    color: var(--ink-soft);
    line-height: 1;
}
.col-header-menu-item:hover:not(:disabled) .col-header-menu-glyph {
    color: var(--accent);
}
.col-header-menu-clear .col-header-menu-glyph {
    color: var(--rag-red);
}

/* Column-filter dialogs ride on the .unit-picker-dialog chrome
   (header + frame + toolbar at top), so they're narrower than the
   true multi-select pickers but share the search-and-actions row. */
.col-filter-dialog.unit-picker-dialog { width: min(322px, 96vw); }
.col-filter-form-modal { /* inherits .unit-picker-modal */ }
.col-filter-body {
    padding: 0.9rem 1rem 1rem;
}
/* Numeric dialogs put the spacer on the left of the toolbar so the
   Clear and Apply buttons hug the right edge - same visual rhythm
   as the text dialogs where the search input fills that space. */
.unit-picker-toolbar-spacer { flex: 1 1 auto; }

.col-filter-presets {
    margin: 0 0 1rem;
    padding: 0.6rem 0.75rem 0.75rem;
    background: var(--accent-soft);
    border-radius: 3px;
}
.col-filter-presets-head,
.col-filter-custom-head {
    margin: 0 0 0.6rem;
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--ink-soft);
    font-weight: 600;
}
.col-filter-preset {
    display: block;
    width: 100%;
    min-height: 27px;
    text-align: left;
    background: var(--panel);
    border: 1px solid var(--rule);
    color: var(--ink);
    padding: 0.6rem 0.8rem;
    margin-bottom: 0.45rem;
    border-radius: 3px;
    font: inherit;
    font-size: 10px;
    cursor: pointer;
    transition: background 140ms ease, border-color 140ms ease;
}
.col-filter-preset:last-child { margin-bottom: 0; }
.col-filter-preset:hover,
.col-filter-preset:focus-visible {
    background: var(--panel);
    border-color: var(--accent);
    color: var(--accent-hover);
}
.col-filter-custom {
    border-top: 1px dashed var(--rule);
    padding-top: 0.85rem;
}
.col-filter-fieldset {
    border: none;
    padding: 0;
    margin: 0 0 0.85rem;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.4rem 1rem;
}
.col-filter-fieldset-inline {
    grid-template-columns: repeat(3, auto);
    gap: 0 0.6rem;
    justify-content: start;
}

/* Compact one-line dialog variant: no header, no framed body, just
   radios + clear + apply on a single horizontal strip. Reserved for
   trivial binary / ternary picks where the column-header label
   already names the filter (Manager note: All / With / Without). */
.col-filter-dialog-compact {
    border: 1px solid var(--rule);
    border-radius: 6px;
    background: var(--panel);
    box-shadow: 0 8px 24px rgba(16, 42, 67, 0.18);
    padding: 0;
    width: auto;
    max-width: none;
}
.col-filter-dialog-compact::backdrop {
    background: rgba(16, 42, 67, 0.08);
}
.col-filter-form-compact {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.45rem 0.55rem;
}
.col-filter-form-compact label {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    font-size: 10px;
    padding: 4px 6px;
    border-radius: 3px;
    cursor: pointer;
    white-space: nowrap;
}
.col-filter-form-compact label:hover {
    background: var(--accent-soft);
}
.col-filter-form-compact input[type="radio"] {
    width: 12px;
    height: 12px;
    margin: 0;
}

.col-filter-fieldset legend {
    grid-column: 1 / -1;
    font-size: 10px;
    color: var(--ink-soft);
    padding: 0;
    margin-bottom: 0.4rem;
}
.col-filter-fieldset label {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 10px;
    padding: 5px 5px;
    border-radius: 3px;
    cursor: pointer;
    min-height: 24px;
}
.col-filter-fieldset label:hover {
    background: var(--accent-soft);
}
.col-filter-fieldset input[type="radio"] {
    width: 12px;
    height: 12px;
}
.col-filter-numrow {
    display: flex;
    gap: 1rem;
    align-items: center;
    margin: 0;
    padding: 0.7rem 0.8rem;
    background: var(--bg);
    border-radius: 3px;
}
.col-filter-numrow label {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 10px;
    color: var(--ink-soft);
}
.col-filter-num {
    width: 64px;
    min-height: 24px;
    padding: 0.45rem 0.6rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    font: inherit;
    font-size: 10px;
    background: var(--panel);
    color: var(--ink);
}

/* ── Top-of-viewport in-flight progress bar ─────────────────────
   Lit by JS whenever the inflight Map has any open HTMX request.
   2 px high, accent-coloured, indeterminate shuttle animation so
   a slow request reads as 'still working' even when nothing else
   on the page is changing. Honours reduced-motion - the bar still
   appears (so the user knows something is in flight), it just
   doesn't shuttle. */
.page-progress {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 1px;
    background: transparent;
    z-index: 10000;
    pointer-events: none;
    opacity: 0;
    transition: opacity 120ms ease;
}
.page-progress.is-busy {
    opacity: 1;
    background: linear-gradient(
        90deg,
        transparent 0%,
        var(--accent) 35%,
        var(--accent-hover) 50%,
        var(--accent) 65%,
        transparent 100%);
    background-size: 240% 100%;
    animation: page-progress-shuttle 1100ms linear infinite;
}
@keyframes page-progress-shuttle {
    0%   { background-position: 100% 0; }
    100% { background-position: -100% 0; }
}
@media (prefers-reduced-motion: reduce) {
    .page-progress.is-busy {
        animation: none;
        background: var(--accent);
    }
}

/* ── Ward Guardian badge ────────────────────────────────────────
   Small accent shield rendered next to a unit name when that
   OrgUnitId is in the current user's BarnacleLocString (i.e. the
   user's ob_sentinel scope - the set the Ward Guardian programme
   covers for this user). Lives inline alongside the name so it
   reads as 'this unit, on the WG programme' at a glance. Hover
   title spells it out. */
/* Reserve a fixed-width slot for the 'Hide all Notes' / 'Show all
   Notes' label so the button doesn't change width when toggled
   (which would shift everything after it in the status bar). The
   wider of the two strings is 'Show all Notes' (14 chars). */
.notes-collapse-label {
    display: inline-block;
    min-width: 72px;
    text-align: left;
}

.wg-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-left: 0.45rem;
    color: var(--rag-green-strong);
    vertical-align: -2px;
    line-height: 1;
}
.wg-badge .icon {
    width: 12px;
    height: 12px;
    stroke-width: 2;
}

/* WG-only button in the unit picker action row - shield icon in
   accent green so it visually pairs with the row-level WG shield
   badges below it. */
/* Combined-class specificity (2) so we win over the bare
   .unit-picker-action color rule (specificity 1) regardless of
   source order. Without this the shield icon picks up the dim
   ink-soft action default and reads as almost invisible. */
/* WG shield - 3-state cycle (All -> WG only -> Non-WG -> All).
   Specificity is .unit-picker-action.unit-picker-wg-only (2) so we
   beat the default .unit-picker-action ink-soft regardless of source
   order. */
.unit-picker-action.unit-picker-wg-only {
    color: var(--rag-green-strong);
    border-color: var(--rag-green-soft-border);
}
.unit-picker-action.unit-picker-wg-only:hover {
    background: var(--rag-green-soft-bg);
    border-color: var(--rag-green-strong);
}
.unit-picker-action.unit-picker-wg-only.is-wg-only {
    background: var(--rag-green-strong);
    border-color: var(--rag-green-strong);
    color: #fff;
}
.unit-picker-action.unit-picker-wg-only.is-wg-only:hover {
    background: var(--rag-green-strong);
    color: #fff;
    filter: brightness(0.95);
}
.unit-picker-action.unit-picker-wg-only.is-wg-non {
    background: var(--rag-red-strong);
    border-color: var(--rag-red-strong);
    color: #fff;
}
.unit-picker-action.unit-picker-wg-only.is-wg-non:hover {
    background: var(--rag-red-strong);
    color: #fff;
    filter: brightness(0.95);
}
/* Legacy .is-active state kept for any consumer that hasn't moved
   to the 3-way classes - same look as is-wg-only. */
.unit-picker-action.unit-picker-wg-only.is-active {
    background: var(--rag-green-strong);
    border-color: var(--rag-green-strong);
    color: #fff;
}
.unit-pick.is-wg .wg-badge { margin-left: 0.5rem; }

/* Rollup variant on the Divisions grid: shield + 'X / Y' count.
   Same accent shield as the per-unit badge but with a number
   inline so a row reads 'Surgery [shield] 8 / 14'. */
.wg-rollup {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    margin-left: 0.5rem;
    padding: 1px 5px 1px 3px;
    border: 1px solid var(--rule);
    border-radius: 669px;
    color: var(--accent-hover);
    font-size: 8px;
    font-weight: 600;
    line-height: 1.4;
    background: var(--accent-soft);
}
.wg-rollup .icon { width: 8px; height: 8px; }
.wg-rollup-count { font-variant-numeric: tabular-nums; }

/* ── Active filters strip ───────────────────────────────────────
   Lives between the toolbar and the grid. Hidden entirely when
   no filters are active (so the page stays calm); chips appear
   inline when filters bite. Each chip is a button - clicking it
   removes that filter. Re-fetched via HTMX on every
   balances-refresh so it stays in sync with the grid below it. */
.active-filters {
    display: flex;
    /* Single-row strip - never wraps onto a second line. If more
       chips exist than fit, the strip scrolls horizontally instead
       of pushing the grid down. The Filtering-by label + Clear all
       link stay pinned at either end via flex-shrink: 0 on them. */
    flex-wrap: nowrap;
    align-items: center;
    gap: 0.5rem;
    overflow-x: auto;
    overflow-y: hidden;
    /* Fixed height so the strip never grows the grid vertically -
       same value the chip's own border + padding produces, so it
       lines up cleanly with neighbouring rows. */
    height: 29px;
    padding: 0.25rem 0.25rem;
    margin: 0.25rem 0;
    /* Pale-blue thin scrollbar matching the grid's own. */
    scrollbar-color: color-mix(in srgb, var(--accent) 55%, var(--panel)) transparent;
    scrollbar-width: thin;
}
.active-filters::-webkit-scrollbar { height: 4px; }
.active-filters::-webkit-scrollbar-track { background: transparent; }
.active-filters::-webkit-scrollbar-thumb {
    background: color-mix(in srgb, var(--accent) 55%, var(--panel));
    border-radius: 4px;
}
.active-filters-label,
.active-filter-chip,
.active-filters-clear-all { flex-shrink: 0; }
.active-filters.is-empty {
    display: none;
}
.active-filters-label {
    color: var(--ink-soft);
    font-size: 9px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-right: 0.15rem;
}
.active-filter-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.3rem 0.55rem 0.3rem 0.85rem;
    background: var(--accent-soft);
    border: 1px solid var(--rule);
    border-radius: 669px;
    color: var(--ink);
    font: inherit;
    font-size: 9px;
    line-height: 1.2;
    cursor: pointer;
    transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
}
.active-filter-chip:hover {
    background: var(--panel);
    border-color: var(--accent);
}
.active-filter-chip-key {
    color: var(--ink-soft);
    font-weight: 600;
}
.active-filter-chip-val {
    color: var(--ink);
    font-weight: 500;
    max-width: 28ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.active-filter-chip-close {
    color: var(--ink-soft);
    font-size: 11px;
    line-height: 1;
    margin-left: 0.1rem;
    width: 12px;
    height: 12px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    transition: background 120ms ease, color 120ms ease;
}
.active-filter-chip:hover .active-filter-chip-close {
    background: var(--rag-red-soft-bg);
    color: var(--rag-red);
}
.active-filters-clear-all {
    background: transparent;
    border: 0;
    color: var(--accent-hover);
    cursor: pointer;
    font: inherit;
    font-size: 9px;
    padding: 0.3rem 0.55rem;
    margin-left: 0.25rem;
    text-decoration: underline;
    text-underline-offset: 2px;
}
.active-filters-clear-all:hover { color: var(--rag-red); }

/* ── Per-button busy feedback ───────────────────────────────────
   Any button triggering an HTMX request gets a brief in-flight
   look (slight dim + 'wait' cursor) so the click registers
   instantly even before the server responds. Combined with the
   top-of-viewport progress bar this gives the perception of
   optimistic UI without the complexity of true client-side
   reconciliation. Toggled by inline JS in _Layout.cshtml. */
.is-button-busy {
    opacity: 0.65;
    cursor: progress !important;
    pointer-events: none;
}

/* ── Share-this-view button ─────────────────────────────────────
   Lives next to 'Ask a question' in the toolbar. Click copies a
   URL that recreates the current filter state (units, grade,
   name, asOf, tab) when pasted into another browser. The
   .share-view-flash class is applied for ~1.6 s on success so
   the label can read 'Link copied' without a separate toast. */
.share-view-button {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}
.share-view-button.share-view-flash {
    background: var(--rag-green-soft-bg);
    border-color: var(--rag-green-soft-border);
    color: var(--rag-green);
}
.share-view-label { font-weight: 500; }
@media (prefers-reduced-motion: reduce) {
    .share-view-button.share-view-flash { transition: none; }
}

/* ── Command palette (Ctrl+K typeahead) ─────────────────────────
   Centred modal that searches units + staff in the user's scope.
   Single input at the top + a scrollable results pane below. The
   results pane is HTMX-loaded with 180 ms debounce on the input. */
.command-palette {
    border: 1px solid var(--rule);
    border-radius: 7px;
    padding: 0;
    background: var(--panel);
    color: var(--ink);
    box-shadow: 0 12px 32px rgba(16, 42, 67, 0.22);
    width: 429px;
    max-width: 92vw;
}
.command-palette::backdrop {
    background: rgba(16, 42, 67, 0.35);
}
.command-palette-modal {
    display: flex;
    flex-direction: column;
    max-height: 70vh;
}
.command-palette-search-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.85rem 1rem;
    border-bottom: 1px solid var(--rule);
}
.command-palette-search-icon {
    flex: 0 0 auto;
    color: var(--ink-soft);
    width: 15px;
    height: 15px;
}
.command-palette-input {
    flex: 1 1 auto;
    border: 0;
    outline: 0;
    background: transparent;
    color: var(--ink);
    font: inherit;
    font-size: 11px;
    padding: 0.35rem 0.25rem;
}
.command-palette-input::placeholder { color: var(--ink-soft); }
.command-palette-close {
    background: transparent;
    border: 0;
    color: var(--ink-soft);
    cursor: pointer;
    font-size: 15px;
    line-height: 1;
    padding: 0 0.4rem;
    border-radius: 3px;
}
.command-palette-close:hover { color: var(--ink); }
.command-palette-results-wrap {
    overflow-y: auto;
    flex: 1 1 auto;
    min-height: 43px;
}
.command-palette-hint,
.command-palette-empty {
    margin: 0;
    padding: 1rem 1.25rem;
    color: var(--ink-soft);
    font-style: italic;
}
.command-palette-empty strong {
    color: var(--ink);
    font-style: normal;
}
.command-palette-section {
    padding: 0.4rem 0 0.5rem;
    border-bottom: 1px solid var(--rule);
}
.command-palette-section:last-child { border-bottom: 0; }
.command-palette-section-title {
    margin: 0;
    padding: 0.4rem 1.1rem 0.3rem;
    font-size: 7px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--ink-soft);
}
.command-palette-list {
    list-style: none;
    margin: 0;
    padding: 0;
}
.command-palette-item {
    display: flex;
    align-items: baseline;
    gap: 1rem;
    width: 100%;
    background: transparent;
    border: 0;
    border-left: 2px solid transparent;
    text-align: left;
    padding: 0.55rem 1.1rem;
    font: inherit;
    font-size: 10px;
    color: var(--ink);
    cursor: pointer;
}
.command-palette-item:hover,
.command-palette-item:focus {
    background: var(--accent-soft);
    border-left-color: var(--accent);
    outline: 0;
}
.command-palette-item-name {
    font-weight: 500;
    flex: 0 1 auto;
}
.command-palette-item-meta {
    color: var(--ink-soft);
    font-size: 9px;
    flex: 1 1 auto;
    text-align: right;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.command-palette-footer {
    display: flex;
    gap: 1rem;
    padding: 0.5rem 1rem;
    border-top: 1px solid var(--rule);
    background: var(--bg);
    color: var(--ink-soft);
    font-size: 8px;
}
.command-palette-footer kbd {
    font-family: var(--mono);
    font-size: 7px;
    background: var(--panel);
    border: 1px solid var(--rule);
    border-radius: 2px;
    padding: 1px 3px;
    margin-right: 0.2rem;
    color: var(--ink);
}

/* ── Keyboard shortcuts dialog ──────────────────────────────────
   Opened by '?'. Native <dialog> chrome (modal layer + Esc close)
   wrapped in the existing .staff-modal pattern for visual parity. */
.shortcuts-dialog {
    border: 1px solid var(--rule);
    border-radius: 5px;
    padding: 0;
    background: var(--panel);
    color: var(--ink);
    box-shadow: 0 8px 21px rgba(16, 42, 67, 0.18);
    max-width: 92vw;
    width: 322px;
}
.shortcuts-modal { padding: 1.25rem 1.5rem; }
.shortcuts-title {
    margin: 0 0 1rem 0;
    font-family: var(--display);
    font-size: 15px;
}
.shortcuts-list {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 0.6rem 1rem;
    margin: 0;
}
.shortcuts-list dt {
    display: flex;
    gap: 0.25rem;
    align-items: center;
    white-space: nowrap;
}
.shortcuts-list dd { margin: 0; color: var(--ink); }
.shortcuts-list kbd {
    font-family: var(--mono);
    font-size: 9px;
    line-height: 1;
    background: var(--accent-soft);
    border: 1px solid var(--rule);
    border-radius: 3px;
    padding: 2px 5px;
    color: var(--ink);
}
.shortcuts-foot {
    margin: 1.25rem 0 0;
    color: var(--ink-soft);
    font-size: 9px;
    font-style: italic;
}

/* Staff Details rendered into the shared #detail-modal - wraps a copy of
   the same content the full /staff/{id} page renders, but without the
   tab bar or back link. */
.staff-modal {
    position: relative;
    width: min(880px, 95vw);
    max-height: 95vh;
    display: flex;
    flex-direction: column;
}

/* Tighter spacing inside the modal so every panel fits into 92vh
   without an outer scroll on typical desktop sizes (1080p and up).
   The full-page /staff/{id} view keeps the roomier original padding. */
.staff-modal .staff-panel {
    padding: 0.55rem 0.8rem;
}
.staff-modal .panel-title {
    font-size: 10px;
    margin-bottom: 0.3rem;
}
/* Inside the modal the staff header gets the same bordered-panel
   chrome as the chart / contracts panels below, so the title + name
   read as a real first row rather than floating loose above the
   close X. */
.staff-modal .staff-header {
    border: 1px solid var(--rule);
    border-radius: 4px;
    background: var(--panel);
    padding: 0.55rem 0.8rem;
}
.staff-modal .staff-header-top { align-items: flex-start; }
.staff-modal .stat-strip { gap: 0.4rem; }
.staff-modal .stat { padding: 0.35rem 0.7rem; }
.staff-modal .staff-duties-panel { display: flex; flex-direction: column; }
.staff-modal .staff-duties-panel .balance-grid-container { flex: 1 1 auto; }

/* Compress chart panel heights inside the modal so the headline +
   stat strip + Weekly chart + Mini Shift Viewer + Contract Time
   Records + AI notes all stack inside one 92vh dialog. The
   full-page view keeps the inline height set in the partial. */
.staff-modal .sdv-chart-panel .chart-canvas-inner { height: 22vh !important; min-height: 170px !important; max-height: 260px !important; }
.staff-modal .sdv-mini-panel { padding-bottom: 0.4rem !important; }
.staff-modal .sdv-mini-panel .chart-canvas-inner { height: 13vh !important; min-height: 110px !important; max-height: 150px !important; min-width: 580px !important; }
.staff-modal .sdv-mini-panel .daily-legend { margin-top: 0.2rem; }

/* Contract Time Records: 5-row scroll + sticky header + table-layout
   fixed so the panel honours its container width instead of demanding
   a horizontal scrollbar when Location runs long. The freed-up
   vertical space inside the modal goes to the AI Notes panel below. */
.staff-contracts-panel .balance-grid-scroll {
    max-height: 190px;
    overflow-y: auto;
    overflow-x: hidden;
}
.staff-modal .staff-contracts-panel .balance-grid-scroll {
    max-height: 105px;
    min-height: 80px;
}
.staff-contracts-panel .balance-grid {
    table-layout: fixed;
    width: 100%;
}
.staff-contracts-panel .balance-grid th,
.staff-contracts-panel .balance-grid td {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Column widths: Location stretches; the rest are content-sized. */
.staff-contracts-panel .balance-grid th:nth-child(1),
.staff-contracts-panel .balance-grid td:nth-child(1) { width: 28%; }
.staff-contracts-panel .balance-grid th:nth-child(2),
.staff-contracts-panel .balance-grid td:nth-child(2) { width: 16%; }
.staff-contracts-panel .balance-grid th:nth-child(3),
.staff-contracts-panel .balance-grid td:nth-child(3) { width: 13%; }
.staff-contracts-panel .balance-grid th:nth-child(4),
.staff-contracts-panel .balance-grid td:nth-child(4) { width: 12%; }
.staff-contracts-panel .balance-grid th:nth-child(5),
.staff-contracts-panel .balance-grid td:nth-child(5) { width: 12%; }
.staff-contracts-panel .balance-grid th:nth-child(6),
.staff-contracts-panel .balance-grid td:nth-child(6) { width: 12%; }
.staff-contracts-panel .balance-grid th:nth-child(7),
.staff-contracts-panel .balance-grid td:nth-child(7) { width: 7%; }
.staff-contracts-panel .balance-grid thead th {
    position: sticky;
    top: 0;
    background: var(--panel);
    z-index: 1;
}

/* Tighten the rest of the modal so the column-only layout fits. */
.staff-modal .staff-name { font-size: 1rem; margin: 0.1rem 0 0.15rem; }
.staff-modal .staff-subtitle { font-size: 9.5px; margin: 0; }
.staff-modal .stat-label { font-size: 8.5px; }
.staff-modal .stat-value { font-size: 13px; }
.staff-modal .stat-meta { font-size: 8.5px; }
.staff-modal .staff-modal-scroll { gap: 0.5rem; padding: 0.9rem 1.1rem 0.7rem; }
/* The X close button (.staff-modal-close, top 1.2rem) is absolutely
   positioned to sit INSIDE the first panel's border rectangle near the
   top-right. The first panel therefore needs extra top padding so its
   heading doesn't crash into the X. Applies regardless of what the
   first child is - a bordered panel (.staff-header / .staff-panel) or
   a bare <h2>. Bare elements use margin since they have no padding box. */
.staff-modal .staff-modal-scroll > :first-child { padding-top: 2rem; }
.staff-modal .staff-modal-scroll > h1:first-child,
.staff-modal .staff-modal-scroll > h2:first-child,
.staff-modal .staff-modal-scroll > h3:first-child,
.staff-modal .staff-modal-scroll > p:first-child { margin-top: 2rem; padding-top: 0; }
.staff-modal .staff-notes-panel { padding: 0.45rem 0.8rem; min-height: 65px; }
.staff-modal .staff-notes-cell { min-height: 60px; }
.staff-modal .sdv-mini-panel .daily-legend { margin-top: 0.25rem; gap: 0.3rem; }
.staff-modal .daily-legend-pill.is-static { font-size: 8px; padding: 0.05rem 0.4rem; min-height: 18px; }
.staff-modal .sdv-chart-average { padding: 0.2rem 0.6rem; font-size: 9px; }
.staff-modal .sdv-hint { display: none; }
.staff-modal .staff-history-selector { font-size: 9.5px; }

/* Zoom controls cluster on the Weekly chart header. */
.sdv-zoom-controls,
.sdv-week-nav {
    margin-left: 0.4rem;
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
}
.sdv-week-nav { margin-left: auto; }
.zoom-btn {
    width: 22px;
    height: 22px;
    padding: 0;
    border: 1px solid var(--rule);
    background: var(--panel);
    color: var(--ink);
    border-radius: 3px;
    font-size: 13px;
    font-weight: 600;
    line-height: 1;
    cursor: pointer;
    transition: background 140ms ease, border-color 140ms ease;
}
.zoom-btn:hover  { background: var(--accent-soft); border-color: var(--accent); }
.zoom-btn:focus-visible { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.zoom-reset { display: inline-flex; align-items: center; gap: 0.2rem; font-size: 10px; }
.zoom-reset span[aria-hidden] { font-size: 12px; }
.staff-modal .zoom-reset-label { display: none; }
.staff-modal .zoom-btn { width: 19px; height: 19px; font-size: 12px; }

.sdv-hint {
    margin: 0.25rem 0 0;
    font-size: 9.5px;
    color: var(--ink-soft);
    font-style: italic;
}

.staff-modal-close {
    position: absolute;
    /* Sits INSIDE the first panel's border rectangle, ~5 px below its
       top edge. Scroll padding-top is 0.9rem (~14 px) so the panel
       border is at y ~14 px; X starts at y ~19 px - clear of the
       border. Pair this with the > :first-child padding-top above so
       the panel heading doesn't crash into the X bottom edge. */
    top: 1.2rem;
    /* 2.2 rem so the X sits well clear of the modal's vertical
       scrollbar gutter on modals whose content overflows. The scroll
       container is .staff-modal-scroll; its scrollbar lives at the
       right edge of the dialog, ~14-20 px wide depending on OS +
       browser zoom. 1.6 rem still occasionally collided. */
    right: 2.2rem;
    z-index: 2;
    background: var(--panel);
    border: 1px solid transparent;
    color: var(--ink-soft);
    font-size: 17px;
    line-height: 1;
    /* 24 px hit-target floor per the readability guideline. */
    width: 24px;
    height: 24px;
    border-radius: 50%;
    cursor: pointer;
}
.staff-modal-close:hover { background: var(--accent-soft); color: var(--ink); }
.staff-modal-close:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }

.staff-modal-scroll {
    overflow-y: auto;
    padding: 1.25rem 1.5rem 1rem;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.staff-back-inline {
    color: var(--accent);
    text-decoration: none;
}
.staff-back-inline:hover { text-decoration: underline; }

.drill-modal-btn {
    background: transparent;
    border: 1px solid var(--rule);
    color: var(--ink-soft);
    border-radius: 3px;
    width: 19px;
    height: 19px;
    padding: 3px;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    vertical-align: middle;
}
.drill-modal-btn svg { width: 11px; height: 9px; fill: currentColor; }
.drill-modal-btn:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
.drill-modal-btn:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }

/* Weeks selector + chart that can pan horizontally beyond viewport width.
   The inner div carries the min-width set by JS; the outer wrap clips and
   provides the horizontal scrollbar. */
.weeks-selector {
    margin-left: auto;
    font-size: 10px;
    color: var(--ink);
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}
.weeks-selector select {
    font: inherit;
    padding: 0.4rem 0.6rem;
    min-height: 24px;
    border: 1px solid var(--rule);
    border-radius: 3px;
    background: var(--panel);
    color: var(--ink);
    cursor: pointer;
}
.weeks-selector select:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}

.chart-scrollable {
    overflow-x: auto;
    overflow-y: hidden;
}
.chart-canvas-inner {
    min-width: 100%;
    height: 214px;
}

/* Duty link in the staff-details subtitle - jumps back to /balances
   Staff tab pre-filtered to that unit. */
.duty-link {
    color: var(--accent);
    text-decoration: none;
    border-bottom: 1px dotted var(--accent);
}
.duty-link:hover { color: var(--accent-hover); border-bottom-style: solid; }

/* Bi-directional sync between weekly chart bars and the duties grid.
   Click a bar → scroll the matching week into view + brief yellow flash.
   Click a duty row → scroll the chart container to centre that week. */
tr.duty-row { cursor: pointer; }
tr.duty-row:hover { background: var(--accent-soft); }
tr.duty-row-flash { animation: duty-row-flash 1.2s ease-out; }
@keyframes duty-row-flash {
    0%   { background: #fef9c3; }
    100% { background: transparent; }
}

/* Dedicated Open-modal column on the Staff grid. Sits to the left of the
   Staff name so the action stays in the user's scan path even when the
   row is wider than the viewport. */
/* Two icon buttons per row: drill (open SDV) + duties (open
   shifts modal). Slightly wider drill-col + a small flex
   container so they sit side-by-side without ugly stacking on
   narrow viewports. Icons bumped to be obviously clickable on
   the lower-res Trust-issue laptops the audience uses. */
.drill-col { width: 56px; text-align: center; padding: 3px 4px; }
.row-actions {
    display: inline-flex;
    gap: 4px;
    align-items: center;
    justify-content: center;
}
.row-actions .drill-modal-btn { width: 19px; height: 19px; }
.row-actions .drill-modal-btn svg { width: 13px; height: 12px; }
.duties-btn .icon { width: 13px; height: 13px; }
.duties-btn:hover { color: var(--accent); }

/* Contract Time Records grid - the primary (currently-active)
   contract gets a subtle accent-soft row tint so the "today's
   contract" is obvious without dominating the table. */
.contract-primary { background: var(--accent-soft); }
.contract-primary-cell { color: var(--rag-green-strong); font-weight: 700; text-align: center; }
.contract-location { color: var(--ink); }
.contract-location-none { color: var(--ink-soft); font-style: italic; }

/* SDV header restructure: name + a right-aligned history-window
   stepper, matching the wireframe's "Show the last 186 weeks of
   history" control. */
.staff-name-prefix {
    color: var(--ink-soft);
    font-weight: 600;
    font-size: 1.1rem;
    margin-right: 0.4rem;
}

/* < > chevrons that flank the staff name and step through the
   session staff order. Small inline circles so they sit on the
   H1 baseline; disabled when the session has no next/prev. */
.sdv-nav-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    margin: 0 0.2rem;
    border: 1px solid var(--rule);
    border-radius: 50%;
    background: var(--panel);
    color: var(--ink);
    font-size: 14px;
    line-height: 1;
    font-weight: 600;
    text-decoration: none;
    vertical-align: middle;
    transition: background 140ms ease, border-color 140ms ease, color 140ms ease;
}
.sdv-nav-btn:hover { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
.sdv-nav-btn:focus-visible { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.sdv-nav-btn.is-disabled {
    color: var(--rule);
    cursor: not-allowed;
    pointer-events: none;
    opacity: 0.5;
}
.staff-modal .sdv-nav-btn { width: 19px; height: 19px; font-size: 12px; }
/* The single-angle-quote glyph sits ~1 px below its line-box centre
   because of its baseline metrics. Translate just the glyph so the
   chevron's optical centre matches the staff-name text beside it. */
.sdv-nav-glyph {
    display: inline-block;
    transform: translateY(-1px);
}

.staff-history-selector {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    margin-left: auto;
    font-size: 10px;
    color: var(--ink);
}
.sdv-weeks-input {
    width: 54px;
    height: 24px;
    padding: 0 0.5rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    font: inherit;
    font-size: 10px;
    text-align: center;
    background: var(--panel);
    color: var(--ink);
    transition: border-color 140ms ease, box-shadow 140ms ease;
}
.sdv-weeks-input:hover  { border-color: var(--accent); }
.sdv-weeks-input:focus-visible {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}

/* "Average: 33.18" chip on the Weekly Contract Shift Hours chart
   header, right-aligned next to the panel title. */
.sdv-chart-average {
    margin-left: auto;
    display: inline-flex;
    align-items: baseline;
    gap: 0.45rem;
    padding: 0.35rem 0.85rem;
    background: var(--accent-soft);
    border: 1px solid var(--rule);
    border-radius: 3px;
    font-size: 10px;
    color: var(--ink);
    font-variant-numeric: tabular-nums;
}
.sdv-avg-label { color: var(--ink-soft); font-weight: 600; }
.sdv-avg-value { font-weight: 700; font-size: 11px; }

/* Mini Shift Viewer - Chart.js bar chart sitting full-modal-width
   under the Weekly Contract Shift Hours panel. The chart-canvas
   inner sets the minimum width (780 px) so 21 columns + shift-code
   badges stay legible; the chart-scrollable wrap supplies the
   horizontal scrollbar when the modal is narrower than that. */
.sdv-mini-panel .panel-title { font-size: 10px; }
.sdv-mini-panel .daily-legend { margin-top: 0.5rem; }

/* Static legend pill (no click-to-isolate on the Mini Shift Viewer
   anymore - the chart's own per-bucket dataset toggling would
   complicate the badge logic). */
.daily-legend-pill.is-static {
    cursor: default;
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    font-size: 9px;
    padding: 0.15rem 0.5rem;
    border: 1px solid var(--rule);
    border-left: 3px solid var(--rule);
    background: var(--panel);
    color: var(--ink-soft);
    border-radius: 3px;
}
.daily-legend-pill.is-static .daily-legend-swatch {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
}

/* Trailing-13-rosters bar chart on the Staff grid. Rendered as
   absolutely-positioned <span> bars inside a relative container - plain
   HTML paints faster than SVG when there are hundreds of staff rows.
   Zero baseline runs through 50%; blue bars grow up (over-worked), red
   bars grow down (under-worked). Native title-attr tooltip per bar. */
/* Trail cell: bounded box + overflow clipping so the inline sparkline
   bars can never paint outside the row, even if a hover scale or an
   off-screen content-visibility placeholder pushes them. The bars
   carry a native title= attribute now, so clipping doesn't fight the
   tooltip - the browser paints title text outside the cell anyway. */
.trail-col {
    width: 67px;
    position: relative;
    overflow: hidden;
}

/* Last hours checkpoint column - the RosterEndDate of the most
   recent personnethours row for this staff member. Stale (>8 wks
   ago) rows pick up the dimmed amber treatment so a Ward Manager
   can scan for data drift at a glance. */
.checkpoint-col {
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
    font-size: 9px;
    color: var(--ink-soft);
}
.checkpoint-col.checkpoint-stale {
    color: var(--rag-amber);
    font-weight: 600;
}

/* Duties-modal toolbar: search input + Type dropdown + count
   pill + Export CSV button. Sits between the title and the
   grid. All controls match the 46 px / 17 px Ward-Manager floor
   per the design brief's First principles. */
.duties-toolbar {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    align-items: center;
    margin: 0.4rem 0 0.8rem;
}

/* Manager-note column on the Staff grid. The cell is a button that
   swaps to a textarea + Save / Cancel via HTMX. Read mode shows the
   note text (truncated at 3 lines via line-clamp) or "Add note" when
   empty. */
.manager-note-col { min-width: 161px; }
.staff-note-cell { display: flex; flex-direction: column; gap: 2px; }
.staff-note-view {
    border: none;
    background: transparent;
    text-align: left;
    padding: 0.15rem 0.25rem;
    cursor: pointer;
    color: var(--ink);
    width: 100%;
    border-radius: 3px;
    transition: background 140ms ease;
}
.staff-note-view:hover { background: var(--accent-soft); }
.staff-note-text {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
    white-space: normal;
    line-height: 1.3;
    font-size: 10px;
}
.staff-note-empty { color: var(--accent); font-size: 10px; text-decoration: underline; }
.staff-note-meta { font-size: 8px; color: var(--ink-soft); padding: 0 0.25rem; display: flex; gap: 4px; align-items: center; }
.staff-note-count {
    display: inline-block;
    padding: 0 4px;
    border-radius: 7px;
    background: var(--accent-soft);
    color: var(--accent-hover);
    font-weight: 600;
}

/* Manager-notes modal. Lists every note for one staff member,
   newest first. Add form at top, individual delete per note, clear-
   all button. Opens via openDetailModal('/staff/{id}/notes/modal')
   so it lands in the shared <dialog id="detail-modal">. */
.staff-notes-modal-title { margin: 0 0 0.3rem 0; font-size: 17px; }
.staff-notes-modal-sub {
    margin: 0 0 0.8rem 0;
    color: var(--ink-soft);
    font-size: 10px;
    display: flex;
    align-items: center;
    gap: 8px;
}
.staff-notes-clear {
    font-size: 10px;
    padding: 0.2rem 0.5rem;
    border-radius: 3px;
    cursor: pointer;
    border: 1px solid #f5c6c0;
    background: #fdecea;
    color: #8a1f1f;
    transition: background 140ms ease;
}
.staff-notes-clear:hover { background: #f9d6d1; }

.staff-notes-add-form {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-bottom: 0.8rem;
    padding: 0.6rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    background: var(--accent-soft);
}
.staff-notes-add-textarea {
    border: 1px solid var(--rule);
    border-radius: 3px;
    padding: 0.3rem 0.4rem;
    font: inherit;
    font-size: 11px;
    color: var(--ink);
    resize: vertical;
    background: var(--panel);
}
.staff-notes-add-textarea:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}
.staff-notes-add-actions { display: flex; justify-content: flex-end; }
.staff-notes-add-save {
    font-size: 11px;
    padding: 0.25rem 0.7rem;
    min-height: 24px;
    border-radius: 3px;
    cursor: pointer;
    border: 1px solid var(--accent);
    background: var(--accent);
    color: var(--panel);
    transition: background 140ms ease;
}
.staff-notes-add-save:hover { background: var(--accent-hover); }

.staff-notes-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.staff-notes-item {
    padding: 0.5rem 0.6rem;
    border: 1px solid var(--rule);
    border-left: 2px solid var(--accent);
    border-radius: 3px;
    background: var(--panel);
}
.staff-notes-item-text {
    white-space: pre-line;
    font-size: 11px;
    color: var(--ink);
    margin-bottom: 0.3rem;
}
.staff-notes-item-meta {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 9px;
    color: var(--ink-soft);
}
.staff-notes-item-when { font-weight: 600; }
.staff-notes-item-by { flex: 1 1 auto; }
.staff-notes-item-delete {
    font-size: 9px;
    padding: 0.15rem 0.45rem;
    border-radius: 3px;
    cursor: pointer;
    border: 1px solid var(--rule);
    background: var(--panel);
    color: var(--ink-soft);
    transition: background 140ms ease, color 140ms ease;
}
.staff-notes-item-delete:hover { background: #fdecea; color: #8a1f1f; border-color: #f5c6c0; }

/* Duties modal Shift column - reduced ~25% from its natural width so
   the wider Booking ref / Reason / Additional reason columns get more
   room. Long shift names ("Long Day Early") get truncated with an
   ellipsis; the cell carries the full name via title= for hover. */
.duties-grid .duties-shift-col,
.duties-grid td[data-col="shift"] {
    max-width: 88px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Duties modal: flex column inside the modal viewport. The outer
   .staff-modal-scroll suppresses its own vertical overflow (the inner
   grid wrapper owns it), and the duties-grid-scroll takes whatever
   vertical space remains - so its horizontal scrollbar always sits at
   the visible bottom of the modal, not at the bottom of a possibly-
   tall table that's pushed below the fold. */
.duties-modal .staff-modal-scroll { overflow-y: hidden; flex: 1 1 auto; min-height: 0; }
.duties-modal .balance-grid-container {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}
.duties-grid-scroll {
    flex: 1 1 auto;
    min-height: 0;
    overflow-x: scroll;
    overflow-y: auto;
}
.duties-search,
.duties-type-filter {
    height: 31px;
    padding: 0 0.85rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    background: var(--panel);
    color: var(--ink);
    font: inherit;
    font-size: 11px;
    transition: border-color 140ms ease, box-shadow 140ms ease;
}
.duties-search { min-width: 188px; flex: 1 1 188px; }
.duties-type-filter { min-width: 107px; cursor: pointer; }
.duties-search:hover,
.duties-type-filter:hover { border-color: var(--accent); }
.duties-search:focus-visible,
.duties-type-filter:focus-visible {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}
.duties-date-range {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 9px;
    color: var(--ink-soft);
}
.duties-date-label { font-weight: 600; color: var(--ink); }
.duties-date-input {
    height: 31px;
    padding: 0 0.6rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    background: var(--panel);
    color: var(--ink);
    font: inherit;
    font-size: 10px;
    cursor: pointer;
    transition: border-color 140ms ease, box-shadow 140ms ease;
}
.duties-date-input:hover { border-color: var(--accent); }
.duties-date-input:focus-visible {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}
.duties-count {
    font-size: 9px;
    color: var(--ink-soft);
    font-variant-numeric: tabular-nums;
    margin-left: 0.2rem;
}
/* Lozenge - rounded pill - matches the other export buttons across
   the app (Status bar export on the staff/units/divisions grids).
   The shifts modal previously had this as a square button which
   broke the visual taxonomy. */
.duties-export-btn {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    height: 31px;
    padding: 0 1rem;
    border: 1px solid var(--rule);
    border-radius: 999px;
    background: var(--panel);
    color: var(--accent);
    cursor: pointer;
    font: inherit;
    font-size: 10px;
    font-weight: 600;
    transition: background 140ms ease, border-color 140ms ease, box-shadow 140ms ease;
}
.duties-export-btn:hover {
    background: var(--accent-soft);
    border-color: var(--accent);
}
.duties-export-btn:focus-visible {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}
.duties-export-btn .icon { width: 12px; height: 12px; }

/* Unit-modal header actions row - groups the sentinel-report
   icon buttons + the sign-off lozenge so they sit on one line
   to the right of the unit identity. Square ghost icon buttons
   match .unit-picker-action chrome for consistency. */
.unit-detail-actions {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
}
.unit-action-btn {
    flex: 0 0 auto;
    width: 25px;
    height: 25px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--panel);
    border: 1px solid var(--rule);
    border-radius: 3px;
    color: var(--accent);
    cursor: pointer;
    text-decoration: none;
    transition: background 140ms ease, border-color 140ms ease, box-shadow 140ms ease;
}
.unit-action-btn:hover {
    background: var(--accent-soft);
    border-color: var(--accent);
}
.unit-action-btn:focus-visible {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}
.unit-action-btn .icon { width: 13px; height: 13px; }
.trail-bars {
    position: relative;
    display: inline-block;
    width: 60px;
    height: 15px;
    line-height: 0;
    vertical-align: middle;
}
.trail-bars::after {
    content: '';
    position: absolute;
    left: 0; right: 0; top: 50%;
    height: 1px;
    background: var(--rule);
}
.trail-bar {
    position: absolute;
    display: block;
    cursor: pointer;
}
/* Sparkline trail bars - share the accent (positive = blue/owing trust)
   and the softened red (negative = trust owes). Kept distinct from the
   activity-palette tokens because trail bars convey balance sign, not
   duty type. */
.trail-bar-pos {
    bottom: 50%;
    background: var(--accent);
}
.trail-bar-neg {
    top: 50%;
    background: var(--rag-red-strong);
}
.trail-bar:hover { opacity: 0.7; }

/* Daily detail strip - one absolutely-positioned cell per day, each
   cell a vertical stack of NERG-bucket segments coloured per the
   activity palette. Zero baseline at the bottom; bars grow up. */
/* ── Group-by selector + grouped Staff grid rows ──────────────────
   Toolbar control toggles server-side grouping; the grid then emits
   sticky group-header rows + per-group subtotal rows interspersed
   with the staff rows. Collapse is client-side only (per visit). */
.group-by-label {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    height: 31px;
    padding: 0 0.85rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    background: var(--panel);
    font-size: 11px;
    color: var(--ink-soft);
}
.group-by-text { font-weight: 500; }
.group-by-select {
    padding: 0.3rem 0.5rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    font: inherit;
    color: var(--ink);
    background: var(--panel);
}
.group-by-select:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}

/* Group-by select inside the Staff grid's .status-bar. Matches the
   46 px chrome of the toolbar pickers (Units, Grades, Bands, As-of)
   exactly - same border, padding, focus ring - per the "same job,
   same component" rule in design-brief.md First principles. Options
   carry the "Group by" hint in their label text so no separate label
   is needed alongside ("View as flat list" / "Grouped by Division"). */
.group-by-status {
    height: 31px;
    width: auto;
    min-width: 147px;
    flex: 0 0 auto;
    font-size: 10px;
    padding: 0.55rem 0.85rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    background-color: var(--panel);
    color: var(--ink);
    cursor: pointer;
    transition: border-color 140ms ease, box-shadow 140ms ease;
}
.group-by-status:hover {
    border-color: var(--accent);
}
.group-by-status:focus-visible {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}

/* Expand-all / collapse-all icon buttons next to the group-by select.
   Same chrome as .unit-picker-action so the toolbar reads as one row
   of consistent square icon buttons. */
.group-bulk-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 31px;
    height: 31px;
    padding: 0;
    background: var(--panel);
    border: 1px solid var(--rule);
    border-radius: 3px;
    color: var(--accent);
    cursor: pointer;
    transition: background 140ms ease, border-color 140ms ease, box-shadow 140ms ease;
}
.group-bulk-toggle:hover {
    background: var(--accent-soft);
    border-color: var(--accent);
}
.group-bulk-toggle:focus-visible {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}
.group-bulk-toggle .icon { width: 15px; height: 15px; }

/* Sticky group header - sits below the sticky thead. `top` is the
   approximate thead height; if a header overlaps a cell, tune here. */
.balance-grid tr.group-header td {
    position: sticky;
    top: 24px;
    z-index: 2;
    background: var(--accent-soft);
    border-top: 1px solid var(--accent);
    border-bottom: 1px solid var(--accent);
    padding: 0;
}
.group-toggle {
    width: 100%;
    text-align: left;
    background: transparent;
    border: 0;
    cursor: pointer;
    padding: 0 0.85rem;
    /* Match the toolbar input floor (46 px) so the +/- group icons
       align with the grid params above them. */
    min-height: 31px;
    font: inherit;
    font-weight: 600;
    color: var(--ink);
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
}
.group-toggle:hover { background: rgba(111, 149, 187, 0.18); }
.group-toggle:focus-visible {
    outline: none;
    box-shadow: inset 0 0 0 2px var(--accent-soft);
}
.group-toggle-arrow {
    display: inline-block;
    width: 1ch;
    font-size: var(--ui-arrow-size);
    font-weight: var(--ui-arrow-weight);
    line-height: 1;
    color: var(--accent-hover);
    transition: transform 120ms ease;
}
.group-toggle-label { color: var(--ink); }
.group-toggle-count {
    margin-left: auto;
    color: var(--ink-soft);
    font-weight: 500;
    font-size: 9px;
}

.balance-grid tr.group-subtotal td {
    background: var(--bg);
    font-weight: 600;
    border-top: 1px solid var(--rule);
    border-bottom: 1px solid var(--rule);
    padding: 0.3rem 0.85rem;
    font-style: italic;
    color: var(--ink);
}
.balance-grid tr.group-subtotal td:first-child {
    font-style: normal;
    color: var(--ink-soft);
}
@media (prefers-reduced-motion: reduce) {
    .group-toggle-arrow { transition: none; }
}

/* ── Top movers card row (above the Staff grid) ───────────────────
   Slim horizontal strip of 4-6 cards naming the staff with the
   biggest balance change roster-on-roster, scored by |delta| × WTE.
   Each card is a button that opens Staff Details. Cards colour by
   direction-vs-position (deteriorating = pastel red, improving =
   pastel green, neutral = grey) so a manager can scan the strip and
   immediately see who is moving away from balance vs back toward it. */
/* Top-movers strips sit AT THE TOP of each grid with no chrome -
   borderless, transparent background. The summary row uses the
   same lozenge component as the Activity bar (.activity-lozenge)
   so the two disclosure surfaces read as one family. */
.top-movers {
    background: transparent;
    border: none;
    border-radius: 0;
    margin: 0;
}
.top-movers-summary {
    list-style: none;
    cursor: pointer;
    padding: 0.45rem 0.25rem;
    display: flex;
    align-items: center;
    gap: 0.6rem;
    color: var(--ink-soft);
    user-select: none;
    font-size: 9px;
}
.top-movers-summary::-webkit-details-marker { display: none; }
.top-movers[open] .top-movers-summary .activity-lozenge-arrow { transform: rotate(180deg); }
.top-movers-caret {
    display: inline-block;
    color: var(--accent-hover);
    transition: transform 120ms ease;
    width: 1ch;
    font-size: var(--ui-arrow-size);
    font-weight: var(--ui-arrow-weight);
    line-height: 1;
}
.top-movers:not([open]) .top-movers-caret { transform: rotate(-90deg); }
.top-movers-title { color: var(--ink); }
.top-movers-count {
    display: inline-block;
    padding: 0.05rem 0.45rem;
    background: var(--accent-soft);
    border: 1px solid var(--accent);
    border-radius: 7px;
    font-size: 7px;
    font-weight: 600;
    color: var(--ink);
}

.top-movers-scroller {
    position: relative;
    padding: 0.6rem 0;
}
.top-movers-row {
    display: flex;
    flex-wrap: nowrap;
    gap: 0.55rem;
    overflow-x: auto;
    padding: 0 2.4rem 0.4rem;
    scroll-behavior: smooth;
    scroll-snap-type: x proximity;
}
.top-movers-row > .top-mover-card { scroll-snap-align: start; }
.top-movers-nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 27px;
    height: 48px;
    border: 1px solid var(--rule);
    background: var(--panel);
    color: var(--ink-soft);
    font-size: 21px;
    font-weight: var(--ui-arrow-weight);
    line-height: 1;
    border-radius: 3px;
    cursor: pointer;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
}
.top-movers-nav:hover {
    background: var(--accent-soft);
    border-color: var(--accent);
    color: var(--ink);
}
.top-movers-nav:focus-visible {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}
.top-movers-prev { left: 0.5rem; }
.top-movers-next { right: 0.5rem; }
@media (prefers-reduced-motion: reduce) {
    .top-movers-caret, .top-movers-nav { transition: none; }
    .top-movers-row { scroll-behavior: auto; }
}
.top-mover-card {
    flex: 0 0 auto;
    min-width: 117px;
    max-width: 147px;
    text-align: left;
    padding: 0.55rem 0.75rem 0.65rem;
    background: var(--panel);
    border: 1px solid var(--rule);
    border-left: 2px solid var(--rule);
    border-radius: 3px;
    cursor: pointer;
    transition: background 120ms ease, border-color 120ms ease, transform 120ms ease;
    font: inherit;
    color: inherit;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}
.top-mover-card:hover {
    background: var(--accent-soft);
    border-color: var(--accent);
    border-left-color: var(--accent);
    transform: translateY(-1px);
}
.top-mover-card:focus-visible {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}
.top-mover-card.top-mover-bad  { border-left-color: var(--rag-red-strong); }
.top-mover-card.top-mover-good { border-left-color: var(--rag-green-strong); }
.top-mover-name {
    font-weight: 600;
    font-size: 9px;
    color: var(--ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.top-mover-meta {
    font-size: 9px;
    color: var(--ink-soft);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    margin-bottom: 0.15rem;
}
.top-mover-delta {
    font-family: var(--mono);
    font-variant-numeric: tabular-nums;
    font-size: 13px;
    font-weight: 600;
    color: var(--ink);
    line-height: 1.1;
    display: flex;
    align-items: baseline;
    gap: 0.15rem;
}
.top-mover-bad .top-mover-delta  { color: var(--rag-red); }
.top-mover-good .top-mover-delta { color: var(--rag-green); }
.top-mover-arrow { font-size: 15px; font-weight: var(--ui-arrow-weight); line-height: 1; }
.top-mover-unit  { font-size: 9px; color: var(--ink-soft); font-weight: 400; margin-left: 1px; }
.top-mover-prevcurrent {
    font-family: var(--mono);
    font-variant-numeric: tabular-nums;
    font-size: 9px;
    color: var(--ink-soft);
}
@media (prefers-reduced-motion: reduce) {
    .top-mover-card { transition: none; }
    .top-mover-card:hover { transform: none; }
}

/* When top-movers is rendered above the grid container, drop the
   container's top border-radius so the strip and the grid read as
   one stacked surface. */
.top-movers + .status-bar + .balance-grid-container { border-radius: 0 0 4px 4px; }

/* ── Daily detail strip on Staff Details ─────────────────────────
   Horizontal day cells with stacked NERG-bucket segments. Pastel
   palette matches CSS --duty-* tokens. Day count is controlled by
   the days-back selector (90 / 180 / 365); the strip's height is
   fixed but cells dynamically scale to fit. */
.daily-axis {
    position: relative;
    width: 100%;
    height: 9px;
    margin-bottom: 1px;
}
.daily-axis-tick {
    position: absolute;
    top: 0;
    font-size: 7px;
    color: var(--ink-soft);
    font-variant-numeric: tabular-nums;
    transform: translateX(-1px);
    white-space: nowrap;
}
.daily-axis-tick::before {
    content: '';
    position: absolute;
    left: 0;
    top: 100%;
    width: 1px;
    height: 3px;
    background: var(--rule);
}
.daily-strip {
    position: relative;
    width: 100%;
    height: 134px;
    background: linear-gradient(to right,
        transparent 0,
        transparent calc(100% / 13 - 1px),
        var(--rule) calc(100% / 13 - 1px),
        var(--rule) calc(100% / 13),
        transparent calc(100% / 13));
    background-size: calc(100% / 13) 100%;
    background-color: var(--bg);
    border: 1px solid var(--rule);
    border-radius: 3px;
}
.daily-cell {
    position: absolute;
    bottom: 0;
    top: 0;
    cursor: default;
}
/* Empty-day cell - the shape is visible (so the user knows there's
   a day there) but with no segment bars. Subtle striped background
   distinguishes "no data" from "0 hours". */
.daily-cell-empty {
    background: repeating-linear-gradient(45deg,
        transparent 0,
        transparent 2px,
        color-mix(in srgb, var(--ink-soft) 6%, transparent) 2px,
        color-mix(in srgb, var(--ink-soft) 6%, transparent) 3px);
}
/* Today marker - vertical accent line so the user can see "where
   we are" against the historical strip. Renders on top of any
   segments via a high z-index. */
.daily-cell-today {
    box-shadow: inset 1px 0 0 var(--accent), inset -1px 0 0 var(--accent);
    z-index: 2;
}
.daily-cell-today::before {
    content: "";
    position: absolute;
    left: 50%;
    top: -3px;
    width: 4px;
    height: 4px;
    background: var(--accent);
    border-radius: 50%;
    transform: translateX(-50%);
}
.daily-cell-anchor {
    animation: cell-anchor-pulse 1400ms ease-out 1;
}
@keyframes cell-anchor-pulse {
    0%   { box-shadow: inset 1px 0 0 var(--accent), inset -1px 0 0 var(--accent), 0 0 0 0 var(--accent-soft); }
    60%  { box-shadow: inset 1px 0 0 var(--accent), inset -1px 0 0 var(--accent), 0 0 0 5px transparent; }
    100% { box-shadow: inset 1px 0 0 var(--accent), inset -1px 0 0 var(--accent); }
}
@media (prefers-reduced-motion: reduce) {
    .daily-cell-anchor { animation: none; }
}
.daily-seg {
    position: absolute;
    left: 0;
    right: 0;
    display: block;
    transition: opacity 140ms ease;
}
/* Click-to-isolate on the legend: dim every segment whose bucket
   isn't the chosen one. .is-isolating on the strip is set when a
   legend pill is pressed; .is-dim is set per non-matching segment. */
.daily-strip.is-isolating .daily-seg.is-dim { opacity: 0.12; }

.daily-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-top: 0.7rem;
    align-items: center;
}
/* Mirrors .heatmap-bucket so the SDV daily-strip legend reads as
   the same control as the Activity heatmap filter buttons. Same
   shape, size, font; left-border tint carries the taxonomy colour
   via the inline `border-left-color` style the JS sets per bucket;
   active state fills the button with the same colour. */
.daily-legend-pill {
    font: inherit;
    font-size: 9px;
    padding: 0.15rem 0.5rem;
    min-height: 21px;
    border: 1px solid var(--rule);
    border-left: 3px solid var(--rule);
    background: var(--panel);
    color: var(--ink-soft);
    border-radius: 3px;
    cursor: pointer;
    transition: background 140ms ease, border-color 140ms ease, color 140ms ease;
}
.daily-legend-pill:hover {
    background: var(--accent-soft);
    color: var(--ink);
    border-color: var(--accent);
}
.daily-legend-pill:focus-visible {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}
.daily-legend-pill.is-isolated {
    background: var(--accent-soft);
    border-color: var(--accent);
    color: var(--ink);
    font-weight: 600;
}
/* Legend swatch is no longer needed - the left border carries the
   taxonomy colour. Hidden visually but kept in the DOM so existing
   selectors keep working. */
.daily-legend-swatch { display: none; }
.daily-legend-reset {
    margin-left: 0.25rem;
    font-size: 8px;
}
@media (prefers-reduced-motion: reduce) {
    .daily-seg, .daily-legend-pill { transition: none; }
}
/* .daily-cell + .heatmap-cell tooltips render via the JS-driven
   .barnacles-tip singleton (see _Layout.cshtml). The earlier
   pseudo-element arrow rule was removed when the tooltip moved
   off pseudo-elements; the JS bubble paints its own arrow. */

/* ── Activity heatmap ─────────────────────────────────────────────
   GitHub-style calendar grid on the Unit Details modal: 7 rows
   (days Mon→Sun) × 13 columns (weeks oldest→newest). Each cell is
   coloured on a 5-tier ramp that blends from --rule (no activity)
   toward the softened brand --accent (peak day). Future cells get
   a striped treatment so they don't read as "no activity". */
.heatmap-wrap {
    padding: 0.25rem 0 0;
    overflow-x: auto;
}

/* Bucket filter buttons that sit in the panel title row above the
   heatmap. Each carries its taxonomy colour (the same --duty-*
   palette the .duty-tag-* lozenges use) so the row of buckets reads
   as a legend - users see the colour and instantly map it to the
   tag they see elsewhere on the staff drill / shifts list. The
   active button is solid-filled with its colour; inactive buttons
   carry a thin left-border tint as a visual hint. */
.heatmap-bucket-filter {
    display: inline-flex;
    flex-wrap: wrap;
    gap: 9px;
    margin-left: auto;
    align-items: flex-end;
}
/* Each bucket-group is a labelled cluster - the data-group-label
   shows up as an eyebrow above the buttons via a ::before pseudo
   so users see the taxonomy structure rather than a flat list of
   nine buttons. */
.heatmap-bucket-group {
    display: inline-flex;
    gap: 3px;
    flex-wrap: wrap;
    position: relative;
    padding-top: 12px;
}
.heatmap-bucket-group::before {
    content: attr(data-group-label);
    position: absolute;
    top: 0;
    left: 0;
    font-size: 8px;
    color: var(--ink-soft);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-weight: 600;
}
.heatmap-bucket-group + .heatmap-bucket-group {
    border-left: 1px solid var(--rule);
    padding-left: 9px;
    margin-left: 0;
}
.heatmap-bucket {
    font: inherit;
    font-size: 9px;
    padding: 0.15rem 0.5rem;
    min-height: 21px;
    border: 1px solid var(--rule);
    background: var(--panel);
    color: var(--ink-soft);
    border-radius: 3px;
    cursor: pointer;
    transition: background 140ms ease, border-color 140ms ease, color 140ms ease;
}
.heatmap-bucket:hover { background: var(--accent-soft); color: var(--ink); border-color: var(--accent); }

/* Per-bucket inactive accent: 3 px left border in the taxonomy
   colour so the button reads as "this bucket has this colour"
   without overpowering the row. */
.bucket-local       { border-left: 3px solid var(--duty-local); }
.bucket-bank        { border-left: 3px solid var(--duty-bank); }
.bucket-agency      { border-left: 3px solid var(--duty-agency); }
.bucket-sickness    { border-left: 3px solid var(--duty-sickness); }
.bucket-annualleave { border-left: 3px solid var(--duty-annual); }
.bucket-studyleave  { border-left: 3px solid var(--duty-study); }
.bucket-parenting   { border-left: 3px solid var(--duty-parenting); }
.bucket-otherleave  { border-left: 3px solid var(--duty-other); }
.bucket-workingday  { border-left: 3px solid var(--duty-workingday); }
/* Day / Night - time-of-day axis, not part of the duty taxonomy so
   uses neutral warm / cool tints rather than --duty-* variables. */
.bucket-day         { border-left: 3px solid #f59e0b; }
.bucket-night       { border-left: 3px solid #4338ca; }

/* Active button: solid-filled with its taxonomy colour, ink text
   for the pale tones (annual / working day) and panel text for the
   darker ones. */
.heatmap-bucket.is-active {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--panel);
    font-weight: 600;
}
.bucket-local.is-active       { background: var(--duty-local);      border-color: var(--duty-local); color: var(--panel); }
.bucket-bank.is-active        { background: var(--duty-bank);       border-color: var(--duty-bank); color: var(--panel); }
.bucket-agency.is-active      { background: var(--duty-agency);     border-color: var(--duty-agency); color: var(--panel); }
.bucket-sickness.is-active    { background: var(--duty-sickness);   border-color: var(--duty-sickness); color: var(--panel); }
.bucket-annualleave.is-active { background: var(--duty-annual);     border-color: var(--duty-annual); color: var(--ink); }
.bucket-studyleave.is-active  { background: var(--duty-study);      border-color: var(--duty-study); color: var(--panel); }
.bucket-parenting.is-active   { background: var(--duty-parenting);  border-color: var(--duty-parenting); color: var(--panel); }
.bucket-otherleave.is-active  { background: var(--duty-other);      border-color: var(--duty-other); color: var(--panel); }
.bucket-workingday.is-active  { background: var(--duty-workingday); border-color: var(--duty-workingday); color: var(--ink); }
.bucket-day.is-active         { background: #f59e0b; border-color: #f59e0b; color: var(--ink); }
.bucket-night.is-active       { background: #4338ca; border-color: #4338ca; color: var(--panel); }
.heatmap-bucket.is-active:hover { filter: brightness(0.92); }
.heatmap-grid {
    display: grid;
    grid-auto-rows: 11px;
    gap: 2px;
    align-items: center;
    width: max-content;
}
.heatmap-month-label {
    font-size: 9px;
    color: var(--ink-soft);
    height: 11px;
    line-height: 11px;
    text-align: left;
    font-variant-numeric: tabular-nums;
}
.heatmap-day-label {
    font-size: 9px;
    color: var(--ink-soft);
    text-align: right;
    padding-right: 0.35rem;
    line-height: 11px;
}
.heatmap-cell {
    width: 11px;
    height: 11px;
    border-radius: 2px;
    background: #eef2f6;
    position: relative;
    transition: outline-color 100ms ease;
}
.heatmap-cell[data-tip]:hover {
    outline: 1px solid var(--ink-soft);
    outline-offset: 1px;
    cursor: help;
}
/* 5-tier pastel ramp toward --accent (#6f95bb). Tier-0 is the
   "no activity" base; tiers 1-4 climb in 25-percentile bands of the
   window's busiest day. */
.heatmap-tier-0 { background: #eef2f6; }
.heatmap-tier-1 { background: #d8e4ef; }
.heatmap-tier-2 { background: #b3c9de; }
.heatmap-tier-3 { background: #8fadce; }
.heatmap-tier-4 { background: #6f95bb; }
/* Future days inside the window - present in the grid for alignment
   but visually opted out so the eye doesn't read "no activity". */
.heatmap-future {
    background: repeating-linear-gradient(
        45deg,
        #f5f8fb 0, #f5f8fb 3px,
        #eef2f6 3px, #eef2f6 5px
    );
}
.heatmap-legend {
    margin-top: 0.75rem;
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 1rem;
    font-size: 9px;
    color: var(--ink-soft);
    flex-wrap: wrap;
}
.heatmap-legend-meta { font-variant-numeric: tabular-nums; }
.heatmap-legend-scale { display: inline-flex; align-items: center; gap: 0.25rem; }
.heatmap-legend-cell {
    width: 8px;
    height: 8px;
    border-radius: 1px;
    display: inline-block;
}
.heatmap-loading, .heatmap-error {
    color: var(--ink-soft);
    font-size: 9px;
    padding: 0.5rem 0;
}
.heatmap-error { color: var(--error-text); }
.daily-cell:hover { outline: 1px solid var(--accent); }

/* Flash highlight when the user clicks a bar in the weekly chart -
   the seven days inside that ISO week pulse so the eye can pick them
   out of the surrounding strip. */
.daily-cell.daily-cell-flash {
    outline: 1px solid var(--accent);
    background: color-mix(in srgb, var(--accent-soft) 60%, transparent);
    animation: daily-cell-pulse 1.4s ease-out;
}
@keyframes daily-cell-pulse {
    0%   { box-shadow: 0 0 0 0 var(--accent-soft); }
    40%  { box-shadow: 0 0 0 5px transparent; }
    100% { box-shadow: 0 0 0 0 transparent; }
}

/* Container must allow the tooltip's data-bar context to escape on the
   y-axis even though the bubble itself is now a body-attached element. */
.trail-bars { overflow: visible; }

/* ── Global custom tooltip ──────────────────────────────────────
   A singleton .barnacles-tip element appended to <body> (or to the
   nearest open <dialog> when the target is inside a modal) is shown
   on hover/focus by the JS in _Layout.cshtml. The JS positions it
   ABOVE the target by default, flips it BELOW when there isn't room
   above, and clamps the horizontal position to the viewport so it
   never extends past the edge. Replaces the previous CSS-pseudo
   approach which had hard-to-fix edge-clip and modal-stacking
   problems. Native `title=` attributes are promoted to `data-tip=`
   by barnaclesUpgradeTooltips() so existing markup just works. */
.barnacles-tip {
    position: fixed;
    top: -1000px;
    left: -1000px;
    background: var(--panel);
    color: var(--ink);
    border: 1px solid var(--accent);
    padding: 0.55rem 0.95rem;
    border-radius: 3px;
    font-size: 11px;
    font-weight: 500;
    line-height: 1.4;
    max-width: 320px;
    width: max-content;
    text-align: center;
    box-shadow: 0 4px 12px rgba(16, 42, 67, 0.12);
    pointer-events: none;
    z-index: 2147483646;
    white-space: pre-wrap;
    opacity: 0;
    transition: opacity 140ms ease;
}
.barnacles-tip.is-visible { opacity: 1; }
.barnacles-tip::after {
    content: '';
    position: absolute;
    bottom: -5px;
    left: var(--bt-arrow-x, 50%);
    transform: translateX(-50%);
    width: 0;
    height: 0;
    border-left: 5px solid transparent;
    border-right: 5px solid transparent;
    border-top: 5px solid var(--accent);
}
.barnacles-tip.is-below::after {
    bottom: auto;
    top: -5px;
    border-top: 0;
    border-bottom: 5px solid var(--accent);
}
@media (prefers-reduced-motion: reduce) {
    .barnacles-tip { transition: none; }
}

/* Unit-modal balance distribution - one bar per staff member, zero
   baseline, blue above (positive AllHours = Owes Trust), red below
   (negative = Trust Owes). Reuses .trail-bars + .trail-bar geometry
   but spans the full panel width. */
.unit-balance-bars {
    display: block;
    width: 100%;
    height: 54px;
}

.spark-empty { color: var(--faint); font-size: 9px; }

.spark-link { display: inline-block; line-height: 0; cursor: pointer; }
.spark-link:hover .spark { filter: drop-shadow(0 0 1px var(--accent)); }

/* Balance-history page prev/next staff buttons */
.history-nav {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    white-space: nowrap;
}
.link-disabled { color: var(--faint); cursor: not-allowed; }



/* ── Button taxonomy ─────────────────────────────────────────────────
   Three canonical button looks across the app - keep these consistent
   when adding new actions.

   1. **Primary** - filled accent on a coloured background, white text,
      3px radius. For main CTAs (Sign in, Ask a question, Sign off the
      year). Classes: .primary-button, .ask-button (which inherits).
   2. **Ghost / outline** - transparent background, 1px var(--accent)
      border, accent text, fills with accent on hover. 3px radius.
      For secondary actions and table-row affordances. Classes:
      .generate-notes, .generate-all-btn.
   3. **Quiet ghost** - transparent background, 1px var(--rule) border,
      var(--ink-soft) text, lights up accent on hover. 3px radius. For
      "All" clear pills, modal close × buttons, row icon buttons.
      Classes: .filter-clear, .drill-modal-btn, .row-icon-btn,
      .staff-modal-close.

   Link-style buttons (.drill-link, .link-action) carry no background
   or border - they look like text. .lozenge is a round status pill,
   separate.

   Every button-like element shares the focus ring rule below. */

/* Unified focus ring on every interactive button-like element. */
button:focus-visible,
a.drill-link:focus-visible,
.drill-link:focus-visible,
.link-action:focus-visible {
    outline: none;
    box-shadow: 0 0 0 2px var(--accent-soft);
}

/* ── Animations ──────────────────────────────────────────────────────
   Subtle micro-interactions: easing on hover/focus colour changes, a
   short fade+scale on modal open, a fade-in on HTMX content swaps, and
   a soft "press" on primary buttons. All respect prefers-reduced-motion. */

button,
.drill-link,
.drill-modal-btn,
.filter-input,
.filter-clear,
.lozenge,
.tab,
.header-nav-link,
.duty-link,
.staff-back,
.staff-back-inline,
.sort-link,
.link-action,
.primary-button,
.nl-example,
.spark-link,
.trail-bar,
input[type="search"],
input[type="date"],
input[type="email"],
input[type="number"],
textarea,
select,
summary {
    transition:
        background-color 140ms ease,
        color 140ms ease,
        border-color 140ms ease,
        box-shadow 140ms ease,
        transform 90ms ease,
        opacity 140ms ease;
}

.primary-button:active { transform: translateY(1px); }
.lozenge-button:active { transform: scale(0.94); }
.drill-modal-btn:active { transform: scale(0.94); }

/* Smooth row hover on every grid */
.balance-grid tbody tr {
    transition: background-color 120ms ease;
}

/* Modal: fade + slight scale on open. Dialogs are appended to the top
   layer with [open] when showModal() runs, so an animation on the
   [open] selector fires once on entrance. */
dialog#detail-modal[open],
dialog#ai-dialog[open] {
    animation: modal-in 180ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

@keyframes modal-in {
    from { opacity: 0; transform: scale(0.96) translateY(-3px); }
    to   { opacity: 1; transform: scale(1) translateY(0); }
}

dialog::backdrop {
    animation: backdrop-in 180ms ease;
}

@keyframes backdrop-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}

/* HTMX content-swap fade. The grid container is swapped via outerHTML
   on every filter/sort change - animating the new node gives a soft
   fade-in without any per-element wiring. Also covers the modal partial
   contents and the tab bar. */
#balance-grid,
#tab-bar,
#units-filter-wrap,
#unit-results {
    animation: swap-in 200ms ease;
}

@keyframes swap-in {
    from { opacity: 0.35; }
    to   { opacity: 1; }
}

/* Sparkline bar hover (already opacity-based - slow it down a hair) */
.trail-bar:hover { transform: scaleY(1.08); transform-origin: center; }

/* Accessibility: when the user has asked for reduced motion, drop the
   animations to a single millisecond - keeps the final visual state but
   skips the journey. */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* Version stamp at the bottom of the login screen. Built at compile
   time by ComputeGitVersion in Barnacles.Api.csproj - shows the SemVer,
   short commit SHA, and commit date so support can validate releases. */
.login-version {
    margin: 1rem auto 0;
    text-align: center;
    color: var(--ink-soft);
    font-size: 9px;
    letter-spacing: 0.01em;
}
.login-version-num {
    font-weight: 600;
    color: var(--ink);
    font-variant-numeric: tabular-nums;
}
.login-version-sha {
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;
    font-size: 9px;
    color: var(--ink-soft);
}
.login-version-date {
    font-variant-numeric: tabular-nums;
    color: var(--ink-soft);
}
.login-version-sep {
    margin: 0 0.4em;
    color: var(--faint);
}

/* Account-menu inline version chip - small, low-contrast, sits to the
   right of the "What's new" label so the menu doubles as a build-stamp. */
.user-menu-version {
    margin-left: auto;
    padding: 1px 5px;
    border: 1px solid var(--rule);
    border-radius: 669px;
    font-size: 9px;
    font-variant-numeric: tabular-nums;
    color: var(--ink-soft);
    background: var(--panel);
}

/* Release-notes modal - list of every recorded release with a version
   chip, date, short title, and labelled "what changed" / "why" blocks.
   The currently-running version gets an accent border + "Now running"
   pill so the user can spot their build at a glance. */
.release-notes-modal h2 {
    margin: 0 0 0.5rem;
    font-size: 15px;
}
.release-notes-lead {
    color: var(--ink-soft);
    margin: 0 0 1.25rem;
    font-size: 11px;
    line-height: 1.5;
}
.release-notes-empty {
    color: var(--ink-soft);
    font-style: italic;
}
.release-notes-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}
.release-note {
    border: 1px solid var(--rule);
    border-radius: 4px;
    padding: 0.85rem 1rem 1rem;
    background: var(--panel);
}
.release-note.is-current {
    border-color: var(--accent);
    box-shadow: 0 0 0 1px var(--accent) inset;
}
.release-note-head {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    font-size: 9px;
    color: var(--ink-soft);
    margin-bottom: 0.35rem;
    flex-wrap: wrap;
}
.release-version-chip {
    display: inline-block;
    padding: 1px 7px;
    border: 1px solid var(--rule);
    border-radius: 669px;
    background: var(--panel);
    color: var(--ink);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    font-size: 9px;
}
.release-note-date {
    font-variant-numeric: tabular-nums;
}
.release-note-sha {
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;
    font-size: 9px;
    color: var(--faint);
}
.release-note-current {
    margin-left: auto;
    padding: 3px 9px;
    border-radius: 669px;
    background: var(--accent);
    color: white;
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    box-shadow: 0 0 0 2px var(--accent-soft);
}
.release-note-title {
    margin: 0 0 0.5rem;
    font-size: 13px;
    font-weight: 600;
    color: var(--ink);
}
.release-note-section {
    margin-top: 0.55rem;
}
.release-note-label {
    display: inline-block;
    font-size: 9px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--faint);
    margin-bottom: 0.2rem;
}
.release-note-body {
    color: var(--ink);
    font-size: 11px;
    line-height: 1.5;
    white-space: pre-wrap;
}

/* Forgot-password / register links + placeholder body copy on those
   two stub pages. Subtle so they don't pull focus from Sign in. */
.login-links {
    margin-top: 1.25rem;
    text-align: center;
    font-size: 11px;
}
.login-links a {
    color: var(--accent);
    text-decoration: none;
}
.login-links a:hover { text-decoration: underline; }
.login-link-sep {
    color: var(--faint);
    margin: 0 0.5rem;
}
.login-placeholder {
    color: var(--ink-soft);
    font-size: 10px;
    line-height: 1.5;
    margin: 0 0 0.5rem;
}
.login-placeholder a { color: var(--accent); }

/* Audit-trail page (/sign-offs) - top-level list of every ward-manager
   sign-off across the user's accessible units. */
.audit-header { margin: 1rem 0; }

/* Headline cards above the audit list - three side-by-side stats showing
   the cumulative hours and £ equivalents (Owes Trust / Trust Owes / Net)
   summed across the latest event per (unit, year). */
.audit-headlines {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 0.75rem;
    margin: 0.5rem 0 0.75rem;
}
.audit-headline {
    background: var(--panel);
    border: 1px solid var(--rule);
    border-radius: 4px;
    padding: 0.85rem 1rem;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}
.audit-headline-label {
    font-size: 9px;
    color: var(--ink-soft);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-weight: 600;
}
.audit-headline-hours {
    font-size: 17px;
    font-weight: 600;
}
.audit-headline-money {
    font-size: 10px;
    color: var(--ink);
}
.audit-rate {
    margin: 0 0 0.75rem;
    font-size: 10px;
    color: var(--ink);
}

/* Filter toolbar above the audit table - three selects + an optional
   Clear pill on the right. Submits on every change (no Apply button). */
.audit-filters {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin-bottom: 0.75rem;
    flex-wrap: wrap;
}
.audit-filter {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 11px;
    color: var(--ink-soft);
}
/* Match the Staff toolbar chrome - 46 px tall, 17 px font - so an
   admin landing here from /balances doesn't suddenly see tiny
   controls. Per design-brief First principles: "same job, same
   component" + "Readable and clickable, always". */
.audit-filter select,
.audit-filter input[type=datetime-local],
.audit-filter input[type=date] {
    font: inherit;
    font-size: 11px;
    height: 31px;
    padding: 0 0.85rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    background: var(--panel);
    color: var(--ink);
    cursor: pointer;
    min-width: 121px;
    transition: border-color 140ms ease, box-shadow 140ms ease;
}
.audit-filter select:hover,
.audit-filter input:hover { border-color: var(--accent); }
.audit-filter select:focus-visible,
.audit-filter input:focus-visible {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}
.audit-filters .filter-clear {
    margin-left: auto;
    height: 24px;
    line-height: 24px;
    padding: 0 0.95rem;
    text-decoration: none;
    display: inline-block;
}

/* Per-row Edit / Delete icon buttons in the audit table. Small, ghost,
   stay visually quiet until hover. */
.signoff-actions-col {
    width: 54px;
    text-align: center;
    white-space: nowrap;
}
.row-icon-btn {
    background: transparent;
    border: 1px solid var(--rule);
    color: var(--ink-soft);
    border-radius: 3px;
    /* 36 x 36 hit target per design brief First principles.
       Was 28 x 28 - too small to read on a Trust laptop. */
    width: 24px;
    height: 24px;
    padding: 0;
    cursor: pointer;
    font-size: 11px;
    line-height: 1;
    margin: 0 0.15rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    vertical-align: middle;
    transition: background 140ms ease, border-color 140ms ease, color 140ms ease, box-shadow 140ms ease;
}

.row-icon-btn .icon { width: 13px; height: 13px; }
.row-icon-btn:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
.row-icon-btn:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.row-icon-btn.row-icon-danger:hover { border-color: var(--rag-red-strong); color: var(--rag-red-strong); background: var(--rag-red-soft-bg-hover); }
/* Ward Guardian launcher = green shield. Bigger glyph than the
   row-icon-btn default (13 px -> 18 px) so the affordance reads at
   a glance across the KPI grid; still fits inside the 24 x 24
   button so the row height is unchanged. Green hover keeps the
   identity instead of jumping to the default accent blue. */
.wg-open-btn { color: var(--rag-green); }
.wg-open-btn .icon { width: 18px; height: 18px; }
.wg-open-btn:hover { border-color: var(--rag-green-strong); color: var(--rag-green-strong); background: var(--rag-green-soft-bg); }
.wg-open-btn:focus { outline: none; border-color: var(--rag-green-strong); box-shadow: 0 0 0 2px var(--rag-green-soft-bg); }
.audit-title {
    font-family: var(--display);
    font-weight: 600;
    font-size: 19px;
    margin: 0 0 0.25rem 0;
    color: var(--ink);
    letter-spacing: -0.01em;
}
.audit-subtitle { margin: 0; color: var(--ink-soft); }

.header-nav-link {
    color: var(--accent);
    text-decoration: none;
    font-size: 10px;
}
.header-nav-link:hover { text-decoration: underline; }

/* Drill-direction arrows on column headers - ‹ means the column's cells
   drill back up the hierarchy, › means they drill down. Tight against
   the column name so the whole thing reads as one label. */
.drill-arrow {
    margin-left: 0.1rem;
    color: var(--accent);
    font-weight: 700;
    /* Drill arrows in column headers ride alongside the label text -
       they should read at the same scale, just bolder. The big
       glyph carets in disclosure controls (lozenges, division
       carets) keep using --ui-arrow-size. */
    font-size: 1em;
    line-height: 1;
    line-height: 1;
    vertical-align: -1px;
}
.drill-arrow-up   { color: var(--ink-soft); }
.drill-arrow-down { color: var(--accent); }

/* Grid column headers must stay on a single line so the drill-arrow and
   sort indicator don't wrap the label onto a second row. */
.balance-grid th { white-space: nowrap; }

/* Body cells are also single-line by default - long names render
   with ellipsis, never wrap into a second line. Per the design
   brief First principles consistency rule: the Staff grid was
   already nowrap (via .unit-col); the Annual Leave grid wasn't,
   so a long unit name there spilled to two rows while the Staff
   grid kept it on one. Standardise to nowrap everywhere; cells
   that legitimately need wrap (multi-line notes, AI commentary,
   sign-off note edits, top-mover meta lines) opt back in below. */
.balance-grid td {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.balance-grid td.notes-cell,
.balance-grid td.commentary-cell,
.balance-grid td.signoff-note-cell,
.balance-grid td.signoff-matched-cell,
.balance-grid td.row-meta-cell,
.balance-grid td .commentary-text,
.balance-grid td .row-meta {
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
}

.panel-title-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0.5rem;
}
.panel-title-row .panel-title { margin: 0; }

dialog#detail-modal::backdrop {
    background: rgba(16, 42, 67, 0.45);
}

.staff-panel {
    background: var(--panel);
    border: 1px solid var(--rule);
    border-radius: 4px;
    padding: 1.25rem 1.5rem;
}

.panel-title {
    font-family: var(--display);
    font-weight: 600;
    font-size: 12px;
    margin: 0 0 0.5rem 0;
    color: var(--ink);
}

.panel-title .panel-subtle {
    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    font-weight: 400;
    font-size: 9px;
    color: var(--ink-soft);
    margin-left: 0.5rem;
}

.placeholder-text {
    margin: 0;
    color: var(--ink-soft);
    font-style: italic;
    font-size: 11px;
}

.chart-wrap {
    height: 188px;
    position: relative;
}

.chart-wrap-small {
    height: 80px;
    position: relative;
}

* { box-sizing: border-box; }

html, body {
    margin: 0;
    padding: 0;
    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    font-size: 13px;
    line-height: 1.5;
    background: var(--bg);
    color: var(--ink);
    -webkit-font-smoothing: antialiased;
}

/* Form controls don't follow the body's 1.5 prose line-height -
   otherwise descenders (g, j, p, q, y) clip inside fixed-height
   inputs and selects after the 67% rescale. 1.2 gives the line box
   enough room for descenders without overflowing common control
   heights (24-31 px). */
input, select, textarea {
    line-height: 1.2;
}

/* Balances page is designed to fit one viewport - the grid handles its own
   vertical scroll internally. Suppress the body vertical scrollbar so the
   page never shows the "one row away from fitting" right-hand slider.
   Pages that genuinely need long scroll (Staff Details, login) opt out via
   the .page-scrolls modifier below.
   overflow-x: hidden on body pins the page to viewport width so any wide
   inner content (KPI / Staff / Approvals grids) has to scroll INSIDE its
   own wrapper rather than widening the whole page. Without this rule the
   body grew to fit the widest grid and the inner .balance-grid-scroll
   never engaged its horizontal scrollbar. */
body { overflow-y: hidden; overflow-x: hidden; }
body.page-scrolls { overflow-y: auto; }

/* ── Header ───────────────────────────────────────────────────────────── */

.site-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.75rem 1.5rem;
    background: var(--panel);
    border-bottom: 1px solid var(--rule);
}

.brand {
    font-weight: 600;
    color: var(--accent);
    font-size: 1.25rem;
    letter-spacing: -0.01em;
}

.brand-tagline {
    font-weight: 400;
    color: var(--ink-soft);
    font-size: 0.95rem;
    margin-left: 0.25rem;
}

.user-strip {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    color: var(--ink-soft);
    font-size: 11px;
}

.user-email { font-weight: 500; color: var(--ink); }
.trust-name { font-weight: 500; color: var(--ink); }

.trust-selector {
    padding: 0.3rem 0.55rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    font: inherit;
    background: var(--panel);
}

main { padding: 0; }

/* ── Mini-icons ───────────────────────────────────────────────────
   Inline-SVG sprite icons via <svg class="icon"><use href="#..."/></svg>.
   Inherits currentColor from the surrounding text so the icon
   takes its hue from whatever the button/label happens to be.
   The wrapper svg is inline-flex aligned to the cap height of
   adjacent text and given a touch of right-margin so the call
   site can write "<svg/>Label" and have it look like one chip. */
.icon {
    display: inline-block;
    width: var(--icon-size);
    height: var(--icon-size);
    vertical-align: -2px;
    flex: 0 0 auto;
    color: inherit;
    fill: none;
    stroke: currentColor;
}
.icon-sm { width: var(--icon-size-sm); height: var(--icon-size-sm); vertical-align: -1px; }
.icon-lg { width: var(--icon-size-lg); height: var(--icon-size-lg); vertical-align: -3px; }
.icon + span,
.icon-leading + * {
    margin-left: 0.45rem;
}

/* The activity bar lives fixed at the bottom of every signed-in
   page. Reserve EXACTLY its height so the grid's scrollbar lane
   rests flush on top of the activity strip with no body-bg gap. */
body { padding-bottom: var(--activity-strip-h); }

/* ── Login page ───────────────────────────────────────────────────────── */

.login-wrap {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: calc(100vh - 40px);
    padding: 1rem;
}

.login-card {
    background: var(--panel);
    border-radius: 5px;
    padding: 2rem;
    width: 100%;
    max-width: 348px;
    box-shadow: 0 1px 3px rgba(16, 42, 67, 0.04);
    border: 1px solid var(--rule);
}

.login-logo {
    display: block;
    margin: 0 auto 1.25rem;
    max-width: 214px;
    height: auto;
}

.login-title {
    margin: 0 0 1.25rem 0;
    font-size: 1.375rem;
    font-weight: 600;
    text-align: center;
}

.login-form {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.login-form label {
    font-size: 11px;
    color: var(--ink-soft);
}

.login-form input {
    padding: 0.45rem 0.75rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    font: inherit;
    background: var(--panel);
}

.login-form input:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}

.primary-button {
    padding: 0.7rem 1.1rem;
    background: var(--accent);
    color: white;
    border: 1px solid var(--accent);
    border-radius: 3px;
    font: inherit;
    font-weight: 500;
    cursor: pointer;
    line-height: 1.2;
}

.primary-button:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.primary-button:focus-visible {
    outline: none;
    border-color: var(--accent-hover);
    box-shadow: 0 0 0 2px var(--accent-soft);
}

/* Ghost twin of .primary-button - IDENTICAL dimensions so modal action
   rows ("Cancel" + "Save changes") read as a matched pair. Use this for
   any secondary action that's a button (Cancel, Close, Re-sign). Keep
   .link-action for inline text links (e.g. "Cancel" next to a small
   form field, not a footer action row). */
.secondary-button {
    padding: 0.7rem 1.1rem;
    background: var(--panel);
    color: var(--ink);
    border: 1px solid var(--rule);
    border-radius: 3px;
    font: inherit;
    font-weight: 500;
    cursor: pointer;
    line-height: 1.2;
    text-decoration: none;
    display: inline-block;
}
.secondary-button:hover {
    background: var(--accent-soft);
    border-color: var(--accent);
    color: var(--ink);
}
.secondary-button:focus-visible {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}

.login-error {
    background: var(--error-bg);
    border: 1px solid var(--error-border);
    color: var(--error-text);
    padding: 0.7rem 0.8rem;
    border-radius: 3px;
    font-size: 11px;
    margin-bottom: 1rem;
}

/* Friendly /Error page reuses .login-wrap + .login-card chrome. Only
   the body copy, action button, and reference line need new rules. */
.error-body {
    margin: 0 0 1.25rem;
    color: var(--ink-soft);
    font-size: 12px;
    line-height: 1.5;
    text-align: center;
}
.error-action {
    display: block;
    text-align: center;
    text-decoration: none;
}
.error-ref {
    margin: 1rem 0 0;
    text-align: center;
    color: var(--ink-soft);
    font-family: 'JetBrains Mono', monospace;
    font-size: 10px;
    word-break: break-all;
}

/* Informational notice on the login page - used for the "you've been
   signed out after 2 hours of inactivity" message. Calmer than
   .login-error (accent blue, not red) because an idle timeout isn't
   the user's fault. */
.login-notice {
    background: var(--accent-soft);
    border: 1px solid var(--accent);
    color: var(--ink);
    padding: 0.7rem 0.8rem;
    border-radius: 3px;
    font-size: 11px;
    margin-bottom: 1rem;
}

/* Login button loading state: form's onsubmit toggles .is-loading on
   the button while the page navigates, so the user has clear visual
   feedback their click was registered. The spinner uses the same arc
   as the global .spinner just sized down to sit beside the label. */
.login-submit {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.55rem;
    min-height: 28px;
    white-space: nowrap;
    /* Extra breathing room above the button so it doesn't crowd
       the password field on the password-required form. */
    margin-top: 0.85rem;
}
.login-spinner {
    display: none;
    width: 13px;
    height: 13px;
    border: 2px solid rgba(255, 255, 255, 0.35);
    border-top-color: #fff;
    border-right-color: #fff;
    border-radius: 50%;
    animation: spinner-spin 0.7s linear infinite;
    flex: 0 0 auto;
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.15);
}
.login-submit.is-loading .login-spinner { display: inline-block; }
.login-label-loading {
    display: none;
    color: #fff;
    font-weight: 600;
    letter-spacing: 0.02em;
}
.login-submit.is-loading .login-label-idle { display: none; }
.login-submit.is-loading .login-label-loading { display: inline; }
/* Don't fade the button while loading - the spinner needs the
   solid accent background behind it to read clearly. Use --ink
   (the dark blue heading colour) rather than the medium accent
   so white spinner + white label both have strong contrast. */
.login-submit.is-loading {
    background: var(--ink);
    border-color: var(--ink);
    color: #fff;
    opacity: 1;
}
.login-submit:disabled { cursor: progress; }

/* ── Balances page ───────────────────────────────────────────────────── */

/* Flex column so #balance-grid (and its inner .balance-grid-container)
   can fill the remaining viewport below the chrome above it. This is
   what keeps the grid's horizontal scrollbar pinned to the visible
   bottom of the container - the previous block-flow layout used a
   magic-number height calc on .balance-grid-scroll that went stale
   every time chrome above the grid changed (filter chips, group-by row,
   status bar tweaks), so the wrapper bottom + its scrollbar slipped
   below the viewport. Height bound: viewport minus .site-header (~58 px)
   minus the body padding-bottom that reserves space for the fixed
   activity strip (30 px). */
.balances-page {
    /* Zero bottom padding so the .balance-grid-scroll wrapper's
       scrollbar lane sits flush on top of the fixed activity
       strip. Any bottom padding here would re-introduce a band
       of dead body-bg space between the scrollbar and the strip. */
    padding: 1.25rem 1.5rem 0;
    display: flex;
    flex-direction: column;
    height: calc(100vh - 58px - var(--activity-strip-h));
    box-sizing: border-box;
}
.balances-page > .tab-row,
.balances-page > .toolbar,
.balances-page > #active-filters,
.balances-page > .top-movers,
.balances-page > .audit-headlines,
.balances-page > .audit-rate,
.balances-page > .empty-state,
.balances-page > form.toolbar {
    flex: 0 0 auto;
}
.balances-page > #balance-grid {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}
.balances-page > #balance-grid > .status-bar,
.balances-page > #balance-grid > .top-movers,
.balances-page > #balance-grid > .empty-state {
    flex: 0 0 auto;
}
.balances-page > #balance-grid > .skeleton-grid,
.balances-page > #balance-grid > .balance-grid-container,
.balances-page > .balance-grid-container {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}
.balances-page > #balance-grid > .skeleton-grid > .balance-grid-container {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}
.balances-page .balance-grid-container > .balance-grid-scroll {
    flex: 1 1 0;
    min-height: 0;
    /* Override the legacy block-flow height calc + .top-movers
       max-height overrides so flex sizing wins. */
    height: auto;
    max-height: none;
}
.balances-page > .status-bar {
    flex: 0 0 auto;
}

/* ── Tab bar (overarching folder-style tabs) ─────────────────────── */

/* The tab bar sits flush against the toolbar below - the active tab's
   bottom edge becomes the toolbar's top edge, so the three tabs read as
   tabs OF the whole page rather than a separate widget. */

.tab-bar {
    display: flex;
    align-items: flex-end;
    gap: 0;
    margin-bottom: 0;
    padding: 0 0.25rem;
    position: relative;
    z-index: 1;
}

.tabs {
    display: flex;
    gap: 0.25rem;
    flex: 1 1 auto;
}
/* Sign-offs is an audit view, not a primary working tab - push it
   to the far-right edge so it reads as a separate concern. */
/* Right-cluster tabs (Approvals + Sign-offs) push to the far right
   via margin-left: auto. Whichever tab appears FIRST in the right
   cluster takes the auto-margin; subsequent right-cluster tabs sit
   adjacent. With Sign-offs at the very end of the markup it lands
   rightmost regardless of whether Approvals is shown. */
.tabs > .tab.tab-approvals,
.tabs > .tab.tab-signoffs { margin-left: auto; }
/* When Approvals is shown, Sign-offs follows it - kill Sign-offs'
   auto-margin so the two tabs cluster tightly together. */
.tabs > .tab.tab-approvals ~ .tab.tab-signoffs { margin-left: 0; }

.tab {
    background: var(--accent-soft);
    border: 1px solid var(--rule);
    border-bottom: none;
    color: var(--ink-soft);
    cursor: pointer;
    font: inherit;
    font-size: 11px;
    font-weight: 500;
    padding: 0.65rem 1.4rem;
    border-radius: 5px 5px 0 0;
    position: relative;
    margin-bottom: -1px;       /* overlap toolbar's top border */
    transition: background 0.1s ease;
}

.tab:hover { color: var(--ink); background: var(--panel); }

.tab.active {
    background: var(--panel);
    color: var(--ink);
    border-color: var(--rule);
    /* Toolbar no longer has a top border, so the active tab can sit
       on it cleanly without any masking strip - the two surfaces
       share the same white background and the tab's own bottom edge
       lines up exactly with the toolbar's now-borderless top edge. */
    border-bottom: none;
    font-weight: 600;
    z-index: 2;
}

.export-link {
    margin-left: auto;
    margin-bottom: 0.5rem;
    color: var(--accent);
    text-decoration: none;
    font-size: 10px;
    padding: 0.45rem 0.85rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    background: var(--panel);
}

.export-link:hover {
    background: var(--accent-soft);
    border-color: var(--accent);
}

/* ── Drill link buttons in grid cells ─────────────────────────────── */

.drill-link {
    background: none;
    border: none;
    color: var(--accent);
    cursor: pointer;
    font: inherit;
    padding: 0;
    text-align: left;
    text-decoration: none;
    border-bottom: 1px dotted var(--accent);
}

.drill-link:hover {
    color: var(--accent-hover);
    border-bottom-style: solid;
}

/* ── Duty tags ────────────────────────────────────────────────────── */

.duty-tag {
    display: inline-block;
    font-size: 7px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: 0.15rem 0.5rem;
    border-radius: 669px;
    color: white;
}

.duty-tag-local      { background: var(--duty-local); }
.duty-tag-bank       { background: var(--duty-bank); }
.duty-tag-agency     { background: var(--duty-agency); }

/* NERG sub-types - colour map matches sql/schema/ob_shifts.md.
   Yellow + lighter blue need dark text for AA contrast. */
.duty-tag-workingday { background: var(--duty-workingday); color: var(--ink); }
.duty-tag-annualleave{ background: var(--duty-annual);     color: var(--ink); }
.duty-tag-sickness   { background: var(--duty-sickness); }
.duty-tag-studyleave { background: var(--duty-study); }
.duty-tag-parenting  { background: var(--duty-parenting); }
.duty-tag-otherleave { background: var(--duty-other); }
.duty-tag-unknown    { background: var(--duty-other); }

/* Fallback for any NERG group name we haven't styled yet. */
.duty-tag-nerg       { background: var(--duty-other); }

.row-meta {
    display: block;
    color: var(--ink-soft);
    font-size: 8px;
    margin-top: 0.15rem;
}

.toolbar {
    background: var(--panel);
    /* No top border - the tab strip above provides the visual chrome.
       Side + bottom borders only so the toolbar reads as a continuous
       surface with whichever tab is active. */
    border-left: 1px solid var(--rule);
    border-right: 1px solid var(--rule);
    border-bottom: 1px solid var(--rule);
    border-top: none;
    border-top-left-radius: 0; /* tabs flow into this corner */
    border-top-right-radius: 5px;
    border-bottom-left-radius: 5px;
    border-bottom-right-radius: 5px;
    padding: 0.8rem 1rem;
    position: relative;
    /* Above the table's sticky thead / tfoot (both z-index: 1) so the
       Grade and Units dropdowns float over column headers and totals
       instead of being clipped beneath them. */
    z-index: 10;
}

/* Single-row toolbar: no wrapping. On narrower screens the page
   itself gets a horizontal scrollbar - the toolbar does NOT clip
   its own overflow because that would also cut off the picker
   popovers (Grade, Units) which absolute-position inside it. */
.filter-row {
    display: flex;
    align-items: center;
    flex-wrap: nowrap;
    gap: 1.25rem;
}

.filter-input {
    padding: 0.55rem 0.75rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    font: inherit;
    /* Use background-color (not shorthand `background:`) so the
       Units filter's background-image chevron isn't reset by this
       base rule. */
    background-color: var(--panel);
    color: var(--ink);
    height: 31px;
}

/* Unify placeholder colour with the Grade picker's resting label so
   the three primary filters (Name, Units, Grade) read as the same
   visual family when nothing is selected. */
.filter-input::placeholder {
    color: var(--ink-soft);
    opacity: 1;
}

/* Name shortened 30% from the previous 720 px so the toolbar can
   keep everything (Name, Units, Grade, Date, Ask) on a single line
   on a typical wide screen. */
.filter-name { width: 338px; }

/* Units control - rebuilt as a plain dropdown using <details> so it
   matches the Grade picker's chrome (clickable summary + popover).
   The actual search-as-you-type input lives INSIDE the popover so
   the toolbar reads as a button, not a text field. */
.units-search-wrap {
    position: relative;
    display: inline-block;
}
.unit-picker {
    position: relative;
    z-index: 100;
}
.unit-summary {
    list-style: none;
    cursor: pointer;
    padding: 0.6rem 0.95rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    background: var(--panel);
    color: var(--ink-soft);
    height: 31px;
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    line-height: 20px;
    user-select: none;
    /* Shortened 20% from the previous 880 px so the toolbar can
       keep Name, Units, Grade, Date and Ask on one line. The
       popover beneath is allowed to stay wider so long unit
       names still fit. */
    min-width: 472px;
    max-width: 100%;
    box-sizing: border-box;
}
.unit-summary::-webkit-details-marker { display: none; }
.unit-summary:hover { border-color: var(--accent); }
details.unit-picker[open] > .unit-summary {
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}
.unit-summary-label {
    flex: 1 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--ink-soft);
}
.unit-summary-caret {
    font-size: var(--ui-arrow-size);
    font-weight: var(--ui-arrow-weight);
    color: var(--ink-soft);
    line-height: 1;
    flex: 0 0 auto;
}

/* Trigger button that opens the units picker dialog. Same chip shape
   as the legacy .unit-summary so the toolbar layout stays identical. */
.unit-picker-trigger {
    cursor: pointer;
    padding: 0.6rem 0.95rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    background: var(--panel);
    color: var(--ink-soft);
    height: 31px;
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    line-height: 20px;
    user-select: none;
    min-width: 472px;
    max-width: 100%;
    box-sizing: border-box;
    font: inherit;
    text-align: left;
    transition: border-color 140ms ease, box-shadow 140ms ease;
}
.unit-picker-trigger:hover  { border-color: var(--accent); }
.unit-picker-trigger:focus  { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.unit-picker-trigger .unit-summary-label { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink-soft); }
.unit-picker-trigger .unit-summary-caret { font-size: var(--ui-arrow-size); font-weight: var(--ui-arrow-weight); color: var(--ink-soft); line-height: 1; flex: 0 0 auto; }

/* Grade + Band pickers share the .unit-picker-trigger chrome but
   their labels are short ("Grade Band 6 +N" / "Band 6 +N") so the
   704 px min-width from the toolbar Units picker is far too wide
   and pushes other toolbar buttons off the page. Override to size-
   to-content with a sensible floor. */
.unit-picker-trigger.grade-picker-trigger {
    min-width: 0;
    width: auto;
    padding: 0.55rem 0.8rem;
}

/* Modal version of the picker. One outer dialog, one inner framed
   card. Only the hierarchy inside the frame scrolls; no nested
   scrollbars on the modal itself. */
.unit-picker-dialog {
    border: none;
    background: transparent;
    padding: 0;
    width: min(482px, 96vw);
    max-height: 90vh;
    overflow: visible;
}
.unit-picker-dialog::backdrop {
    background: rgba(16, 42, 67, 0.45);
}
.unit-picker-modal {
    background: var(--panel);
    border-radius: 5px;
    box-shadow: 0 8px 24px rgba(16, 42, 67, 0.18);
    max-height: 90vh;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    position: relative;
}
.unit-picker-modal-head {
    position: relative;
    padding: 1.1rem 3rem 0.6rem 1.5rem;
    flex: 0 0 auto;
}
.unit-picker-modal-title {
    margin: 0 0 0.2rem 0;
    font-size: 1.15rem;
    color: var(--ink);
}
.unit-picker-modal-hint {
    margin: 0;
    font-size: 9px;
    color: var(--ink-soft);
    line-height: 1.4;
}
.unit-picker-modal-close {
    position: absolute;
    top: 0.65rem;
    right: 0.65rem;
    width: 21px;
    height: 21px;
    border-radius: 50%;
    border: 1px solid transparent;
    background: transparent;
    color: var(--ink-soft);
    font-size: 15px;
    line-height: 1;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.unit-picker-modal-close:hover { background: var(--accent-soft); color: var(--ink); }

/* The framed card inside the modal: search row at the top,
   hierarchy below. Single scroller (.unit-picker-frame-body). */
.unit-picker-frame {
    flex: 1 1 auto;
    margin: 0 1.25rem 1.25rem 1.25rem;
    border: 1px solid var(--rule);
    border-radius: 4px;
    background: var(--panel);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    min-height: 0;
}
.unit-picker-frame-toolbar {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.6rem;
    background: var(--accent-soft);
    border-bottom: 1px solid var(--rule);
}
.unit-picker-frame-body {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 0.4rem 0.6rem 0.6rem 0.6rem;
    min-height: 0;
}
/* Search input + Clear / Apply icon-buttons - shared between the
   framed toolbar (current) and the legacy search-row (kept for any
   stray caller). */
.unit-picker-search-icon {
    color: var(--ink-soft);
    flex: 0 0 auto;
}
.unit-picker-search-input {
    flex: 1 1 auto;
    padding: 0.55rem 0.75rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    background: var(--panel);
    color: var(--ink-medium);
    font: inherit;
    font-size: 10px;
    min-width: 0;
}
.unit-picker-search-input:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}
.unit-picker-search-input::-webkit-search-cancel-button { display: none; }

.unit-picker-action {
    flex: 0 0 auto;
    width: 25px;
    height: 25px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--panel);
    border: 1px solid var(--rule);
    border-radius: 3px;
    color: var(--ink-soft);
    cursor: pointer;
    transition: background 140ms ease, color 140ms ease, border-color 140ms ease;
}
.unit-picker-action:hover {
    background: var(--accent-soft);
    color: var(--ink);
    border-color: var(--accent);
}
.unit-picker-action:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}
.unit-picker-apply {
    background: var(--accent);
    border-color: var(--accent);
    color: #fff;
}
.unit-picker-apply:hover {
    background: var(--accent-hover);
    border-color: var(--accent-hover);
    color: #fff;
}


.filter-group {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
}

.filter-clear {
    background: transparent;
    border: 1px solid var(--rule);
    color: var(--ink-soft);
    cursor: pointer;
    height: 31px;
    padding: 0 0.95rem;
    border-radius: 3px;
    font: inherit;
    font-size: 9px;
    font-weight: 500;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.filter-clear:hover {
    border-color: var(--accent);
    color: var(--accent);
    background: var(--accent-soft);
}

.status-meta {
    color: var(--ink-soft);
    font-style: italic;
    margin-left: 0.5rem;
}

.filter-input:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}

/* Date + roster-nav cluster - date input on the left, three small
   pills (back 28 / Today / forward 28) on the right, all at the same
   46 px control height so they sit flush in the toolbar row. */
.date-controls {
    display: inline-flex;
    align-items: stretch;
    gap: 0.35rem;
}
/* ── Custom date popover (Today + month nav inside) ─────────────── */

.date-popover-wrap {
    position: relative;
    display: inline-flex;
    align-items: stretch;
}

/* Date trigger matches the Grade picker summary - same height,
   padding, border, background and font so all three primary
   pickers (Units, Grade, Date) read as one family. */
.date-popover-trigger {
    height: 31px;
    min-width: 147px;
    padding: 0.6rem 0.95rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    background: var(--panel);
    color: var(--ink-soft);
    font: inherit;
    line-height: 20px;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    text-align: left;
    user-select: none;
}
#date-popover-label {
    color: var(--ink-soft);
    font-weight: 400;
}

.date-popover-trigger:hover { border-color: var(--accent); }
.date-popover-trigger:focus-visible {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}

.date-popover-trigger-icon {
    font-size: var(--ui-arrow-size);
    font-weight: var(--ui-arrow-weight);
    color: var(--accent-hover);
    line-height: 1;
    margin-left: auto;
}

.date-popover {
    position: absolute;
    top: calc(100% + 3px);
    left: 0;
    z-index: 200;
    background: var(--panel);
    border: 1px solid var(--rule);
    border-radius: 4px;
    box-shadow: 0 5px 19px rgba(16, 42, 67, 0.12);
    padding: 0;
    display: none;
    width: 241px;
    user-select: none;
}
.date-popover.is-open { display: block; }

.date-popover-header {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.6rem 0.7rem;
    border-bottom: 1px solid var(--rule);
    background: var(--bg);
}
.date-popover-nav {
    background: transparent;
    border: 1px solid var(--rule);
    border-radius: 3px;
    cursor: pointer;
    width: 28px;
    height: 28px;
    font-size: var(--ui-arrow-size);
    font-weight: var(--ui-arrow-weight);
    color: var(--accent-hover);
    line-height: 1;
    padding: 0;
}
.date-popover-nav:hover { background: var(--accent-soft); border-color: var(--accent); }
.date-popover-today {
    margin-left: auto;
    padding: 0 1rem;
    height: 28px;
    border: 1px solid var(--rule);
    border-radius: 3px;
    background: var(--panel);
    color: var(--ink);
    font: inherit;
    font-size: 10px;
    font-weight: 600;
    cursor: pointer;
}
.date-popover-today:hover { background: var(--accent-soft); border-color: var(--accent); }
.date-popover-label {
    font-weight: 600;
    color: var(--ink);
    font-size: 11px;
    flex: 1 1 auto;
    text-align: center;
    min-width: 0;
}

.date-popover-grid {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    padding: 0.4rem 0.5rem 0.5rem;
    gap: 1px;
}
.date-popover-dow {
    font-size: 8px;
    font-weight: 600;
    color: var(--ink-soft);
    text-align: center;
    padding: 0.35rem 0;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.date-popover-day {
    height: 28px;
    border: 1px solid transparent;
    background: transparent;
    color: var(--ink);
    font: inherit;
    font-size: 10px;
    font-variant-numeric: tabular-nums;
    cursor: pointer;
    border-radius: 3px;
    padding: 0;
    text-align: center;
}
.date-popover-day:hover {
    background: var(--accent-soft);
    border-color: var(--accent);
}
.date-popover-day.is-other-month { color: var(--faint); }
.date-popover-day.is-today {
    border-color: var(--accent);
    font-weight: 700;
}
.date-popover-day.is-selected {
    background: var(--accent);
    color: white;
    font-weight: 700;
    border-color: var(--accent-hover);
}

.ask-button {
    height: 31px;
    padding: 0 1.4rem;
    margin-left: auto;
    /* "Ask a question" must never wrap on a small Trust laptop. The
       icon + label needs a hard floor on both width AND a no-wrap
       constraint so the button keeps its full label even when the
       parm row is tight. flex-shrink:0 stops siblings squeezing it. */
    white-space: nowrap;
    flex: 0 0 auto;
    min-width: 114px;
}

/* ── Grade multi-select picker ────────────────────────────────────── */

.grade-picker {
    position: relative;
    flex: 0 0 auto;
    z-index: 100;
}

.grade-summary {
    list-style: none;
    cursor: pointer;
    padding: 0.6rem 0.95rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    background: var(--panel);
    color: var(--ink-soft);
    height: 31px;
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    line-height: 20px;
    user-select: none;
    /* Doubled to match the Name + Units filter widths so the three
       primary filters read as a single visual family. */
    min-width: 456px;
}

.grade-summary::-webkit-details-marker { display: none; }
.grade-summary::after {
    content: '▾';
    margin-left: 0.45rem;
    color: var(--ink-soft);
    font-size: var(--ui-arrow-size);
    font-weight: var(--ui-arrow-weight);
    line-height: 1;
}

.grade-summary:hover { border-color: var(--accent); }
.grade-summary-label { color: var(--ink-soft); flex: 0 0 auto; }
.grade-summary-selection {
    color: var(--ink);
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    flex: 1 1 auto;
    min-width: 0;
}
.grade-summary-count {
    display: inline-block;
    min-width: 15px;
    padding: 0.05rem 0.45rem;
    background: var(--accent-soft);
    border: 1px solid var(--accent);
    border-radius: 7px;
    font-size: 9px;
    font-weight: 600;
    color: var(--ink);
    text-align: center;
}

details[open] > .grade-summary {
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}

.grade-form {
    position: absolute;
    top: 100%;
    left: 0;
    z-index: 200;
    min-width: 161px;
    margin-top: 3px;
    background: var(--panel);
    border: 1px solid var(--rule);
    border-radius: 3px;
    box-shadow: 0 3px 11px rgba(16, 42, 67, 0.08);
    display: flex;
    flex-direction: column;
}

.grade-list {
    max-height: 214px;
    overflow-y: auto;
    padding: 0.3rem;
}

label.grade-pick {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    padding: 0.35rem 0.6rem;
    cursor: pointer;
    border-radius: 2px;
}

label.grade-pick:hover { background: var(--accent-soft); }

label.grade-pick input[type=checkbox] {
    width: 11px;
    height: 11px;
    accent-color: var(--accent);
    cursor: pointer;
    flex: 0 0 auto;
}

.grade-empty {
    padding: 0.75rem;
    color: var(--ink-soft);
    font-style: italic;
    font-size: 10px;
}

.grade-actions {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 0.6rem;
    padding: 0.65rem 0.8rem;
    border-bottom: 1px solid var(--rule);
    background: var(--bg);
    /* Sticky-top so Apply / Clear stay visible regardless of how far
       the user has scrolled down the list. */
    position: sticky;
    top: 0;
    z-index: 1;
}

.grade-actions .primary-button,
.grade-actions .secondary-button {
    margin: 0;
    padding: 0.55rem 1.4rem;
    height: 29px;
    font-size: 10px;
    font-weight: 600;
}

/* "All" toggle as the first row inside the Grade list - same row
   layout as a regular grade, with a subtle separator below so it
   reads as a meta-row, not "another grade called All". */
.grade-pick.is-all {
    border-bottom: 1px solid var(--rule);
    font-weight: 600;
}

/* ── AI dialog ────────────────────────────────────────────────────── */

dialog.ai-dialog {
    width: min(375px, 92vw);
    border: 1px solid var(--rule);
    border-radius: 5px;
    padding: 0;
    background: var(--panel);
    color: var(--ink);
    box-shadow: 0 8px 21px rgba(16, 42, 67, 0.18);
}

dialog.ai-dialog::backdrop {
    background: rgba(16, 42, 67, 0.45);
}

.ai-dialog-form {
    padding: 1.4rem 1.5rem 1.2rem;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.ai-dialog-title {
    margin: 0;
    font-size: 1.25rem;
    font-weight: 600;
}

.ai-dialog-hint {
    margin: 0;
    color: var(--ink-soft);
    font-size: 9px;
}

.nl-examples {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    margin-top: 0.25rem;
}

.nl-examples-label {
    font-size: 9px;
    color: var(--ink-soft);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-weight: 600;
}

.nl-example {
    background: var(--accent-soft);
    border: 1px solid var(--rule);
    color: var(--ink);
    cursor: pointer;
    font: inherit;
    font-size: 9px;
    padding: 0.5rem 0.75rem;
    border-radius: 3px;
    text-align: left;
    line-height: 1.4;
}

.nl-example:hover {
    border-color: var(--accent);
    background: white;
}

.ai-dialog-hint em {
    color: var(--ink);
    font-style: italic;
}

.nl-input {
    width: 100%;
    padding: 0.6rem 0.8rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    font: inherit;
    background: var(--panel);
    color: var(--ink);
}

.nl-input:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}

.ai-dialog-actions {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: 0.6rem;
    margin-top: 0.2rem;
}

.ai-dialog-actions .primary-button { margin: 0; padding: 0.5rem 1rem; }

/* Natural-language Question chip on the Filtering-by strip. Sits
   alongside every other .active-filter-chip but reads a touch
   stronger so the user can see "this is the question that drove
   the filters below it". Same chrome - just a saturated accent
   border so it doesn't disappear into the soft-pastel neighbours. */
.active-filter-chip-question {
    border-color: var(--accent);
    background: var(--panel);
}
.active-filter-chip-question .active-filter-chip-key {
    color: var(--accent-hover);
}
/* "Couldn't answer" state - pale red wash so it reads as a failed
   ask at a glance without shouting. Used when Actionable=false on
   the active log row (rate-limited / not-configured / unparseable
   / AI declined to apply filters). The key colour shifts to RAG red
   so it reads as a status, not just a different chip. */
.active-filter-chip-question-unanswered {
    border-color: var(--rag-red-soft-border);
    background: var(--rag-red-soft-bg);
}
.active-filter-chip-question-unanswered .active-filter-chip-key {
    color: var(--rag-red);
}

/* Search results panel inside the units picker dialog - flows inline
   beneath the sticky search input. The dialog chrome provides the
   border/shadow/background. */
.unit-results {
    background: var(--panel);
    overflow-y: auto;
    flex: 1 1 auto;
    min-height: 0;
}

.unit-results:empty { display: none; }

.unit-results-empty {
    padding: 1rem;
    color: var(--ink-soft);
    font-style: italic;
    font-size: 11px;
}

.unit-pick-scroll {
    overflow-y: auto;
    flex: 1 1 auto;
    min-height: 0;
}

/* Division (collapsible group) */

details.division {
    border-bottom: 1px solid var(--rule);
}
details.division:last-of-type { border-bottom: none; }

.division-summary {
    list-style: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.55rem 0.8rem;
    background: var(--panel);
    color: var(--ink);
    user-select: none;
    /* Stick to the top of the scrollable picker as the user scrolls
       past each division. The next division's summary pushes the
       previous one out when it reaches the top - one header is
       visible at any time, which gives the user a constant sense of
       which division they're currently inside. */
    position: sticky;
    top: 0;
    z-index: 1;
    border-bottom: 1px solid var(--rule);
}
details.division:not(:last-of-type) .division-summary {
    /* Each header carries the bottom rule of its OWN section so the
       sticky stack visually separates from the rows below it. */
}

.division-summary::-webkit-details-marker { display: none; }

.division-summary:hover { background: var(--accent-soft); }

/* Division collapse caret. One glyph (▸) rotated 90° when the
   <details> opens, so the closed -> open transition animates
   instead of doing an instant glyph swap. Sized + weighted via
   the shared --ui-arrow-* tokens so it matches every other
   disclosure arrow in the app. Honours prefers-reduced-motion. */
.caret {
    display: inline-block;
    width: 1ch;
    font-size: var(--ui-arrow-size);
    font-weight: var(--ui-arrow-weight);
    color: var(--accent-hover);
    line-height: 1;
    margin-right: 0.15rem;
    transform-origin: 50% 50%;
    transition: transform 140ms ease;
}
.caret::before { content: '▸'; }
details[open] > .division-summary .caret { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) {
    .caret { transition: none; }
}

/* Division-level "tick all units in this group" checkbox. Wrapped
   in a label so the hit area is comfortable; click bubbling is
   stopped in JS so it doesn't also toggle the <summary> expand. */
.division-pick {
    display: inline-flex;
    align-items: center;
    margin-right: 0.15rem;
    cursor: pointer;
}
.division-pick-input {
    width: 12px;
    height: 12px;
    cursor: pointer;
    accent-color: var(--accent);
}

.division-name {
    font-weight: 600;
    flex: 1 1 auto;
}

.division-count {
    color: var(--ink-soft);
    font-size: 9px;
}

.division-body {
    padding: 0.25rem 0.4rem 0.4rem 1.5rem;
    background: var(--bg);
}

.division-actions {
    display: flex;
    gap: 0.75rem;
    padding: 0.25rem 0.6rem 0.5rem;
}

.link-action {
    background: none;
    border: none;
    color: var(--accent);
    cursor: pointer;
    font: inherit;
    font-size: 9px;
    padding: 0;
    text-decoration: underline;
}

.link-action:hover { color: var(--accent-hover); }

label.unit-pick {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    padding: 0.3rem 0.6rem;
    cursor: pointer;
    border-radius: 2px;
}

label.unit-pick:hover { background: var(--accent-soft); }

label.unit-pick input[type=checkbox] {
    width: 11px;
    height: 11px;
    accent-color: var(--accent);
    cursor: pointer;
    flex: 0 0 auto;
}

/* "All units" toggle row at the top of the Unit-search results -
   same visual treatment as the Grade picker's "All" row so the
   pattern reads as one shared component across filters. */
label.unit-pick.is-all {
    padding: 0.55rem 0.85rem;
    border-bottom: 1px solid var(--rule);
    font-weight: 600;
    background: var(--bg);
    border-radius: 0;
}

/* ── Unit chips ───────────────────────────────────────────────────────── */

.unit-chips {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    margin: 0.9rem 0 0;
    min-height: 1.75rem;
}

/* When no chips are selected the strip collapses - Staff grid already
   defaults to "all units in scope", so the row would otherwise be a
   confusing empty box. */
.unit-chips.is-empty {
    margin: 0;
    min-height: 0;
}

.unit-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.3rem 0.55rem 0.3rem 0.85rem;
    background: var(--accent-soft);
    color: var(--ink);
    border: 1px solid var(--rule);
    border-radius: 669px;
    font-size: 10px;
}

.unit-chip button {
    background: transparent;
    border: none;
    color: var(--ink-soft);
    cursor: pointer;
    font: inherit;
    padding: 0;
    width: 13px;
    height: 13px;
    line-height: 12px;
    border-radius: 50%;
    text-align: center;
    font-size: 11px;
}

.unit-chip button:hover {
    background: var(--accent);
    color: white;
}

.clear-all {
    background: transparent;
    border: 1px solid var(--rule);
    color: var(--ink-soft);
    cursor: pointer;
    font: inherit;
    font-size: 9px;
    padding: 0.2rem 0.7rem;
    border-radius: 669px;
    margin-left: auto;
}

.clear-all:hover {
    border-color: var(--accent);
    color: var(--accent);
}

/* ── Balance grid ─────────────────────────────────────────────────────── */

.empty-state {
    padding: 3rem 1.5rem;
    text-align: center;
    color: var(--ink-soft);
    background: var(--panel);
    border: 1px solid var(--rule);
    border-radius: 4px;
    margin-top: 1rem;
    font-size: 11px;
}

/* Animated spinner used while a grid (or any HTMX target) is loading. */
@keyframes spinner-spin {
    to { transform: rotate(360deg); }
}

.spinner {
    display: inline-block;
    width: 19px;
    height: 19px;
    border: 2px solid var(--rule);
    border-top-color: var(--accent);
    border-radius: 50%;
    animation: spinner-spin 0.8s linear infinite;
}

/* Empty-state loader for the balances grid. Reserves the full grid
   height so the page doesn't reflow when the first render arrives -
   the toolbar / movers / status-bar all stay anchored. */
.loading-state {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.75rem;
    min-height: calc(100vh - 228px);
    color: var(--ink-soft);
    background: var(--panel);
    border: 1px solid var(--rule);
    border-radius: 4px;
    margin-top: 1rem;
    font-size: 11px;
}
.loading-text { font-weight: 500; color: var(--ink); }

/* ── Skeleton rows for in-flight grid loads ─────────────────────
   Shown in place of the empty-grid placeholder while the first
   /balances/grid request is in flight; replaces the brief "blank
   panel then content" flicker with a stable shape that reads as
   'loading the right amount of data'. Honours reduced-motion. */
/* Skeleton renders as a real .balance-grid table so the column
   widths line up exactly with the eventual grid - .staff-col,
   .unit-col, .contract-col, .num and .trail-col all apply.
   Container fills viewport; inner table scrolls if there are
   more rows than fit. */
.skeleton-grid {
    margin-top: 1rem;
}
.skeleton-scroll {
    /* Fill the viewport between the toolbar and the activity
       strip. min-height anchors the panel even when there are
       fewer rows than needed; max-height clips overflow so the
       page doesn't grow taller than the viewport. */
    min-height: calc(100vh - 147px);
    max-height: calc(100vh - 147px);
}
.skeleton-balance-grid {
    /* Suppress the hover affordances that the real grid has -
       skeleton rows aren't interactive. */
    pointer-events: none;
    /* Fixed layout so the colgroup widths anchor every column,
       independent of what content sits inside each cell. Without
       this the empty .skeleton-cell divs would let auto layout
       collapse columns to their min-width. */
    table-layout: fixed;
    width: max-content;
    min-width: 100%;
}
.skeleton-balance-grid tbody tr:hover {
    background: inherit;
}
.skeleton-balance-grid td,
.skeleton-balance-grid th {
    /* Defeat any .balance-grid max-width caps that would otherwise
       fight the colgroup. With table-layout: fixed + explicit col
       widths these caps don't apply, but be explicit for clarity. */
    max-width: none;
    min-width: 0;
}
/* Reserve a strip for where the real column headers + the few
   sticky toolbar / status-bar rows will land when data arrives -
   same background as the real grid head, no animation, no
   content. Tall enough to cover the header row plus a couple of
   row heights so the animated body starts further down. */
.skeleton-head-strip th {
    background: var(--bg);
    border-bottom: 1px solid var(--rule);
    height: 74px;
}

/* The animated bar inside each table cell. Gentle opacity
   pulse rather than the previous dramatic diagonal sweep -
   reads as 'data on its way' without competing for attention. */
.skeleton-cell {
    height: 9px;
    background: var(--accent);
    border-radius: 2px;
    animation: skeleton-pulse 1500ms ease-in-out infinite;
    opacity: 0.35;
}
/* Pad each skeleton row out so it matches the real grid's row
   height (the real .balance-grid td has ~0.5rem vertical padding;
   the empty skeleton cell would otherwise collapse to ~9px). */
.skeleton-balance-grid tbody td {
    padding-top: 0.55rem;
    padding-bottom: 0.55rem;
}
.skeleton-cell.is-head {
    height: 6px;
    background: var(--accent-soft);
    border: 1px solid var(--rule);
    width: 55%;
    animation: none;
    opacity: 1;
}
.skeleton-cell.is-narrow { width: 65%; }
.skeleton-cell.is-short  { width: 40%; }

/* Stagger the pulse across rows so it reads as a slow wave
   moving down the grid rather than every row pulsing in unison.
   Six tiers - rows cycle through them via :nth-child. */
.skeleton-balance-grid tbody tr:nth-child(6n+1) .skeleton-cell { animation-delay: 0ms; }
.skeleton-balance-grid tbody tr:nth-child(6n+2) .skeleton-cell { animation-delay: 120ms; }
.skeleton-balance-grid tbody tr:nth-child(6n+3) .skeleton-cell { animation-delay: 240ms; }
.skeleton-balance-grid tbody tr:nth-child(6n+4) .skeleton-cell { animation-delay: 360ms; }
.skeleton-balance-grid tbody tr:nth-child(6n+5) .skeleton-cell { animation-delay: 480ms; }
.skeleton-balance-grid tbody tr:nth-child(6n+0) .skeleton-cell { animation-delay: 600ms; }

@keyframes skeleton-pulse {
    0%, 100% { opacity: 0.18; }
    50%      { opacity: 0.6; }
}
@media (prefers-reduced-motion: reduce) {
    .skeleton-cell { animation: none; opacity: 0.35; }
}

/* KPI grid - a fail flag on the matching Pass column tints the
   parent metric's cell red. Same colour weight as the R/A/G red
   used elsewhere so the eye reads "outside target". */
.balance-grid.kpis-grid td.kpi-fail {
    color: var(--rag-red);
    font-weight: 600;
}
/* Compliance mode cells: large tick / cross glyphs centred in the
   cell instead of a numeric value. kpi-pass tints green; kpi-fail
   (defined above) tints red - same RAG palette as everywhere else. */
.balance-grid.kpis-grid td.kpi-compliance-cell {
    text-align: center;
    font-size: 12px;
    font-weight: 700;
}
.balance-grid.kpis-grid td.kpi-pass {
    color: var(--rag-green);
    font-weight: 600;
}

/* Compliance + Grouped: per-column "% pass" cells in the group-header
   row. Sit alongside the existing group toggle (colspan=3 across the
   WG icon cells) and pick up the same green / red tint when the
   group-wide pass rate is high / low. */
.balance-grid.kpis-grid tr.group-header-compliance td.kpi-compliance-pct {
    font-weight: 700;
    font-size: 10px;
    padding: 0.2rem 0.6rem;
    background: color-mix(in srgb, var(--accent) 8%, var(--panel));
}
/* Explicit horizontal scroll for the KPI grid - the proc returns
   a wide column set that always exceeds the viewport. The outer
   .balance-grid-container would otherwise clip the inner scroll
   bar (it has overflow: hidden for the corner-radius); override
   that for KPI specifically so the bar can be seen. */
/* KPI grid scroll fix. Was relying on :has() but that's not honoured
   reliably on the Trust-laptop Chromium versions in the field. The
   Razor view now stamps explicit kpis-grid-container + kpis-grid-scroll
   classes on the wrappers so these rules win on every browser. */
.balance-grid-container.kpis-grid-container {
    overflow: visible;
}
.balance-grid-scroll.kpis-grid-scroll {
    overflow-x: auto;
    overflow-y: auto;
    max-width: 100%;
    width: 100%;
    min-width: 0;
}
.balance-grid.kpis-grid {
    /* Per-column min-width so dense KPI tables stay readable rather
       than crushing to the narrowest possible width when the natural
       table width happens to fit the container. */
    min-width: max-content;
    width: max-content;
}
.balance-grid.kpis-grid th,
.balance-grid.kpis-grid td {
    white-space: nowrap;
    min-width: 74px;
}
/* Fixed leftmost Ward Guardian icon cells - narrow, just enough
   for a 36 px row-icon-btn each. */
.balance-grid.kpis-grid th.kpi-wg-col,
.balance-grid.kpis-grid td.kpi-wg-col {
    min-width: 29px;
    width: 29px;
    padding: 3px 4px;
    text-align: center;
}
.balance-grid.kpis-grid td.kpi-wg-icon-cell { padding: 1px; }
/* Category-row header (row 1 of the two-row KPI header). One cell
   per category, colspans the count of metric columns under it.
   Subtle alternating tint per category so the eye can keep its
   place when scrolling sideways through a wide table. */
.balance-grid.kpis-grid thead tr.kpi-category-row th {
    position: sticky;
    top: 0;
    background: var(--accent-soft);
    color: var(--ink);
    text-align: center;
    font-weight: 700;
    font-size: 9px;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    border-bottom: 1px solid var(--accent);
    padding: 3px 5px;
    z-index: 3;
}
/* KPI category-row tints alternate pale-blue / white per section
   so the eye reads "this is a new metric family" without the
   previous 14-pastel rainbow. The Ward Guardian leftmost cell
   keeps a slightly deeper blue so it reads as identity / always-
   on rather than a regular metric section. */
.balance-grid.kpis-grid .kpi-category-row .kpi-cat-ward-guardian {
    background: color-mix(in srgb, var(--accent) 18%, var(--panel));
}
.balance-grid.kpis-grid .kpi-category-row th.kpi-cat:nth-of-type(odd) {
    background: color-mix(in srgb, var(--accent) 10%, var(--panel));
}
.balance-grid.kpis-grid .kpi-category-row th.kpi-cat:nth-of-type(even) {
    background: var(--panel);
}
/* The row-2 sticky thead has to sit below the row-1 thead. The
   existing .balance-grid thead th rule sets top: 0; row-2 needs
   to push down by the row-1 height (~ 30 px including borders). */
.balance-grid.kpis-grid thead tr:not(.kpi-category-row) th {
    top: 20px;
    z-index: 2;
}

/* Icon-only Share button - sized to match the surrounding parm
   inputs (46 px tall to align with .filter-input). Square footprint
   so the icon sits centered; no inner label. No auto-margin -
   Share is treated as a parm-row item with the standard gap; the
   Ask cluster (below) is what gets pushed to the far right. */
.share-view-button.share-view-iconic {
    height: 31px;
    width: 31px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

/* Ask + Help pair wrapped together so they sit a few pixels apart
   while the cluster as a whole anchors to the far-right edge of
   the parm row. Help is rightmost; Ask sits immediately to its
   left. Override the row's larger gap by giving the cluster its
   own tight inner gap. */
.parm-row-ask-cluster {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    margin-left: auto;
}
.share-view-button.share-view-iconic .icon { width: 13px; height: 13px; }

/* Prev/next-month arrows flanking the As-of date picker. Match the
   date-popover-trigger height so the trio reads as one control. */
.date-nav-step {
    height: 31px;
    width: 24px;
    padding: 0;
    background: var(--panel);
    border: 1px solid var(--rule);
    border-radius: 3px;
    color: var(--ink-soft);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background 140ms ease, border-color 140ms ease, color 140ms ease;
}
.date-nav-step:hover { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
.date-nav-step:focus-visible { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.date-nav-step .icon { width: 12px; height: 12px; }

/* Prev / next-staff navigation pair on the Balance History modal.
   Arrow + person icon side-by-side reads as "navigate to a person"
   without needing the verbose label that used to sit there. Same
   visual idiom as the .row-icon-btn family but a touch wider so
   the two SVGs sit comfortably. */
.history-nav-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    height: 24px;
    padding: 0 0.6rem;
    background: var(--panel);
    border: 1px solid var(--rule);
    border-radius: 3px;
    color: var(--ink-soft);
    cursor: pointer;
    text-decoration: none;
    transition: background 140ms ease, border-color 140ms ease, color 140ms ease;
}
.history-nav-btn:hover { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
.history-nav-btn:focus-visible { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.history-nav-btn .icon { width: 12px; height: 12px; }
.history-nav-btn .icon-sm { width: 9px; height: 9px; }
.history-nav-disabled { opacity: 0.35; cursor: not-allowed; pointer-events: none; }

/* KPI cell-click charts modal - distribution bar + per-unit
   trend with linear-regression forecast band. */
.kpi-chart-modal { width: min(858px, 95vw); }
.kpi-chart-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    margin: 0.2rem 0 1rem;
}
.kpi-chart-title {
    margin: 0.2rem 0 0.3rem;
    font-size: 15px;
    font-weight: 700;
    color: var(--ink);
}
.kpi-chart-meta { margin: 0; font-size: 10px; color: var(--ink-soft); }
.kpi-chart-actions { display: flex; align-items: center; gap: 0.6rem; }
.kpi-chart-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 10px;
    color: var(--ink);
    cursor: pointer;
}
.kpi-chart-toggle input { width: 12px; height: 12px; accent-color: var(--accent); cursor: pointer; }

.kpi-chart-section {
    margin: 0 0 1.4rem;
    padding: 0.9rem 1rem 1rem;
    border: 1px solid var(--rule);
    border-radius: 4px;
    background: var(--panel);
}
.kpi-chart-section-headrow {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
}
.kpi-chart-section-title {
    margin: 0 0 0.25rem;
    font-size: 11px;
    font-weight: 700;
    color: var(--ink);
}
.kpi-chart-section-sub {
    margin: 0 0 0.6rem;
    font-size: 10px;
    color: var(--ink-soft);
}
.kpi-chart-canvas-wrap { position: relative; height: 214px; }

/* Per-unit chips on the trend chart. Click to toggle visibility. */
.kpi-chart-unit-filter {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    max-width: 60%;
}
.kpi-chart-chip {
    --chip-col: var(--accent);
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    height: 24px;
    padding: 0 0.85rem;
    border: 1px solid var(--rule);
    border-radius: 669px;
    background: var(--panel);
    color: var(--ink-soft);
    font-size: 10px;
    font-weight: 500;
    cursor: pointer;
    transition: background 140ms ease, border-color 140ms ease, color 140ms ease;
}
.kpi-chart-chip-dot {
    display: inline-block;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--chip-col);
}
.kpi-chart-chip.is-active {
    background: color-mix(in srgb, var(--chip-col) 12%, var(--panel));
    border-color: var(--chip-col);
    color: var(--ink);
}
.kpi-chart-chip:hover { border-color: var(--chip-col); color: var(--ink); }

/* KPI grid - clickable numeric cells. The whole cell becomes
   clickable (data-metric attribute supplied by the view) so
   the chart modal opens with the right metric pre-selected. */
.balance-grid.kpis-grid td[data-metric] {
    cursor: pointer;
    transition: background 120ms ease;
}
.balance-grid.kpis-grid td[data-metric]:hover {
    background: color-mix(in srgb, var(--accent) 8%, var(--panel));
    outline: 1px solid var(--accent);
    outline-offset: -1px;
}

/* KPI grid status bar - multiple controls (Roster start picker,
   verbosity, Unit + KPI profile pickers, mode toggle, Export CSV).
   Single-row strip - if more controls than fit, the strip scrolls
   horizontally so the grid below never gets pushed down. Same
   pattern as .active-filters (see design-brief "Strips above the
   grid never grow vertically").
   display: flex set explicitly so we don't depend on the parent's
   :has(> .status-text) rule (browser-compat). gap matches the
   active-filters strip; align-items vertically centres labels +
   controls. */
.kpi-status-bar {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    gap: 0.6rem;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-color: color-mix(in srgb, var(--accent) 55%, var(--panel)) transparent;
    scrollbar-width: thin;
}
.kpi-status-bar > .status-text { flex: 1 1 auto; min-width: 0; }
.kpi-status-bar::-webkit-scrollbar { height: 4px; }
.kpi-status-bar::-webkit-scrollbar-track { background: transparent; }
.kpi-status-bar::-webkit-scrollbar-thumb {
    background: color-mix(in srgb, var(--accent) 55%, var(--panel));
    border-radius: 4px;
}
.kpi-status-control {
    display: inline-flex;
    align-items: center;
    flex-shrink: 0;
}

/* KPI profile picker + Manage Profiles modal. Picker sized to
   match other status-bar controls (36 px) to avoid visual jump. */
.kpi-profile-picker {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
}
.kpi-profile-select {
    height: 24px;
    min-width: 134px;
    padding: 0 0.5rem;
    font-size: 10px;
    border: 1px solid var(--rule);
    border-radius: 3px;
    background: var(--panel);
    color: var(--ink);
}
.kpi-profile-manage-btn {
    height: 24px;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0 0.7rem;
    font-size: 9px;
}

.kpi-profiles-modal { width: min(590px, 96vw); }
.kpi-profiles-grid {
    display: grid;
    grid-template-columns: 161px 1fr;
    gap: 1rem;
    margin-top: 0.6rem;
}
.kpi-profiles-list {
    border: 1px solid var(--rule);
    border-radius: 4px;
    padding: 0.6rem;
    background: var(--bg);
}
.kpi-profiles-new-btn {
    width: 100%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    margin-bottom: 0.6rem;
}
.kpi-profiles-section-header {
    font-size: 9px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--ink-soft);
    margin: 0.7rem 0 0.3rem;
}
.kpi-profiles-listitems { display: flex; flex-direction: column; gap: 0.2rem; }
.kpi-profiles-list-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.4rem;
    text-align: left;
    background: var(--panel);
    border: 1px solid var(--rule);
    border-radius: 3px;
    padding: 0.5rem 0.7rem;
    font-size: 10px;
    font-weight: 500;
    color: var(--ink);
    cursor: pointer;
    transition: background 140ms ease, border-color 140ms ease;
}
.kpi-profiles-list-item:hover { background: var(--accent-soft); border-color: var(--accent); color: var(--ink); }
/* Profile row with the Oceansblue-only Copy OrgUnitIds icon: the
   list-item button stretches; the export icon sits flush to the right
   with the same height for a clean grid edge. */
.kpi-profiles-list-row { display: flex; align-items: stretch; gap: 0.25rem; }
.kpi-profiles-list-row .kpi-profiles-list-item { flex: 1 1 auto; }
.unit-profile-export-btn { align-self: stretch; }
.kpi-profiles-trust-tag {
    background: var(--accent);
    color: white;
    font-size: 7px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    border-radius: 669px;
    padding: 1px 5px;
}

.kpi-profiles-editor {
    border: 1px solid var(--rule);
    border-radius: 4px;
    padding: 1rem;
    background: var(--panel);
}
.kpi-profiles-visibility {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}
.kpi-profiles-radio {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 10px;
    color: var(--ink);
    cursor: pointer;
}
.kpi-profiles-radio input[type=radio] { width: 12px; height: 12px; accent-color: var(--accent); }
.kpi-profiles-categories {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.4rem 0.9rem;
}
.kpi-profiles-cat-check {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 10px;
    color: var(--ink);
    cursor: pointer;
}
.kpi-profiles-cat-check input[type=checkbox] {
    width: 12px;
    height: 12px;
    accent-color: var(--accent);
    cursor: pointer;
}
.kpi-profiles-cat-actions {
    margin-top: 0.5rem;
    font-size: 9px;
    color: var(--ink-soft);
}
.link-action {
    background: transparent;
    border: none;
    color: var(--accent);
    font: inherit;
    font-size: 9px;
    cursor: pointer;
    padding: 0;
}
.link-action:hover { text-decoration: underline; }

/* Unit profile picker - sized like other toolbar parm inputs
   (46 px) since it sits in the filter-row, not a status bar. */
.unit-profile-picker .kpi-profile-select { height: 31px; min-width: 121px; }
.unit-profile-picker .kpi-profile-manage-btn { height: 31px; }
.unit-profile-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
    padding: 0.4rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    background: var(--bg);
    min-height: 29px;
    max-height: 134px;
    overflow-y: auto;
}
/* Roster-end picker in the main toolbar (KPI mode only). Sits in
   its own .filter-group so it flows with the rest of the parm row;
   the inner label keeps a 'Roster end' caption next to the input. */
.kpi-roster-end-toolbar {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    font-size: 10px;
    color: var(--ink-soft);
}
.kpi-roster-end-toolbar-label { font-weight: 600; color: var(--ink); }
.kpi-roster-end-input {
    height: 24px;
    padding: 0 0.5rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    background: var(--panel);
    color: var(--ink);
    font: inherit;
    font-size: 10px;
    cursor: pointer;
    transition: border-color 140ms ease, box-shadow 140ms ease;
}
.kpi-roster-end-input:hover { border-color: var(--accent); }
.kpi-roster-end-input:focus-visible {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}

/* ── Welcome tour ─────────────────────────────────────────────
   Seven-step popover walk-through that auto-runs once on a user's
   first sign-in. .tour-overlay is the root container (rendered
   hidden in _Layout.cshtml). .tour-backdrop is the soft scrim;
   .tour-card is the floating step bubble positioned by JS relative
   to the target element named in TOUR_STEPS[i].selector. */
.tour-overlay {
    position: fixed;
    inset: 0;
    z-index: 4000;
}
.tour-overlay[hidden] { display: none; }
.tour-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(16, 42, 67, 0.32);
    backdrop-filter: blur(1px);
}
.tour-card {
    position: fixed;
    width: min(255px, calc(100vw - 21px));
    background: var(--panel);
    border: 1px solid var(--rule);
    border-radius: 5px;
    box-shadow: 0 8px 21px rgba(16, 42, 67, 0.28);
    padding: 1.1rem 1.25rem 1rem;
    z-index: 1;
}
.tour-step-pill {
    display: inline-block;
    font-size: 8px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--accent);
    background: var(--accent-soft);
    border-radius: 669px;
    padding: 2px 7px;
    margin-bottom: 0.55rem;
}
.tour-title {
    margin: 0 0 0.5rem;
    font-size: 13px;
    font-weight: 700;
    color: var(--ink);
}
.tour-body {
    margin: 0 0 0.9rem;
    font-size: 11px;
    line-height: 1.45;
    color: var(--ink);
}
.tour-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
.tour-spacer { flex: 1 1 auto; }
.tour-skip {
    background: transparent;
    border: none;
    color: var(--ink-soft);
    font: inherit;
    font-size: 10px;
    cursor: pointer;
    padding: 4px 3px;
}
.tour-skip:hover { color: var(--accent); text-decoration: underline; }
.tour-back,
.tour-next {
    min-height: 24px;
    padding: 0 1rem;
    font-size: 10px;
}
.tour-back[disabled] { opacity: 0.45; cursor: not-allowed; }
/* Highlight ring on the active target. Sits above other chrome
   without obstructing it; the slight outline + pulsing accent
   draws the eye without yelling. */
.tour-highlight {
    position: relative;
    z-index: 4001;
    box-shadow: 0 0 0 3px var(--accent), 0 0 0 5px rgba(111, 149, 187, 0.35);
    border-radius: 4px;
    transition: box-shadow 200ms ease;
}
@media (prefers-reduced-motion: reduce) {
    .tour-highlight { transition: none; }
}

/* Fade-in for the balance grid when its swap completes. HTMX adds
   the swap-in animation via #balance-grid, but we want the inner
   grid container to fade gently too. */
.balance-grid-container { animation: balance-fade-in 220ms ease; }
@keyframes balance-fade-in {
    from { opacity: 0; transform: translateY(1px); }
    to   { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
    .balance-grid-container { animation: none; }
}

/* ESR Division column doubled in width so long division names
   ("Surgery, Critical Care and Anaesthetics") fit without wrapping
   or truncating. Applied across every grid that surfaces the
   division - Staff, Units, Divisions. */
.balance-grid .division-col {
    min-width: 241px;
}

/* Notes column - AI commentary. Given more headroom than the
   other cells because populated commentary runs multi-paragraph;
   the column is the natural place for the eye to rest at the
   end of the row. Bumped up to absorb the width released by the
   narrower Staff and Unit columns. */
.notes-cell {
    min-width: 429px;
    max-width: 549px;
}

/* Staff column - narrower than auto. Long names ellipsis;
   native title on the link gives the full name on hover.
   Trimmed twice (now ~57 % of the original 105 px) to surface
   more room in the wider Unit / Division columns either side. */
.balance-grid .staff-col {
    max-width: 40px;
    min-width: 30px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.balance-grid .staff-col .drill-link {
    display: inline-block;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    vertical-align: middle;
}

/* Unit column - widened to fit longer ward names without clip.
   Still ellipsis on outliers; native title on the td carries
   the full text. Bumped 25 % so longer unit names + the WG
   shield fit on one line more often. */
.balance-grid .unit-col {
    max-width: 235px;
    min-width: 151px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Contract column - shortened header ('Contract') so the column
   is much narrower than the old 'CONTRACTED HRS' width. Single
   tabular number, right-aligned. */
.balance-grid .contract-col {
    max-width: 54px;
    min-width: 47px;
}

.generate-notes {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    background: transparent;
    border: 1px solid var(--accent);
    color: var(--accent);
    cursor: pointer;
    font: inherit;
    font-size: 9px;
    padding: 0.4rem 0.85rem;
    border-radius: 3px;
}

.generate-notes:hover {
    background: var(--accent);
    color: white;
}
.generate-notes:disabled { cursor: progress; opacity: 0.85; }

/* Loading swap: HTMX adds .htmx-request to the button while the
   commentary request is in flight, so the user sees an explicit
   "Generating..." label + spinner instead of the silent press
   the previous Generate flow had. */
/* Slot containing either the sparkle icon (idle) or the spinner
   (loading). Same width either way so the button doesn't reflow
   on click. */
.gen-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 9px;
    height: 9px;
    flex: 0 0 auto;
}
.generate-notes .gen-icon-loading { display: none; }
.generate-notes.htmx-request .gen-icon-idle { display: none; }
.generate-notes.htmx-request .gen-icon-loading { display: inline-block; }

/* Loading state swaps the "Generate Notes" label for "Generating..."
   text. The spinner itself lives in .gen-icon (handled above), so
   .gen-loading carries text only - no inline spinner here. Hidden
   until HTMX adds .htmx-request to the button so the button doesn't
   render "Generating..." from first paint. */
.generate-notes .gen-loading { display: none; }
.generate-notes.htmx-request .gen-label { display: none; }
.generate-notes.htmx-request .gen-loading { display: inline; }
.gen-loading-spinner {
    display: inline-block;
    width: 8px;
    height: 8px;
    border: 1px solid currentColor;
    border-top-color: transparent;
    border-radius: 50%;
    animation: spinner-spin 0.8s linear infinite;
    flex: 0 0 auto;
}

.commentary-placeholder {
    color: var(--ink-soft);
    font-style: italic;
    font-size: 9px;
}

/* Rendered AI commentary cell - small body text, soft accent bar on
   the left so the eye picks it out as a meta annotation rather than
   another grid value. White-space pre-wrap so single-paragraph text
   wraps naturally inside the cell. */
.commentary-text {
    display: block;
    font-size: 9px;
    line-height: 1.4;
    color: var(--ink);
    border-left: 2px solid var(--accent);
    background: var(--accent-soft);
    padding: 0.45rem 0.6rem;
    border-radius: 1px;
    /* One flowing paragraph - any newlines from the AI collapse to
       spaces so the text wraps naturally within the column width. */
    white-space: normal;
}

/* Commentary wrapper holds the prose + a small actions row beneath.
   Default state shows the prose with a labeled "Hide Notes" pill in
   the actions row; the .is-collapsed state (or .all-notes-hidden on
   the grid) hides both and reveals a "Show Notes" pill the user can
   click to expand the note again. Both states are reached without
   touching the cached commentary, so re-opening costs nothing. */
.commentary-wrap {
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.commentary-actions {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: 4px;
}
.commentary-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    border: 1px solid var(--rule);
    background: var(--panel);
    color: var(--ink-soft);
    cursor: pointer;
    font: inherit;
    font-size: 9px;
    padding: 0.2rem 0.65rem;
    border-radius: 669px;
    line-height: 1;
    transition: background 140ms ease, border-color 140ms ease, color 140ms ease;
}
.commentary-toggle:hover {
    background: var(--accent-soft);
    color: var(--accent);
    border-color: var(--accent);
}
.commentary-toggle-show {
    display: none;
    align-self: flex-start;
    color: var(--accent);
}

/* Collapsed state - per-row (.is-collapsed) or grid-wide
   (.balance-grid.all-notes-hidden). Same effect either way: hide
   the prose + the actions row, reveal the Show Notes pill. */
.commentary-wrap.is-collapsed .commentary-text,
.commentary-wrap.is-collapsed .commentary-actions,
.balance-grid.all-notes-hidden .commentary-wrap .commentary-text,
.balance-grid.all-notes-hidden .commentary-wrap .commentary-actions {
    display: none;
}
.commentary-wrap.is-collapsed .commentary-toggle-show,
.balance-grid.all-notes-hidden .commentary-wrap .commentary-toggle-show {
    display: inline-flex;
}

/* Open link in the Staff grid → goes to /staff/{personId}. */
a.drill-link {
    text-decoration: none;
}

/* Plain status-bar - text-only. Block layout so the inline content
   (a mix of <strong> elements and text runs from the Razor template)
   reads as one paragraph, not as scattered flex items. */
.status-bar {
    padding: 0.7rem 0.95rem;
    color: var(--ink-soft);
    font-size: 10px;
    border: 1px solid var(--rule);
    background: var(--panel);
    border-radius: 4px 4px 0 0;
    border-bottom: none;
    margin-top: 1rem;
    line-height: 1.4;
}

/* Status-bar variant - when the template wraps the text in
   .status-text alongside another action (e.g. the Staff grid's
   "+ Generate all" button), switch to a two-column flex layout so
   the button anchors right. */
.status-bar:has(> .status-text) {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
}
.status-text { flex: 1 1 auto; }

.status-bar strong { color: var(--ink); font-weight: 600; }

/* Export-CSV control in the grid's status-bar (replaces the old
   tab-bar-level export link). Anchored right so it sits next to
   the existing Group-by select on the Staff grid, and on its own
   on the Units / Divisions / Duties / Sign-offs grids. */
.status-bar-export {
    margin-left: auto;
    height: 24px;
    padding: 0 0.95rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    background: var(--panel);
    color: var(--accent);
    font: inherit;
    font-size: 9px;
    font-weight: 600;
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    white-space: nowrap;
    transition: background 140ms ease, border-color 140ms ease, color 140ms ease, box-shadow 140ms ease;
}
.status-bar-export:hover {
    background: var(--accent-soft);
    border-color: var(--accent);
}
.status-bar-export:focus-visible {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}

.generate-all-btn {
    background: transparent;
    border: 1px solid var(--accent);
    color: var(--accent);
    cursor: pointer;
    font: inherit;
    font-size: 9px;
    font-weight: 500;
    padding: 0.4rem 0.85rem;
    border-radius: 3px;
    white-space: nowrap;
    transition: background 140ms ease, color 140ms ease, border-color 140ms ease, box-shadow 140ms ease;
}

.generate-all-btn:hover {
    background: var(--accent);
    color: white;
}
.generate-all-btn:focus-visible {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}

.balance-grid-container {
    background: var(--panel);
    border: 1px solid var(--rule);
    border-radius: 0 0 4px 4px;
    /* overflow: visible (NOT hidden) so the inner scroll wrapper's
       horizontal scrollbar isn't clipped at the bottom corner. The
       corner-radius is preserved via the inner wrappers; never let
       a wide grid lose columns off the right edge invisibly. */
    overflow: visible;
}

/* "Matched staff" chips on /sign-offs rows - shown when the
   current search hit a captured staff member rather than the
   unit name. Compact pill list with hover-title for the full
   name; '+N more' for events that matched lots of people. */
.signoff-matched-cell {
    max-width: 174px;
    line-height: 1.6;
}
.signoff-matched-chip {
    display: inline-block;
    margin: 0 0.25rem 0.15rem 0;
    padding: 0.05rem 0.55rem;
    border: 1px solid var(--accent);
    background: var(--accent-soft);
    color: var(--ink);
    border-radius: 669px;
    font-size: 8px;
    white-space: nowrap;
}
.signoff-matched-more {
    display: inline-block;
    margin-left: 0.25rem;
    font-size: 8px;
    color: var(--ink-soft);
    font-style: italic;
}

/* Retraction (Unsigned) rows on /sign-offs - styled subtly so the
   row is clearly NOT a fresh sign-off but stays scannable. The
   small chip in the actions column flags it as a retraction. */
.balance-grid tr.signoff-unsigned td {
    color: var(--ink-soft);
    background: #fbfcfe;
}
.balance-grid tr.signoff-unsigned td .drill-link {
    text-decoration: line-through;
    text-decoration-color: var(--ink-soft);
}
.signoff-unsigned-tag {
    display: inline-block;
    padding: 0.05rem 0.55rem;
    border: 1px solid var(--ink-soft);
    background: var(--panel);
    color: var(--ink-soft);
    border-radius: 669px;
    font-size: 7px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

/* Window-slide sentinels - the loading-placeholder rows at the
   top and bottom of the rendered 2000-row slice. IntersectionObserver
   fires when one scrolls into view; htmx-morph swaps the grid with
   the next/prev window. Visual is a soft accent-soft strip + spinner
   so the user sees the load happen rather than wonder why scroll
   suddenly snapped to a new page. */
.window-sentinel-cell {
    text-align: center;
    padding: 0.55rem 0.85rem;
    color: var(--ink-soft);
    background: var(--accent-soft);
    font-size: 9px;
    font-style: italic;
}
.window-sentinel-spinner {
    display: inline-block;
    width: 7px;
    height: 7px;
    margin-right: 0.4rem;
    border: 1px solid var(--accent);
    border-top-color: transparent;
    border-radius: 50%;
    animation: spinner-spin 0.8s linear infinite;
    vertical-align: -1px;
}
@media (prefers-reduced-motion: reduce) {
    .window-sentinel-spinner { animation: none; }
}

/* Scroll budget subtracts --activity-strip-h so the sticky <tfoot>
   never slides under the fixed activity strip at the foot of the
   viewport. .top-movers variants reserve extra space when the
   strip is rendered above the grid.
   Horizontal scroll: when the table's natural content width
   exceeds the container (narrow Trust laptops, many columns,
   long unit names), the scroll-wrapper grows a horizontal
   scrollbar instead of crushing columns below their min-width.
   Sticky thead + tfoot still work because position:sticky is
   per-axis. */
.balance-grid-scroll {
    /* overflow-x: scroll (not auto) so the horizontal scrollbar is
       always visible at the bottom of the grid, making it obvious
       that wide grids continue past the right edge. The design-brief
       "every grid scrolls horizontally" rule plus user-reported
       confusion when the bar appears only on overflow. */
    overflow-x: scroll;
    overflow-y: auto;
    /* Pin the scroll wrapper to its parent's width so a wide table
       inside it triggers overflow-x. Without these, the wrapper
       grows to fit the table's natural width (.balance-grid's
       min-width: max-content) and overflow-x never fires - so wide
       grids silently lose columns off the right edge.
       Codified in docs/design-brief.md "every grid scrolls
       horizontally" rule. */
    max-width: 100%;
    width: 100%;
    min-width: 0;
    /* Tightened: was 340 px deduction which left ~70 px of empty
       space between the grid's last row and the bottom-fixed
       activity strip. 270 px matches the actual chrome above the
       grid (tab row + toolbar + status bar + chips strip).
       Using `height` (not `max-height`) so the wrapper always
       extends to the available vertical space - if the table
       is short, the wrapper is still tall and the horizontal
       scrollbar reliably sits at the foot of the viewport
       instead of floating somewhere mid-page. */
    height: calc(100vh - 181px - var(--activity-strip-h) - 16px);
    min-height: 134px;
    /* Pale-blue scrollbars matched between Firefox (scrollbar-*) and
       WebKit (::-webkit-scrollbar-* below). The track has its own
       pale fill so the scroll lane is visible at the bottom of the
       grid even when there's no thumb (content fits in viewport). */
    scrollbar-color: color-mix(in srgb, var(--accent) 55%, var(--panel)) var(--accent-soft);
    scrollbar-width: auto;
    scrollbar-gutter: stable;
    /* No bottom margin - the wrapper's scrollbar lane rests flush
       on top of the fixed .activity-panel below it. The activity
       strip's upward box-shadow has been removed so it no longer
       paints over the scrollbar; a single 1 px border-top on the
       strip is the visual separator. */
}
.balance-grid-scroll::-webkit-scrollbar {
    /* Forced visible by !important - some browser configurations
       (Edge "Show scrollbars only when scrolling" / Windows
       "auto-hide" setting) collapse the standard 14 px scrollbar.
       18 px + the deeper colour pair makes the lane obvious. */
    width: 18px !important;
    height: 18px !important;
    background: var(--accent-soft) !important;
}
.balance-grid-scroll::-webkit-scrollbar-track {
    background: var(--accent-soft) !important;
    border-top: 1px solid var(--accent) !important;
    box-shadow: inset 0 1px 0 var(--rule);
}
.balance-grid-scroll::-webkit-scrollbar-thumb {
    background: var(--accent) !important;
    border-radius: 6px;
    border: 2px solid var(--accent-soft);
    min-width: 40px;
    min-height: 40px;
}
.balance-grid-scroll::-webkit-scrollbar-thumb:hover {
    background: var(--accent);
}
.balance-grid-scroll::-webkit-scrollbar-corner {
    background: var(--accent-soft);
}

/* Cached AI commentary rendered inline on the Staff grid - means
   Generate-all-Notes skips the row entirely. The ↻ regen icon sits
   alongside Hide Notes in the actions row so a fresh and a cached
   cell read identically; only the small ↻ marks the cached path. */
.commentary-regen {
    flex: 0 0 auto;
    background: transparent;
    border: 1px solid var(--rule);
    color: var(--ink-soft);
    width: 18px;
    height: 18px;
    border-radius: 50%;
    cursor: pointer;
    font-size: 11px;
    line-height: 1;
    transition: background 140ms ease, color 140ms ease, border-color 140ms ease;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.commentary-regen:hover {
    background: var(--accent-soft);
    color: var(--accent);
    border-color: var(--accent);
}

/* Stop button that interrupts a Generate-all run. Red tint so it's
   visibly distinct from the green-ish Generate / Hide buttons in
   the same pill group. Hidden until a Generate-all is in flight. */
.mode-toggle-button-stop {
    color: var(--rag-red, #b91c1c);
}
.mode-toggle-button-stop:hover {
    background: var(--rag-red-soft-bg, #fdecea);
    color: var(--rag-red, #8a1f1f);
}
.mode-toggle-button-stop[hidden] { display: none; }

/* Per-unit pattern badges. Same visual recipe as .wg-badge so a
   row reads as a small horizontal strip of glyphs after the unit
   name (shield, coin, moon). Two flags surface a unit's 90-day
   operational signature:
   - .unit-flag-payenh   (coin) : >60% of worked shifts in unsocial hours
   - .unit-flag-leaveonly (moon) : >70% of rows are NERG (sickness / leave)
   No background or border by default; the icon stroke colour
   carries the meaning. Hover gets a soft pastel wash so the
   button affordance stays obvious without competing with the
   row text. */
.unit-flag {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    margin-left: 0.3rem;
    padding: 0;
    background: transparent;
    border: 0;
    border-radius: 50%;
    vertical-align: -3px;
    line-height: 1;
    cursor: pointer;
    font-family: inherit;
    transition: background-color 140ms ease, filter 140ms ease;
}
.unit-flag .icon {
    width: 12px;
    height: 12px;
    stroke-width: 2;
}
.unit-flag:hover { filter: brightness(0.9); }

/* Toolbar dropdown for the two unit-pattern filters
   (pay-enhancement, leave-only). <details> + <summary> for the
   native disclosure; popover positioned just below the trigger. */
.patterns-dropdown {
    position: relative;
    display: inline-block;
}
.patterns-trigger {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    height: 31px;
    padding: 0 0.85rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    background: var(--panel);
    color: var(--ink-soft);
    cursor: pointer;
    font: inherit;
    font-size: 10px;
    list-style: none;
    transition: background 140ms ease, border-color 140ms ease, color 140ms ease;
}
.patterns-trigger::-webkit-details-marker { display: none; }
.patterns-trigger:hover {
    background: var(--accent-soft);
    border-color: var(--accent);
    color: var(--ink);
}
.patterns-dropdown.is-active .patterns-trigger {
    background: var(--accent-soft);
    border-color: var(--accent);
    color: var(--ink);
    font-weight: 600;
}
.patterns-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--accent);
    display: none;
}
.patterns-dropdown.is-active .patterns-dot {
    display: inline-block;
}
.patterns-popover {
    position: absolute;
    top: calc(100% + 4px);
    right: 0;
    z-index: 100;
    background: var(--panel);
    border: 1px solid var(--accent);
    border-radius: 4px;
    box-shadow: 0 6px 18px rgba(16, 42, 67, 0.15);
    padding: 0.75rem 0.9rem;
    width: 280px;
}
.patterns-section { margin: 0 0 0.7rem 0; }
.patterns-section:last-child { margin-bottom: 0; }
.patterns-section-label {
    font-weight: 600;
    font-size: 11px;
    color: var(--ink);
    margin-bottom: 0.15rem;
}
.patterns-section-hint {
    color: var(--ink-soft);
    font-weight: 400;
    font-size: 9px;
}
.patterns-section-sub {
    font-size: 9px;
    color: var(--ink-soft);
    margin-bottom: 0.35rem;
    line-height: 1.3;
}
.patterns-radio-row {
    display: flex;
    gap: 0.5rem;
    flex-wrap: wrap;
    font-size: 10px;
}
.patterns-radio-row label {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    cursor: pointer;
    color: var(--ink);
}
.patterns-radio-row input[type="radio"] {
    accent-color: var(--accent);
    cursor: pointer;
}
/* Amber coin = pay-enhancement. Reuses --rag-amber for stroke
   so the colour story stays inside the variance palette. */
.unit-flag-payenh { color: var(--rag-amber); }
.unit-flag-payenh:hover { background: #fef3c7; }

/* Violet moon = leave-only. No matching palette token (NERG /
   away isn't an R/A/G state) so the hex pair stays inline. */
.unit-flag-leaveonly { color: #6d28d9; }
.unit-flag-leaveonly:hover { background: #ede9fe; }

/* Small leading glyph on each Patterns dropdown section so the
   user sees the same icon in the popover that they see on the
   row. Inline-flex + tiny inline-block size mirrors .wg-badge so
   the icons don't disturb the section-label baseline. */
.patterns-section-glyph {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 14px;
    height: 14px;
    margin-right: 0.25rem;
    vertical-align: -2px;
}
.patterns-section-glyph .icon {
    width: 12px;
    height: 12px;
    stroke-width: 2;
}
.patterns-section-glyph-payenh { color: var(--rag-amber); }
.patterns-section-glyph-leaveonly { color: #6d28d9; }
.patterns-section-glyph-wg { color: var(--rag-green-strong); }
.patterns-section-glyph-signoff { color: var(--accent); }
.top-movers ~ .balance-grid-container .balance-grid-scroll {
    max-height: calc(100vh - 208px - var(--activity-strip-h));
}
.top-movers[open] ~ .balance-grid-container .balance-grid-scroll {
    max-height: calc(100vh - 288px - var(--activity-strip-h));
}

/* Transient class applied to a .balance-grid while barnaclesFitColumns
   is measuring natural column widths. Releases per-cell width caps
   (max-width, overflow:hidden, ellipsis, wrap) so each th's
   getBoundingClientRect width reflects the widest single-line cell.
   The class is removed before the next frame - it is never the
   long-lived steady-state look of the grid. */
.balance-grid.grid-measuring th,
.balance-grid.grid-measuring td {
    max-width: none !important;
    min-width: 0 !important;
    width: auto !important;
    overflow: visible !important;
    text-overflow: clip !important;
    white-space: nowrap !important;
}

/* Grids inside modals + detail panels don't have the rich status-bar
   the main grid tabs carry, so the Fit-columns affordance gets a tiny
   leftmost toolbar of its own. .grid-block is display:contents so the
   data-grid-key wrapper has no layout effect; .grid-mini-toolbar is a
   one-row flex strip that sits directly above the grid container. */
.grid-block { display: contents; }
.grid-mini-toolbar {
    display: flex;
    align-items: center;
    margin: 0.4rem 0 0.3rem;
}

.balance-grid {
    /* width: max-content + min-width: 100% is the more reliable
       formulation for "fill the wrapper when content is narrow;
       overflow + scroll when content is wide". The reverse
       (width: 100% + min-width: max-content) didn't engage on
       every grid - max-content as a min-width is not honoured
       consistently on tables in older Chromium versions. */
    width: max-content;
    min-width: 100%;
    border-collapse: collapse;
    /* Bumped body font so Ward Managers on small / low-res screens
       can scan rows without leaning in. Headers + sticky-totals
       inherit unless they override below. */
    font-size: 11px;
}

.balance-grid thead th {
    position: sticky;
    top: 0;
    background: var(--panel);
    border-bottom: 1px solid var(--rule);
    text-align: left;
    /* Padding lives on the <th> itself, not on the inner sort-link
       button, so every header cell (sortable or not, button or
       plain text) shares the same chrome. */
    padding: 0.2rem 0.85rem;
    color: var(--ink);
    font-weight: 700;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    z-index: 1;
}
/* Every visible child of a header inherits the bold-uppercase
   treatment so static labels (Trend, Notes), button-wrapped
   sort-links, and bare text-node children render identically. */
.balance-grid thead th,
.balance-grid thead th * {
    font-weight: 700;
}

/* Drag handle on the right edge of each grid header cell. The
   shaded full-height strip is the grab target; the thin centred
   line is the visual indicator the user aims for. Hover highlights
   the line, indicating it's interactive. */
.col-resizer {
    position: absolute;
    top: 0;
    right: 0;
    width: 5px;
    height: 100%;
    cursor: col-resize;
    user-select: none;
    -webkit-user-select: none;
    touch-action: none;
    background: linear-gradient(
        to right,
        transparent 0%,
        transparent 42%,
        var(--rule) 42%,
        var(--rule) 58%,
        transparent 58%);
    opacity: 0.55;
    transition: opacity 120ms ease, background 120ms ease;
    z-index: 3;
}
.col-resizer:hover,
.col-resizer.is-dragging {
    opacity: 1;
    background: linear-gradient(
        to right,
        rgba(111, 149, 187, 0.10) 0%,
        rgba(111, 149, 187, 0.10) 35%,
        var(--accent) 35%,
        var(--accent) 65%,
        rgba(111, 149, 187, 0.10) 65%,
        rgba(111, 149, 187, 0.10) 100%);
}
body.is-col-resizing,
body.is-col-resizing * { cursor: col-resize !important; user-select: none !important; }

.balance-grid thead th .sort-link,
.balance-grid thead th > button.sort-link {
    display: block;
    width: 100%;
    box-sizing: border-box;
    margin: 0;
    /* No padding here - the <th> provides it. */
    padding: 0;
    background: transparent;
    border: none;
    font-family: inherit;
    font-size: inherit;
    /* Explicit weight: some browsers don't inherit through <button>
       reliably, and non-button sort-link variants (sort-link-static
       on the Trend / Notes headers) lose the th's weight otherwise. */
    font-weight: 700;
    font-style: inherit;
    line-height: inherit;
    color: inherit;
    text-transform: inherit;
    letter-spacing: inherit;
    text-align: inherit;
    white-space: nowrap;
}

.balance-grid thead th .sort-link,
.balance-grid thead th > button.sort-link {
    cursor: pointer;
}

/* Column-header hover/focus: a SEMI-TRANSPARENT darken overlay on
   the WHOLE <th> via :has(), not just the inner button. The family
   tint (light-blue identity, white metric) stays visible underneath -
   the eye sees "this column got darker" instead of "every column
   went blue". Catches both the modern .col-header-trigger button
   (Staff/Units/Divisions) and the older .sort-link button (Leave
   grids). The :has() form fixes the narrow-column gutter: hovering
   Contract or Roster Hrs (47-54 px wide, of which ~27 px is th
   padding) now darkens the full cell, not a tiny strip in the middle.
   Codified in design-brief.md "Hover darkens the existing background,
   never replaces it" rule. */
.balance-grid thead th:has(.col-header-trigger:hover),
.balance-grid thead th:has(.col-header-trigger:focus-visible),
.balance-grid thead th:has(.sort-link:hover),
.balance-grid thead th:has(.sort-link:focus-visible) {
    background-image: linear-gradient(rgba(0, 0, 0, 0.10), rgba(0, 0, 0, 0.10));
}

.balance-grid thead th.num .sort-link { text-align: right; }

.balance-grid thead th.num { text-align: right; }

/* Sort arrow: a single ▴ glyph in a span, rotated by class. Always
   rendered (so the sortable affordance is visible on every column)
   but at reduced opacity until the column is active. With htmx-
   morph the span persists across grid swaps, so a click that flips
   asc -> desc smoothly transitions the existing arrow's transform
   instead of swapping in a fresh DOM node. */
.sort-arrow {
    display: inline-block;
    margin-left: 0.4rem;
    font-size: 16px;
    font-weight: 700;
    line-height: 1;
    /* Hidden until the column is the active sort. Keeps inactive
       headers clean and ensures the active arrow stands out. */
    opacity: 0;
    transform-origin: 50% 55%;
    transition: transform 140ms ease, opacity 140ms ease;
}
.sort-arrow.is-active { opacity: 1; }
.sort-arrow-asc.is-active  { transform: rotate(0deg); }
.sort-arrow-desc.is-active { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) {
    .sort-arrow { transition: none; }
}

.balance-grid tbody tr {
    border-bottom: 1px solid var(--rule);
    /* content-visibility lets the browser skip layout / paint /
       style for rows that aren't in (or near) the viewport. Big
       win when scrolling a grid with hundreds or thousands of
       rows - typical 5-10x scroll-FPS improvement on the Staff
       tab. contain-intrinsic-size gives the layout engine a
       cheap height estimate for off-screen rows so the scrollbar
       still tracks correctly. */
    content-visibility: auto;
    contain-intrinsic-size: 0 24px;
}
.balance-grid tbody tr:last-child { border-bottom: none; }
.balance-grid tbody tr:hover { background: var(--accent-soft); }

.balance-grid td {
    padding: 0.2rem 0.85rem;
    color: var(--ink);
    font-size: 11px;
    line-height: 1.2;
}

/* Right-align + tabular figures on every numeric cell. The monospace
   family is reserved for body and footer cells - column headers
   inherit the standard sans-serif so 'CONTRACTED HRS' reads as the
   same family as 'STAFF', 'UNIT', etc. */
.balance-grid .num {
    text-align: right;
    font-variant-numeric: tabular-nums;
}
.balance-grid tbody .num,
.balance-grid tfoot .num {
    font-family: var(--mono);
    font-size: 11px;
}

.balance-grid .balance-red   { color: var(--rag-red); font-weight: 600; }
.balance-grid .balance-amber { color: var(--rag-amber); font-weight: 600; }
.balance-grid .balance-green { color: var(--rag-green); font-weight: 600; }

/* Totals row pinned to the bottom of the scroll container.

   Sticky lives on the <td> rather than the <tr> because border-
   collapse tables don't honour sticky on rows in WebKit / older
   Chromium, so the row would scroll out of view on long tables.
   Cells share a solid background + a top border so the row reads
   as one strip even though the sticky is per-cell.

   The wrapping <tfoot> separately needs a higher z-index than the
   sticky <thead> (which has z-index: 1), otherwise body rows can
   paint OVER the totals during fast scroll - exactly the "totals
   missing" symptom. */
.balance-grid tfoot {
    position: relative;
    z-index: 3;
}
.balance-grid tfoot tr.totals-row td {
    position: sticky;
    /* Flush with the bottom of the scrollport (just above the
       horizontal scrollbar lane). The previous `bottom: 4px` left
       a 4 px lane between the totals row and the scrollbar where
       data rows were briefly visible while scrolling. */
    bottom: 0;
    background-color: var(--panel);
    background-clip: padding-box;
    padding: 0.3rem 0.85rem;
    font-weight: 600;
    color: var(--ink);
    border-top: 1px solid var(--rule);
    box-shadow: 0 -1px 0 var(--rule);
    z-index: 3;
}

/* ── Mode toggle pill (Net Hours / Annual Leave) ─────────────────── */

/* Sits between the tab strip and the toolbar. Visually a pill switch
   with two equal halves; the active half is filled in with the panel
   surface and bolded. Distinct from the tab bar above (which switches
   what data the grid is showing) - this toggle switches the lens the
   grid is using to look at it. */

/* ── Health card (tiles inside the site-header) ────────────────── */

/* Three small tiles sit in the centre of the very top header bar -
   .site-header is flex with space-between, so the tiles take the
   middle slot between the brand on the left and the Trust selector
   / account menu on the right. HTMX-refreshes on balances-refresh
   so the counts track the toolbar filters. */
.site-header .health-card {
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
    flex: 0 1 auto;
}

.health-tile {
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
    padding: 0.3rem 0.7rem;
    background: var(--panel);
    border: 1px solid var(--rule);
    border-radius: 4px;
    color: var(--ink);
    text-decoration: none;
    line-height: 1.1;
    transition: border-color 140ms ease, background 140ms ease;
}
.health-tile:hover { background: var(--accent-soft); border-color: var(--accent); }
.health-tile:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
/* Single-select toggle: the tile whose focus is currently applied
   to the grid carries this class. Filled accent tint so it reads as
   "on" against the idle neighbours; clicking it clears the focus
   (no chip in Filtering by, the tile itself is the undo). */
.health-tile.is-active {
    background: var(--accent-soft);
    border-color: var(--accent);
    box-shadow: inset 0 0 0 1px var(--accent);
}

.health-tile .icon-sm { color: var(--ink-soft); flex: 0 0 auto; }

.health-tile-body {
    display: inline-flex;
    flex-direction: column;
    align-items: flex-start;
    line-height: 1.1;
}

.health-tile-number {
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--ink);
}
.health-tile-number-alert { color: var(--rag-red-strong); }

.health-tile-label {
    font-size: 7px;
    color: var(--ink-soft);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

/* Action tile (Top movers) shares the chrome with the counter tiles
   but has no number to display - just a prominent label. Match the
   counter font-size so the row's vertical centring lines up with
   the other three. */
.health-tile-label-prominent {
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--ink);
    text-transform: none;
    letter-spacing: 0;
}
.health-tile-action .health-tile-body {
    justify-content: center;
}

/* Skeleton placeholder while the real tiles load. Same chrome
   (border, radius, padding height) so the layout doesn't shift
   when the real partial swaps in. */
.health-tile-skeleton {
    width: 95px;
    height: 25px;
    background: linear-gradient(90deg, var(--rule) 0%, var(--panel) 50%, var(--rule) 100%);
    background-size: 200% 100%;
    animation: health-skeleton-shimmer 1200ms ease-in-out infinite;
    border: 1px solid var(--rule);
    border-radius: 4px;
}
@keyframes health-skeleton-shimmer {
    0%   { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}
@media (prefers-reduced-motion: reduce) {
    .health-tile-skeleton { animation: none; }
}
.health-card-loading { gap: 0.5rem; display: inline-flex; }

/* Inline ? button - same visual weight as any other SVG icon inline
   beside a control. No border, no chip background; just the SVG with
   a subtle ink-soft colour that shifts to the accent on hover. The
   <button> wrapper exists for keyboard accessibility but is invisible
   chrome. */
/* Help icons match the modal-corner Close-X chrome: 36 x 36 round
   button with a subtle hover lift. Big enough to read as a real
   click target on the toolbar - the previous small inline icon
   looked like ornament. */
.help-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    margin-left: 0.25rem;
    border: 1px solid transparent;
    background: transparent;
    color: var(--ink-soft);
    cursor: pointer;
    vertical-align: middle;
    border-radius: 50%;
    transition: background 140ms ease, color 140ms ease, border-color 140ms ease;
}
.help-icon .icon { width: 16px; height: 16px; }
.help-icon:hover { background: var(--accent-soft); color: var(--ink); }
.help-icon:focus-visible { outline: none; color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.help-icon-page { margin-left: auto; }

/* Help modal - readable prose card sized for short explanations.
   Reuses the shared .staff-modal chrome so the close button + scroll
   layout match every other detail dialog. */
.help-modal { width: min(429px, 92vw); }
.help-modal .staff-modal-scroll { padding: 1.25rem 1.5rem 1.5rem 1.5rem; }
.help-modal h2 { margin: 0 0 0.5rem 0; font-size: 1.25rem; color: var(--ink); }
.help-modal h3 { margin: 1rem 0 0.35rem 0; font-size: 1rem; color: var(--ink); }
.help-modal p { margin: 0 0 0.65rem 0; color: var(--ink); line-height: 1.5; font-size: 9px; }
.help-modal ul { margin: 0 0 0.65rem 1.2rem; padding: 0; color: var(--ink); font-size: 9px; line-height: 1.5; }
.help-modal li { margin-bottom: 0.2rem; }
.help-modal strong { color: var(--ink); font-weight: 600; }
.help-modal em { font-style: italic; color: var(--ink-soft); }

/* Tab strip + mode toggle share one row so the toggle doesn't claim
   its own near-empty strip above the toolbar. The tab row is flex with
   the toggle pushed to the right edge. */
.tab-row {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 0.75rem;
    padding-right: 0.25rem;
}

.mode-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0;
    margin: 0 0 0.25rem 0;
    padding: 2px;
    background: var(--accent-soft);
    border: 1px solid var(--rule);
    border-radius: 669px;
    width: fit-content;
    position: relative;
    z-index: 1;
}

.mode-toggle-button {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.4rem 1.1rem;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 669px;
    color: var(--ink-soft);
    cursor: pointer;
    font: inherit;
    font-size: 9px;
    font-weight: 500;
    line-height: 1.1;
    transition: background 140ms ease, color 140ms ease, font-weight 140ms ease;
    min-height: 21px;
}

.mode-toggle-button:hover {
    color: var(--ink);
    background: var(--panel);
}

.mode-toggle-button.active {
    background: var(--accent);
    border-color: var(--accent);
    color: #fff;
    font-weight: 700;
}
.mode-toggle-button.active:hover {
    background: var(--accent-hover);
    border-color: var(--accent-hover);
    color: #fff;
}

.mode-toggle-button .icon-sm {
    width: 9px;
    height: 9px;
}

/* ── Leave-mode grid columns ─────────────────────────────────────── */

/* Header family tints across every grid - pastel-blue / white
   alternation by group of similar columns, matching the KPI
   category-row scheme. The eye reads "this is the same family of
   columns" without four different colours competing.
   Pure CSS via the classes the views already emit, so no per-view
   edit is required to apply the banding. */

/* Default header background - white (the "off" band of the
   alternation). Identity + trail families pick up the pastel blue
   "on" band below. */
.balance-grid thead th { background: var(--panel); }

/* Identity columns (name, unit, division, trust) - pastel blue
   group, anchoring the left of every grid. */
.balance-grid thead th.staff-col,
.balance-grid thead th.unit-col,
.balance-grid thead th.division-col,
.balance-grid thead th.trust-cell {
    background: color-mix(in srgb, var(--accent) 10%, var(--panel));
}

/* Trail / sparkline / checkpoint - pastel blue group, second
   "on" band further along the row. */
.balance-grid thead th.trail-col,
.balance-grid thead th.checkpoint-col {
    background: color-mix(in srgb, var(--accent) 10%, var(--panel));
}

/* Drill / row-action / lozenge columns stay on the white "off"
   band by default - no override needed. */
/* Numeric metric columns also inherit the white band - the right-
   aligned tabular-numeric font already separates them visually. */

/* Leave-grid header families - blue / white alternation only (no
   yellow, no grey). Explicit override wins over the .num default
   above. Total + Remaining keep a slightly deeper blue (22%) so the
   totals still read as the emphasis columns; bold weight is already
   on every thead th and doesn't need re-asserting. */
.leave-grid thead th.leave-ent {
    background: color-mix(in srgb, var(--accent) 10%, var(--panel));
}
.leave-grid thead th.leave-total {
    background: color-mix(in srgb, var(--accent) 22%, var(--panel));
}
.leave-grid thead th.leave-use {
    background: var(--panel);
}
.leave-grid thead th.leave-remaining {
    background: color-mix(in srgb, var(--accent) 22%, var(--panel));
}
.leave-grid thead th.leave-pace {
    background: var(--panel);
}
.leave-grid tbody td.leave-total,
.leave-grid tbody td.leave-remaining { font-weight: 600; }

/* KPI grid keeps its own category-row tints (kpi-cat-*) - the
   two-row header already does the family grouping there. */

/* Leave-grid family rules moved earlier in this file alongside the
   global .balance-grid thead family tints - kept together so the
   precedence story is in one place. */

/* Leave-grid uses the same row spacing as the Staff grid - the
   previous narrower-padding override was added before horizontal-
   scrolling on all grids landed. Now wide Leave grids scroll
   sideways instead of squashing the columns. */

/* Top mover cards in leave mode get the leave-yellow accent on the
   delta number, matching the Used column band. */
.top-mover-card.top-mover-leave .top-mover-delta {
    color: var(--ink);
}
.top-mover-card.top-mover-leave {
    border-left: 2px solid var(--duty-annual);
    /* Square aspect for the "Most leave yet to take" cards - same
       width as height so they tile cleanly. The default top-mover
       wide rectangle suits a single big delta on Staff / Units; the
       Leave variant carries four lines of equally-weighted content
       and reads better as a square. */
    flex: 0 0 101px;
    width: 101px;
    min-width: 101px;
    max-width: 101px;
    height: 101px;
    min-height: 101px;
    justify-content: space-between;
}

/* ── Flying Top Movers strip (site-header) ─────────────────────── */

/* Single "Top movers" button in the flying header (next to the
   health tiles). Click sorts the Staff grid by |variance| DESC. */
.top-movers-flying-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.35rem 0.85rem;
    border: 1px solid var(--rule);
    background: var(--panel);
    color: var(--ink);
    text-decoration: none;
    font-size: 9px;
    line-height: 1.2;
    transition: background 140ms ease, border-color 140ms ease;
    flex: 0 0 auto;
}
.top-movers-flying-btn:hover { background: var(--accent-soft); border-color: var(--accent); }
.top-movers-flying-btn:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.top-movers-flying-btn .icon-sm { color: var(--ink-soft); }

/* ── Button shape: lozenges across the product ──────────────────
   Single override block at the end of the file so every button-
   style control reads as a pill regardless of where the original
   rule set its radius. Cards, modals, inputs, tabs and the table
   chrome keep their existing slight rounding - those aren't buttons
   so the lozenge convention shouldn't claim them. */
.primary-button,
.secondary-button,
.ask-button,
.link-action,
.generate-notes,
.generate-all-btn,
.signoff-button,
.status-bar-export,
.login-submit,
.unit-picker-action,
.balance-band-clear,
.nl-example,
.lozenge-button,
.health-tile,
.top-mover-card,
.top-movers-flying-btn,
.mode-toggle-button {
    border-radius: 669px;
}

/* "Clear" affordance inside grid status-banner sentences (e.g.
   "Balance outside +/- 11.5 h tolerance. [clear]"). Promoted to a
   lozenge button - small but unambiguous - rather than a flat inline
   link which read as part of the sentence. */
.balance-band-clear {
    display: inline-flex;
    align-items: center;
    padding: 0.15rem 0.7rem;
    margin-left: 0.4rem;
    border: 1px solid var(--rule);
    background: var(--panel);
    color: var(--accent);
    font-size: 9px;
    text-decoration: none;
    line-height: 1.4;
    transition: background 140ms ease, border-color 140ms ease;
}
.balance-band-clear:hover {
    background: var(--accent-soft);
    border-color: var(--accent);
    text-decoration: none;
}
.balance-band-clear:focus-visible {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}

/* The Units toolbar chip stays square - it sits in the same row as the
   square Name search and Grade picker, so a lozenge there would clash. */
.unit-picker-trigger { border-radius: 3px; }

/* Tooltips inside the balance grid: barnaclesUpgradeTooltips() already
   skips title-promotion under .balance-grid for performance on long
   grids, so .barnacles-tip never fires there. Any per-cell hint that
   stays as native title= remains a quiet browser tooltip. */

/* ── Sign-off modal: per-staff review grid ─────────────────────────
   The pre-sign-off modal now lists every staff member that's about
   to be captured, with a per-row textarea for an optional manual
   note + a Generate button that asks Oceansblue AI for a suggestion
   the manager can copy into the textarea. */
.signoff-staff-review {
    margin: 1rem 0 1.2rem;
}
.signoff-staff-review-head {
    margin-bottom: 0.5rem;
}
.signoff-staff-review-title {
    font-size: 11px;
    margin: 0 0 0.2rem;
    color: var(--ink);
}
.signoff-staff-review-sub {
    font-size: 10px;
    color: var(--ink-soft);
    margin: 0;
}
.signoff-staff-review-scroll {
    max-height: 241px;
}
.signoff-staff-review-grid td {
    vertical-align: top;
}
.signoff-staff-note-cell {
    min-width: 188px;
}
.signoff-staff-note-input {
    width: 100%;
    min-height: 29px;
    font: inherit;
    font-size: 10px;
    padding: 0.35rem 0.5rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    background: var(--bg);
    resize: vertical;
}
.signoff-staff-note-actions {
    margin-top: 0.3rem;
    display: flex;
    gap: 0.3rem;
}
/* Empty placeholder (no Generate yet, or Dismiss clicked) takes no
   vertical space - keeps the row compact until the manager asks
   for an AI suggestion. */
.signoff-staff-note-suggestion[data-empty] {
    display: none;
}
.signoff-staff-note-suggestion {
    margin-top: 0.4rem;
    padding: 0.5rem 0.6rem;
    border: 1px dashed var(--accent);
    border-radius: 3px;
    background: color-mix(in srgb, var(--accent) 6%, transparent);
}
.signoff-staff-note-suggestion-label {
    font-size: 9px;
    color: var(--accent);
    font-weight: 600;
    margin-bottom: 0.25rem;
}
.signoff-staff-note-suggestion-text {
    font-size: 10px;
    color: var(--ink);
    white-space: pre-wrap;
    margin-bottom: 0.4rem;
}
.signoff-staff-note-suggestion-actions {
    display: flex;
    gap: 0.3rem;
}

/* Sign-off snapshot - per-staff Manual note column.
   Only rendered when at least one staff member in the snapshot
   carries a note (the head/cell are gated by anyManualNotes in
   the view). Wraps long notes onto multiple lines so the column
   doesn't blow the table width on a verbose entry. */
.signoff-snapshot-note {
    white-space: pre-wrap;
    max-width: 241px;
    font-size: 9px;
    color: var(--ink);
}

/* Approval-role badges on the Admin Users grid. Distinct from the
   strong admin-flags (red) - these are quieter pastel blue pills
   for HRD / CNO / FD assignments. */
.admin-flag-role {
    background: color-mix(in srgb, var(--accent) 12%, #fff);
    border: 1px solid color-mix(in srgb, var(--accent) 35%, #fff);
    color: var(--accent);
}

/* ── Approvals stub page (phase 1) ─────────────────────────────────
   Roadmap landing page for the Approvals tab. Replaced by the
   triangulation grids + workflow in subsequent phases. */
.approvals-stub {
    padding: 1.5rem 0;
    max-width: 616px;
}
.approvals-stub-header h1 {
    margin: 0 0 0.4rem;
    font-size: 15px;
    color: var(--ink);
}
.approvals-stub-sub {
    font-size: 10px;
    color: var(--ink-soft);
    margin: 0 0 1.4rem;
}
.approvals-stub-roles, .approvals-stub-roadmap {
    background: #fff;
    border: 1px solid var(--rule);
    border-radius: 4px;
    padding: 1rem 1.2rem;
    margin: 0 0 1rem;
}
.approvals-stub-roles h2, .approvals-stub-roadmap h2 {
    font-size: 11px;
    margin: 0 0 0.7rem;
    color: var(--ink);
}
.approvals-role-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin: 0 0 0.6rem;
}
.approvals-role-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    height: 24px;
    padding: 0 0.85rem;
    font-size: 10px;
    border: 1px solid var(--rule);
    border-radius: 3px;
    background: #fff;
    color: var(--ink-soft);
}
.approvals-role-chip.is-active {
    background: color-mix(in srgb, var(--accent) 12%, #fff);
    border-color: var(--accent);
    color: var(--accent);
    font-weight: 600;
}
.approvals-stub-meta {
    font-size: 9px;
    color: var(--ink-soft);
    margin: 0.4rem 0 0;
}
.approvals-roadmap-list {
    margin: 0;
    padding-left: 1.2rem;
}
.approvals-roadmap-list li {
    font-size: 10px;
    color: var(--ink);
    margin: 0 0 0.6rem;
}
.approvals-roadmap-list li:last-child { margin-bottom: 0; }

/* ── Finance Data admin page (Approvals phase 2) ───────────────────
   Header navigation + per-Trust upload form + headline-summary
   cards + sample-rows preview table. */
.admin-nav {
    display: flex;
    gap: 0.4rem;
    margin: 0.8rem 0 0;
}
.admin-nav .secondary-button.is-current {
    background: var(--accent-soft);
    border-color: var(--accent);
    color: var(--accent);
    font-weight: 600;
}
/* Banner semantic variants - sit on top of the .admin-banner base.
   Same shape (padding / radius / margin / font), different colour
   family per state. The base info-blue style is defined alongside
   .admin-banner higher up in this file. */
.admin-banner-success {
    background: color-mix(in srgb, var(--rag-green) 12%, #fff);
    border-color: color-mix(in srgb, var(--rag-green) 35%, #fff);
    color: var(--ink);
}
.admin-banner-error {
    background: var(--rag-red-soft-bg);
    border-color: var(--rag-red-soft-border);
    color: var(--rag-red);
}
/* ── Shared stat-card tiles ───────────────────────────────────────
   Headline tiles used on Finance Data (.finance-stat) and the
   Approvals headline (.approvals-headline-stat). Same shape, same
   typography - the two view-specific class names share these
   rules so the visual idiom is identical across the app. New
   places that want a "label + big number + meta" tile should
   adopt these class names too.
*/
.finance-summary,
.approvals-headline-stats {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    margin: 0 0 1.2rem;
}
.finance-stat,
.approvals-headline-stat {
    flex: 1;
    min-width: 107px;
    background: #fff;
    border: 1px solid var(--rule);
    border-radius: 4px;
    padding: 0.7rem 0.9rem;
}
.finance-stat-label,
.approvals-headline-label {
    display: block;
    font-size: 9px;
    color: var(--ink-soft);
    text-transform: uppercase;
    letter-spacing: 0.4px;
    margin-bottom: 0.25rem;
}
.finance-stat-value,
.approvals-headline-value {
    display: block;
    font-size: 15px;
    font-weight: 600;
    color: var(--ink);
}
.finance-stat-meta,
.approvals-headline-meta {
    display: block;
    font-size: 9px;
    color: var(--ink-soft);
    margin-top: 0.2rem;
}
.finance-stat-meta-line {
    display: block;
    font-size: 9px;
    color: var(--ink-soft);
    margin-top: 0.2rem;
}
.finance-upload {
    background: #fff;
    border: 1px solid var(--rule);
    border-radius: 4px;
    padding: 1rem 1.2rem;
    margin: 0 0 1.2rem;
}
.finance-upload h2 {
    margin: 0 0 0.5rem;
    font-size: 11px;
}
.finance-upload-hint {
    font-size: 10px;
    color: var(--ink-soft);
    margin: 0 0 0.8rem;
}
.finance-format-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 9px;
    margin: 0 0 1rem;
}
.finance-format-table th,
.finance-format-table td {
    text-align: left;
    border: 1px solid var(--rule);
    padding: 0.4rem 0.6rem;
}
.finance-format-table th {
    background: var(--accent-soft);
    color: var(--ink);
}
.finance-upload-form {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: center;
}
.finance-upload-form input[type=file] {
    flex: 1;
    min-width: 161px;
    height: 24px;
    font-size: 10px;
}
.finance-preview {
    background: #fff;
    border: 1px solid var(--rule);
    border-radius: 4px;
    padding: 1rem 1.2rem;
    margin: 0 0 1.2rem;
}
.finance-preview h2 {
    margin: 0 0 0.6rem;
    font-size: 11px;
}

/* ── Approvals tab phase 3 - triangulation + alert grids ───────────
   The Finance / Estab / Demand / Actual / In Post triangulation
   landing page. Headline stat strip + three alert cards (Estab /
   Demand / Actual exceed Finance) + the full detail grid. */
.approvals-header {
    padding: 1.2rem 0 0.6rem;
}
.approvals-header h1 {
    margin: 0 0 0.3rem;
    font-size: 15px;
}
/* Banner-card family - shared shape across Approvals and Sign-off
   contexts. Same padding / radius / margin / font-size; the
   colour-modifier classes (-error red, -no-alerts green, default
   neutral white) override only background + border + color. Same
   shape rules as .admin-banner above but with a card-style 4px
   radius and rule-grey border by default. */
.approvals-empty,
.approvals-error,
.approvals-no-changes {
    padding: 0.9rem 1.1rem;
    margin: 1rem 0;
    border: 1px solid var(--rule);
    border-radius: 4px;
    background: #fff;
    color: var(--ink);
    font-size: 10px;
}
.approvals-error {
    background: var(--rag-red-soft-bg);
    border-color: var(--rag-red-soft-border);
    color: var(--rag-red);
}
.approvals-no-changes {
    color: var(--ink-soft);
}
.approvals-summary {
    margin: 0 0 1rem;
}
.approvals-summary-meta {
    font-size: 9px;
    color: var(--ink-soft);
    margin: 0 0 0.6rem;
}
/* .approvals-headline-stats / -stat / -label / -value / -meta
   defined alongside .finance-stat higher up - same shape, shared
   rules. Block kept empty so the comment-anchor stays for future
   editors. */
.approvals-alerts {
    margin: 1.5rem 0;
}
.approvals-alerts h2,
.approvals-detail h2,
.approvals-stub-roadmap h2 {
    font-size: 11px;
    margin: 0 0 0.6rem;
    color: var(--ink);
}
.approvals-alert-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(228px, 1fr));
    gap: 0.8rem;
}
.approvals-alert-card {
    background: #fff;
    border: 1px solid var(--rule);
    border-radius: 4px;
    padding: 0.9rem 1rem;
}
.approvals-alert-header {
    position: relative;
    margin: 0 0 0.6rem;
    padding-right: 4rem;
}
.approvals-alert-header h3 {
    margin: 0 0 0.25rem;
    font-size: 10px;
    color: var(--rag-red);
}
.approvals-alert-header p {
    margin: 0;
    font-size: 9px;
    color: var(--ink-soft);
}
.approvals-alert-count {
    position: absolute;
    top: 0;
    right: 0;
    background: var(--rag-red-soft-bg);
    border: 1px solid var(--rag-red-soft-border);
    color: var(--rag-red);
    font-size: 8px;
    padding: 0.15rem 0.5rem;
    border-radius: 669px;
}
.approvals-alert-empty {
    margin: 0;
    padding: 0.8rem 0;
    font-size: 9px;
    color: var(--rag-green);
}
.approvals-no-alerts {
    margin: 0;
    padding: 0.8rem 1rem;
    font-size: 10px;
    color: var(--rag-green);
    background: color-mix(in srgb, var(--rag-green) 10%, #fff);
    border: 1px solid color-mix(in srgb, var(--rag-green) 30%, #fff);
    border-radius: 4px;
}
/* Approvals alert table inherits the .balance-grid 17 px body font
   and row padding - no override needed for visual consistency. */
.approvals-detail {
    margin: 1.5rem 0;
}
.approvals-detail-hint {
    font-size: 9px;
    color: var(--ink-soft);
    margin: 0 0 0.5rem;
}
/* Approvals detail grid uses the standard .balance-grid row spacing
   and 17 px body font - no font-size / padding overrides. The grid
   scrolls horizontally if the column count exceeds the viewport. */
.approvals-detail-grid th.num {
    text-align: right;
}
.approvals-group-head {
    text-align: center !important;
    font-weight: 600;
    border-bottom: 1px solid var(--rule);
}
/* Group-band heads alternate light-blue / white - the only two
   tints permitted on grid headers across the app. Five groups means
   blue / white / blue / white / blue. Status colour (RAG) belongs in
   the body cells, never on the header chrome. */
.approvals-group-fin    { background: color-mix(in srgb, var(--accent) 10%, #fff); }
.approvals-group-estab  { background: #fff; }
.approvals-group-demand { background: color-mix(in srgb, var(--accent) 10%, #fff); }
.approvals-group-actual { background: #fff; }
.approvals-group-inpost { background: color-mix(in srgb, var(--accent) 10%, #fff); }
.approvals-row-sub td {
    background: color-mix(in srgb, var(--accent) 8%, #fff);
    font-weight: 600;
}
.approvals-row-grand td {
    background: color-mix(in srgb, var(--ink) 8%, #fff);
    font-weight: 700;
    color: var(--ink);
}

/* ── Approvals tab phase 4 - budget-change workflow ────────────────
   Three-way HRD/CNO/FD approval cards + a Propose form + the
   recently-decided list. */
.approvals-changes {
    margin: 1.5rem 0;
}
.approvals-changes h2 {
    font-size: 11px;
    margin: 0 0 0.6rem;
    color: var(--ink);
}
.approvals-changes-sub {
    font-size: 9px;
    color: var(--ink-soft);
    margin: 0 0 0.8rem;
}
/* .approvals-no-changes - shape merged into the banner-card family
   block higher up (alongside .approvals-empty / .approvals-error).
   Anchor comment kept for searchability. */
.budget-change-card {
    background: #fff;
    border: 1px solid var(--rule);
    border-radius: 4px;
    padding: 0.9rem 1rem;
    margin: 0 0 0.7rem;
}
.budget-change-card.budget-change-status-approved {
    border-left: 3px solid var(--rag-green);
}
.budget-change-card.budget-change-status-rejected {
    border-left: 3px solid var(--rag-red);
    opacity: 0.92;
}
.budget-change-card.budget-change-status-pending {
    border-left: 3px solid var(--accent);
}
.budget-change-head {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    margin: 0 0 0.5rem;
}
.budget-change-title {
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
    font-size: 10px;
}
.budget-change-band {
    background: var(--accent-soft);
    color: var(--accent);
    padding: 0.1rem 0.4rem;
    border-radius: 3px;
    font-size: 9px;
}
.budget-change-type {
    font-size: 9px;
    color: var(--ink-soft);
}
.budget-change-status {
    font-size: 9px;
    font-weight: 600;
    padding: 0.2rem 0.55rem;
    border-radius: 669px;
    text-transform: uppercase;
    letter-spacing: 0.4px;
}
.budget-change-status-approved.budget-change-status { background: color-mix(in srgb, var(--rag-green) 14%, #fff); color: var(--rag-green); }
.budget-change-status-rejected.budget-change-status { background: var(--rag-red-soft-bg); color: var(--rag-red); }
.budget-change-status-pending.budget-change-status  { background: var(--accent-soft); color: var(--accent); }
.budget-change-body {
    margin: 0 0 0.6rem;
}
.budget-change-wte {
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
    margin: 0 0 0.3rem;
    font-size: 9px;
}
.budget-change-wte-label { color: var(--ink-soft); font-size: 9px; }
.budget-change-wte-value { font-weight: 600; font-size: 10px; }
.budget-change-wte-arrow { color: var(--ink-soft); }
.budget-change-wte-delta { font-size: 9px; margin-left: 0.4rem; }
.budget-change-rationale {
    margin: 0 0 0.3rem;
    font-size: 9px;
    color: var(--ink);
    background: color-mix(in srgb, var(--accent) 6%, #fff);
    padding: 0.45rem 0.6rem;
    border-radius: 3px;
}
.budget-change-meta {
    font-size: 9px;
    color: var(--ink-soft);
    margin: 0;
}
.budget-change-meta .sep { margin: 0 0.4rem; }
.budget-change-slots {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0.4rem;
    margin: 0.5rem 0 0;
}
.budget-change-slot {
    border: 1px solid var(--rule);
    border-radius: 3px;
    padding: 0.5rem 0.6rem;
    background: #fafafa;
}
.budget-change-slot-role {
    font-size: 8px;
    color: var(--ink-soft);
    text-transform: uppercase;
    letter-spacing: 0.4px;
    margin: 0 0 0.3rem;
    font-weight: 600;
}
.budget-change-slot-voted {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    font-size: 9px;
    color: var(--ink-soft);
}
.budget-change-slot-voted strong { font-size: 9px; }
.budget-change-slot-approved strong { color: var(--rag-green); }
.budget-change-slot-rejected strong { color: var(--rag-red); }
.budget-change-slot-voted em { font-style: italic; color: var(--ink); }
.budget-change-slot-waiting {
    font-size: 9px;
    color: var(--ink-soft);
    font-style: italic;
}
.budget-change-vote-form {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
}
/* Vote slot input + buttons. Approve / Reject are the most
   consequential interactions on the page so they hit the
   primary-button floor: 36 px tall, 15 px font, full-width
   inside the slot. */
.budget-change-vote-note {
    width: 100%;
    height: 24px;
    font-size: 10px;
    padding: 0.35rem 0.5rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
}
.budget-change-vote-buttons {
    display: flex;
    gap: 0.3rem;
}
.budget-change-vote-buttons button { flex: 1; }
.budget-change-vote-reject {
    background: var(--rag-red-soft-bg);
    color: var(--rag-red);
    border-color: var(--rag-red-soft-border);
}
.approvals-propose-details {
    background: #fff;
    border: 1px solid var(--rule);
    border-radius: 4px;
    padding: 0.5rem 1rem;
    margin: 1rem 0;
}
.approvals-propose-details summary {
    cursor: pointer;
    font-weight: 600;
    font-size: 10px;
    padding: 0.3rem 0;
}
.approvals-propose-form {
    margin: 0.6rem 0 0;
}
.approvals-propose-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin: 0 0 0.5rem;
}
.approvals-propose-row label {
    flex: 1;
    min-width: 87px;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    font-size: 9px;
    color: var(--ink-soft);
}
.approvals-propose-row input, .approvals-propose-row select {
    height: 24px;
    padding: 0 0.4rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    font-size: 10px;
    background: #fff;
}
.approvals-propose-rationale {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    font-size: 9px;
    color: var(--ink-soft);
    margin: 0 0 0.5rem;
}
.approvals-propose-rationale textarea {
    font: inherit;
    font-size: 10px;
    padding: 0.4rem 0.5rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    background: #fff;
    resize: vertical;
    min-height: 47px;
}
.approvals-propose-actions {
    display: flex;
    justify-content: flex-end;
}
.approvals-decided-details {
    background: #fff;
    border: 1px solid var(--rule);
    border-radius: 4px;
    padding: 0.5rem 1rem;
    margin: 1rem 0;
}
.approvals-decided-details summary {
    cursor: pointer;
    font-weight: 600;
    font-size: 10px;
    padding: 0.3rem 0;
}
.approvals-changes-decided {
    margin: 0.5rem 0 0;
}

/* Inline Propose buttons on the Approvals detail grid. Lives in
   a narrow column at the right of each row; subtotal rows propose
   against the whole unit, detail rows pre-fill the band too. */
.approvals-propose-col {
    width: 1%;
    white-space: nowrap;
    text-align: right;
    padding: 3px 5px;
}
/* Inline Propose buttons on the Approvals detail grid. Sit inside
   per-row narrow column; markup now uses .secondary-button which
   already meets the Ward-Manager 36 px / 15 px floor - no per-button
   overrides needed. */

/* Post-hoc note editor on the Sign-off snapshot modal. CanEditSignedNotes
   gates the edit pencil; every edit lands in the audit table with
   previous + new text + editor + IP. The pencil button itself is
   a .row-icon-btn (markup) for the standard 36 x 36 hit target; the
   inline form uses .primary-button / .secondary-button Save / Cancel
   to match every other modal action row. */
.signoff-snapshot-note-view {
    display: flex;
    gap: 0.4rem;
    align-items: flex-start;
}
.signoff-snapshot-note-text {
    flex: 1;
    white-space: pre-wrap;
}
.signoff-snapshot-note-form {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}
.signoff-snapshot-note-input {
    width: 100%;
    min-height: 34px;
    font: inherit;
    font-size: 10px;
    padding: 0.4rem 0.5rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    resize: vertical;
}
.signoff-snapshot-note-actions {
    display: flex;
    gap: 0.4rem;
}
.signoff-snapshot-note-meta {
    margin-top: 0.3rem;
    font-size: 9px;
    color: var(--ink-soft);
    font-style: italic;
}
