/*
 * ============================================================================
 *  Safety Alert Timeline — visual system  ("calm clinical precision")
 * ----------------------------------------------------------------------------
 *  A refined incident timeline for life-safety alerts: severity-driven accents,
 *  layered depth, tabular/mono numerics, and purposeful motion. Self-contained
 *  design tokens with full light/dark parity. Global CSS (no isolation), linked
 *  from App.razor. Every selector here is referenced by SafetyAlertTimelineView /
 *  SafetyAlertTimelineHeader / SafetyAlertResults.
 * ============================================================================
 */

/* ---- Design tokens (scoped to the timeline surfaces + the result detail) - */
.sa-timeline,
.sa-timeline-dialog,
.sa-result,
.sa-alert-aside {
    --sa-surface: #ffffff;
    --sa-surface-sunken: #f5f7fa;
    --sa-surface-raised: #ffffff;
    --sa-hairline: rgba(15, 23, 42, 0.09);
    --sa-hairline-strong: rgba(15, 23, 42, 0.16);
    --sa-text: #1d2530;
    --sa-text-dim: rgba(29, 37, 48, 0.60);
    --sa-text-faint: rgba(29, 37, 48, 0.42);

    --sa-accent: #5b5bf0;          /* indigo signature, ties to the Timeline button */
    --sa-accent-strong: #4338ca;
    --sa-accent-soft: rgba(91, 91, 240, 0.12);
    --sa-accent-rgb: 91, 91, 240;

    --sa-red: #e5484d;        --sa-red-rgb: 229, 72, 77;
    --sa-red-soft: rgba(229, 72, 77, 0.13);
    --sa-amber: #d97706;      --sa-amber-rgb: 217, 119, 6;
    --sa-amber-soft: rgba(217, 119, 6, 0.14);
    --sa-green: #15916b;      --sa-green-rgb: 21, 145, 107;
    --sa-green-soft: rgba(21, 145, 107, 0.14);
    --sa-blue: #2f72e0;       --sa-blue-rgb: 47, 114, 224;
    --sa-blue-soft: rgba(47, 114, 224, 0.13);
    --sa-slate: #64748b;      --sa-slate-rgb: 100, 116, 139;
    --sa-slate-soft: rgba(100, 116, 139, 0.14);

    /* WHY the alert fired (#135). Deliberately outside the state palette: severity owns
       red/amber/green, category owns violet/blue/slate, the zone owns the rails — a seventh
       tint in that family would read as an eighth state. This is the app's own deep brand
       indigo, SOLID, because the cause is the one thing on the row that is a sentence rather
       than a label, and it is what a responder actually acts on. One filled plate among soft
       chips is unmistakable at a glance and survives greyscale. */
    --sa-why-bg: #052666;
    --sa-why-ink: #ffffff;
    --sa-why-ink-dim: rgba(255, 255, 255, 0.62);

    /* CATEGORY: the card's fill answers "what kind of alert is this?" while the rail keeps
       answering "where did it happen?" — two channels, two facts, and they never argue.

       Violet and blue, because they must be told apart at 5% alpha across a scrolling list.
       Safety was indigo and sat too close to the location blue to survive that; violet buys
       real separation. Neither is in the severity ramp, so a tinted card can never read as
       urgent or as resolved — which a green or amber wash would, instantly and wrongly.

       Unclassified and Ops get NO wash. An alert nobody classified is the one case where a
       colour would be a claim, and the absence of one is the honest answer.

       LOCATION IS CYAN, NOT BLUE (#253 item 2, decision D). It was #2f72e0 — the exact hex of
       --sa-blue, which the rail uses for the Blue zone. Two different axes answering two
       different questions in one colour: blue alone could not tell you whether you were reading
       "where it happened" or "what kind it was". Cyan keeps the cool, informational register the
       category deserves while sharing a hex with nothing. It is not confusable with --sa-green
       either, because the zone only ever appears as a 3px rail, never as a fill. */
    --sa-cat-safety: #7c3aed;
    --sa-cat-safety-wash: rgba(124, 58, 237, 0.10);
    --sa-cat-safety-wash-strong: rgba(124, 58, 237, 0.18);
    --sa-cat-safety-soft: rgba(124, 58, 237, 0.13);
    --sa-cat-location: #0e7490;
    --sa-cat-location-wash: rgba(14, 116, 144, 0.10);
    --sa-cat-location-wash-strong: rgba(14, 116, 144, 0.18);
    --sa-cat-location-soft: rgba(14, 116, 144, 0.13);

    /* CRITICAL is the only FILLED severity on a card (#253 item 2, decision B). Critical and
       High both resolved to --sa-red and were told apart by their text alone, so the colour
       channel carried three states for four severities and the top of the scale had no headroom.
       A deeper plate rather than --sa-red itself: white on #e5484d is 3.4:1, which fails at the
       tick's size. This clears 5.9:1. */
    --sa-sev-crit-bg: #c2262b;
    --sa-sev-crit-ink: #ffffff;

    --sa-radius: 14px;
    --sa-radius-md: 11px;
    --sa-radius-sm: 8px;

    --sa-shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.06), 0 1px 1px rgba(15, 23, 42, 0.04);
    --sa-shadow-md: 0 4px 14px -4px rgba(15, 23, 42, 0.14), 0 2px 5px -2px rgba(15, 23, 42, 0.08);
    --sa-shadow-lg: 0 24px 56px -16px rgba(15, 23, 42, 0.30), 0 8px 22px -10px rgba(15, 23, 42, 0.18);

    --sa-mono: ui-monospace, "SF Mono", "Cascadia Code", "JetBrains Mono", "Roboto Mono", Consolas, monospace;

    /* Shared type system with the Counseling Journey (loaded in App.razor). */
    --sa-serif: "Fraunces", "Iowan Old Style", Georgia, "Times New Roman", serif;
    --sa-sans: "Hanken Grotesk", system-ui, -apple-system, "Segoe UI", sans-serif;

    --sa-ease: cubic-bezier(0.22, 0.68, 0.18, 1);
    --sa-ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);

    font-family: var(--sa-sans);
}

.sa-timeline-dialog .e-dlg-content,
.sa-timeline { font-family: var(--sa-sans); }

[data-theme="dark"] .sa-timeline,
[data-theme="dark"] .sa-timeline-dialog,
[data-theme="dark"] .sa-result,
[data-theme="dark"] .sa-alert-aside {
    --sa-surface: #1b1f27;
    --sa-surface-sunken: #15181f;
    --sa-surface-raised: #222732;
    --sa-hairline: rgba(255, 255, 255, 0.10);
    --sa-hairline-strong: rgba(255, 255, 255, 0.18);
    --sa-text: #e7eaf1;
    --sa-text-dim: rgba(231, 234, 241, 0.62);
    --sa-text-faint: rgba(231, 234, 241, 0.40);

    --sa-accent: #8b8cf8;
    --sa-accent-strong: #a5b4fc;
    --sa-accent-soft: rgba(139, 140, 248, 0.18);
    --sa-accent-rgb: 139, 140, 248;

    --sa-red: #ff6369;        --sa-red-rgb: 255, 99, 105;
    --sa-red-soft: rgba(255, 99, 105, 0.18);
    --sa-amber: #f0a23b;      --sa-amber-rgb: 240, 162, 59;
    --sa-amber-soft: rgba(240, 162, 59, 0.18);
    --sa-green: #4cc79a;      --sa-green-rgb: 76, 199, 154;
    --sa-green-soft: rgba(76, 199, 154, 0.18);
    --sa-blue: #6ba5f5;       --sa-blue-rgb: 107, 165, 245;
    --sa-blue-soft: rgba(107, 165, 245, 0.18);
    --sa-slate: #94a3b8;      --sa-slate-rgb: 148, 163, 184;
    --sa-slate-soft: rgba(148, 163, 184, 0.18);

    /* The why plate stays FILLED on dark — the point is that it is the one solid element on a
       row of soft chips, and softening it here would surrender that in the theme where the
       distinction is hardest to make. Lifted off the near-black surface enough to read as a
       plate rather than a hole. */
    --sa-why-bg: #3f51b5;
    --sa-why-ink: #f6f8ff;
    --sa-why-ink-dim: rgba(246, 248, 255, 0.66);

    /* The category wash needs roughly twice the alpha here. A 5% tint that separates two cards
       on white disappears entirely against #15181f — the same wash is not the same signal, and
       matching the numbers rather than the effect is how a dark theme ends up flat. */
    --sa-cat-safety: #a78bfa;
    --sa-cat-safety-wash: rgba(167, 139, 250, 0.17);
    --sa-cat-safety-wash-strong: rgba(167, 139, 250, 0.28);
    --sa-cat-safety-soft: rgba(167, 139, 250, 0.18);
    --sa-cat-location: #4fd1e0;
    --sa-cat-location-wash: rgba(79, 209, 224, 0.17);
    --sa-cat-location-wash-strong: rgba(79, 209, 224, 0.28);
    --sa-cat-location-soft: rgba(79, 209, 224, 0.18);

    /* Inverted on dark: the plate is the light red and the ink is near-black, because a deeper
       red than --sa-red would sink into the surface rather than lift off it. */
    --sa-sev-crit-bg: #ff6369;
    --sa-sev-crit-ink: #1a0d0e;

    --sa-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
    --sa-shadow-md: 0 6px 18px -6px rgba(0, 0, 0, 0.6);
    --sa-shadow-lg: 0 28px 64px -18px rgba(0, 0, 0, 0.7), 0 8px 24px -12px rgba(0, 0, 0, 0.5);
}

/* ---- Per-card "Timeline" launch button ---------------------------------- */
/* Teal/cyan — a distinct "history / timeline" hue, set apart from the green Load
   button beside it and the app's indigo accent (was indigo, which blended in). */
.sa-timeline-btn {
    background: linear-gradient(135deg, #18b9c7, #0e93b0);
    border: none;
    color: #fff;
    font-weight: 600;
    letter-spacing: 0.01em;
    box-shadow: 0 1px 2px rgba(14, 147, 176, 0.38), inset 0 1px 0 rgba(255, 255, 255, 0.25);
    transition: transform 0.16s var(--sa-ease, ease), box-shadow 0.16s ease, filter 0.16s ease;
}

.sa-timeline-btn:hover,
.sa-timeline-btn:focus {
    color: #fff;
    transform: translateY(-1px);
    box-shadow: 0 6px 16px -4px rgba(14, 147, 176, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.28);
    filter: saturate(1.05);
}

.sa-timeline-btn:active {
    transform: translateY(0);
    filter: brightness(0.96);
}

/* ---- Dialog shell -------------------------------------------------------- */
.sa-timeline-dialog.e-dialog {
    border-radius: var(--sa-radius);
    border: 1px solid var(--sa-hairline);
    box-shadow: var(--sa-shadow-lg);
    overflow: hidden;
    background: var(--sa-surface);
}

/* The header wrapper (.e-dlg-header) picks up a stray cream #fff3cd (Bootstrap
   warning-subtle) — override both it and the content band to the dialog surface.
   Syncfusion also draws a bottom border on BOTH wrappers (the inner one in that
   same cream), stacking two hairlines under the title (#214): zero both here,
   the ONE divider is re-added on the outer wrapper just below. */
.sa-timeline-dialog .e-dlg-header,
.sa-timeline-dialog .e-dlg-header-content {
    background: var(--sa-surface-raised) !important;
    border-bottom: 0 !important;
}

.sa-timeline-dialog .e-dlg-header {
    padding: 0 !important;
}

.sa-timeline-dialog .e-dlg-header-content {
    border-bottom: 1px solid var(--sa-hairline) !important;
    padding: 1rem 1.15rem !important;
    position: relative;
}

/* Brand accent bar across the top of the header (mirrors the break-glass dialog). */
.sa-timeline-dialog .e-dlg-header-content::before {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    height: 3px;
    background: linear-gradient(90deg, var(--sa-accent), var(--sa-accent-strong));
}

.sa-timeline-dialog .e-dlg-content {
    background: var(--sa-surface);
    color: var(--sa-text);
    max-height: 72vh;
    overflow-y: auto;
    /* No top padding: the sticky deck must sit flush at the scroll-port top. A padding
       band here (Syncfusion sets 12px, hence !important) left a gap the timeline spine
       showed through above the #51 header when scrolled (#116 follow-up). Top spacing
       lives inside the deck's .sa-tlh instead. */
    padding: 0 1.15rem 1.15rem !important;
    scrollbar-width: thin;
}

.sa-timeline-dialog .e-dlg-content::-webkit-scrollbar { width: 9px; }
.sa-timeline-dialog .e-dlg-content::-webkit-scrollbar-thumb {
    background: var(--sa-hairline-strong);
    border-radius: 999px;
    border: 2px solid transparent;
    background-clip: padding-box;
}

/* Break-glass-style header: badge + eyebrow/title stack (was a flat icon + text
   line that rendered as a jarring pale bar on the dark dialog). */
.sa-tl-dialog-head {
    display: flex;
    align-items: center;
    gap: 0.7rem;
}

.sa-tl-dialog-badge {
    flex: none;
    width: 2.5rem;
    height: 2.5rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--sa-radius-md);
    background: var(--sa-accent-soft);
    color: var(--sa-accent);
    font-size: 1.15rem;
    box-shadow: inset 0 0 0 1px rgba(var(--sa-accent-rgb), 0.28);
}

.sa-tl-dialog-heading {
    display: flex;
    flex-direction: column;
    min-width: 0;
    line-height: 1.15;
}

.sa-tl-dialog-eyebrow {
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--sa-accent);
}

.sa-tl-dialog-title {
    font-family: var(--sa-serif);
    font-optical-sizing: auto;
    font-weight: 600;
    font-size: 1.2rem;
    letter-spacing: -0.01em;
    color: var(--sa-text);
}

/* ======================================================================== */
/*  Sticky summary header                                                    */
/* ======================================================================== */
/* Sticky control deck: the summary header + the filter / expand-collapse toolbar travel
   together, pinned above the scrolling spine. Opaque so nothing shows through, with a
   shadow + hairline so it reads as a distinct layer and never overlaps the first card. */
.sa-tl-deck {
    position: sticky;
    top: 0;
    /* Above the timeline spine/nodes (z-index 1) so steps scroll cleanly *under* the
       deck. Was a frosted (backdrop-blur) panel, which made the scrolling spine nodes
       faintly show through the header — removed for a crisp, fully-opaque surface. */
    z-index: 10;
    /* No negative top margin now that the scroll-port has no top padding: the deck
       sticks flush at the very top so nothing scrolls into view above it. */
    margin: 0 -1.15rem 0.9rem;
    background: var(--sa-surface-raised);
    border-bottom: 1px solid var(--sa-hairline-strong);
    box-shadow: 0 10px 18px -10px rgba(0, 0, 0, 0.5);
}

.sa-tlh {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding: 0.7rem 0.85rem 0.7rem 1rem;
    background: linear-gradient(180deg, var(--sa-surface-raised), var(--sa-surface));
}

/* Severity accent rail down the left of the header. */
.sa-tlh::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 4px;
    border-radius: 0 4px 4px 0;
    background: var(--sa-slate);
}

.sa-tlh--open::before {
    background: linear-gradient(180deg, var(--sa-red), rgba(var(--sa-red-rgb), 0.55));
    box-shadow: 0 0 12px -2px var(--sa-red);
}

.sa-tlh--resolved::before {
    background: linear-gradient(180deg, var(--sa-green), rgba(var(--sa-green-rgb), 0.55));
}

.sa-tlh-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.45rem 0.65rem;
}

/* Top area: two sections — info on the left, a divided metric panel (time taken + stats) on
   the right. Both stretch to the same height so the divider spans the full block. */
.sa-tlh-top {
    display: flex;
    align-items: stretch;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 0.7rem 1.25rem;
}

.sa-tlh-identity {
    display: flex;
    flex-direction: column;
    gap: 0.55rem;
    flex: 1 1 240px;
    min-width: 0;
}

.sa-tlh-metrics {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0.6rem;
    flex: 0 0 auto;
    width: 320px;
    max-width: 100%;
    padding-left: 1.15rem;
    border-left: 1px solid var(--sa-hairline-strong);
}

.sa-tlh-id {
    font-family: var(--sa-serif);
    font-optical-sizing: auto;
    font-weight: 600;
    font-size: 1.2rem;
    letter-spacing: -0.01em;
    color: var(--sa-text);
}

.sa-tlh-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.32rem;
    font-size: 0.74rem;
    font-weight: 600;
    letter-spacing: 0.01em;
    padding: 0.16rem 0.55rem;
    border-radius: 999px;
    background: var(--sa-slate-soft);
    color: var(--sa-text);
    border: 1px solid transparent;
}

.sa-tlh-crit-high, .sa-tlh-crit-critical { background: var(--sa-red-soft); color: var(--sa-red); }
.sa-tlh-crit-medium { background: var(--sa-amber-soft); color: var(--sa-amber); }
.sa-tlh-crit-low { background: var(--sa-green-soft); color: var(--sa-green); }
.sa-tlh-zone-red { background: var(--sa-red-soft); color: var(--sa-red); }
.sa-tlh-zone-blue { background: var(--sa-blue-soft); color: var(--sa-blue); }
.sa-tlh-zone-green { background: var(--sa-green-soft); color: var(--sa-green); }

.sa-tlh-dur {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 0.12rem;
    line-height: 1;
    white-space: nowrap;
}

/* Duration rendered as a button (jump-to-beat): strip the native chrome so it reads as the
   same metric block, then hint interactivity on hover/focus. */
button.sa-tlh-dur {
    appearance: none;
    background: none;
    border: 0;
    padding: 0.1rem 0.25rem;
    margin: -0.1rem -0.25rem;
    border-radius: var(--sa-radius-sm, 6px);
    cursor: pointer;
    text-align: right;
    transition: background-color 0.15s ease, transform 0.15s ease;
}

button.sa-tlh-dur:hover { background: var(--sa-surface-2, rgba(0, 0, 0, 0.04)); }
button.sa-tlh-dur:hover .sa-tlh-dur-val { color: var(--sa-accent); }
button.sa-tlh-dur:focus-visible {
    outline: 2px solid var(--sa-accent);
    outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
    button.sa-tlh-dur { transition: none; }
}

/* Foot of the metric panel: the stats Less/More toggle alongside the host's Expand/Collapse all. */
.sa-tlh-metrics-foot {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: 0.35rem 0.6rem;
    margin-top: 0.1rem;
}

.sa-tlh-metrics-foot .sa-tl-toolbar-actions { display: flex; gap: 0.15rem; }

.sa-tlh-dur-kicker {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    font-size: 0.6rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--sa-text-faint);
}

.sa-tlh-dur-kicker i { color: var(--sa-accent); }

.sa-tlh-dur-val {
    font-family: var(--sa-serif);
    font-optical-sizing: auto;
    font-weight: 600;
    font-size: 1.5rem;
    letter-spacing: -0.01em;
    color: var(--sa-text);
}

.sa-tlh-meta {
    font-size: 0.8rem;
    color: var(--sa-text-dim);
    gap: 0.5rem 1.2rem;
}

.sa-tlh-meta i { color: var(--sa-text-faint); }

/* Location — a clear chip with a pinned-location glyph, not just faint inline text. */
.sa-tlh-loc {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    font-size: 0.84rem;
    font-weight: 600;
    color: var(--sa-text);
    padding: 0.3rem 0.65rem 0.3rem 0.55rem;
    border-radius: 999px;
    background: var(--sa-surface-sunken);
    border: 1px solid var(--sa-hairline);
}

.sa-tlh-loc i {
    font-size: 0.95rem;
    color: var(--sa-green);
}

/* Detection moment — its own full-width block: a distinct label pill on top, the editorial
   date plate spread across the row beneath it. */
.sa-tlh-detected {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.45rem;
    width: 100%;
    margin-top: 0.25rem;
}

.sa-tlh-detected-label {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.66rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.13em;
    color: var(--sa-accent);
    padding: 0.18rem 0.6rem;
    border-radius: 999px;
    background: var(--sa-accent-soft);
}

.sa-tlh-detected-label i { font-size: 0.8rem; }

/* The date plate fills the row, with the time pushed to the far right. */
.sa-tlh-detected .sa-date--plate {
    width: 100%;
    align-items: center;
    gap: 0.8rem;
    padding: 0.65rem 1rem;
}

/* Hero dateline — a touch larger than the in-timeline plates. */
.sa-tlh-detected .sa-date--plate .sa-date-day { font-size: 2.1rem; }
.sa-tlh-detected .sa-date--plate .sa-date-mon { font-size: 1.32rem; }
.sa-tlh-detected .sa-date--plate .sa-date-yr { font-size: 1.12rem; }

.sa-tlh-detected .sa-date--plate .sa-date-time {
    align-self: center;
    margin-left: auto;
    font-size: 1rem;
}

/* Derived incident stats strip — an even 2-column grid in the right metric panel, so every
   chip is the same width and the rows line up neatly beneath the duration. */
.sa-tlh-stats {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.4rem;
    width: 100%;
}

/* Collapsed strip hides the detail chips, leaving only the headline set + toggle. */
.sa-tlh-stats.is-collapsed .sa-tl-stat--more {
    display: none;
}

/* In the grid each chip fills its cell and aligns its content left. */
.sa-tlh-stats .sa-tl-stat { width: 100%; justify-content: flex-start; }

/* The toggle spans the full grid width and centres, so it never sits as a lonely half-row
   regardless of how many stat chips are present (odd or even). */
.sa-tlh-stats-toggle {
    grid-column: 1 / -1;
    justify-self: center;
}

.sa-tl-stat {
    display: inline-flex;
    align-items: center;
    gap: 0.32rem;
    padding: 0.18rem 0.55rem;
    border-radius: var(--sa-radius-sm);
    background: var(--sa-surface-sunken);
    border: 1px solid var(--sa-hairline);
    white-space: nowrap;
    transition: transform 0.16s ease, border-color 0.16s ease, background 0.16s ease;
}

.sa-tl-stat:hover {
    transform: translateY(-1px);
    border-color: var(--sa-hairline-strong, var(--sa-accent));
    background: var(--sa-surface);
}

/* Detail chips ease in when the strip expands. */
.sa-tlh-stats.is-expanded .sa-tl-stat--more {
    animation: sa-stat-in 0.22s ease both;
}

@keyframes sa-stat-in {
    from { opacity: 0; transform: translateY(-2px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* Toggle pill: reveals / hides the detail chips. */
.sa-tlh-stats-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.18rem 0.6rem;
    border-radius: 999px;
    background: transparent;
    border: 1px dashed var(--sa-hairline);
    color: var(--sa-text-dim);
    font-size: 0.68rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    cursor: pointer;
    transition: color 0.16s ease, border-color 0.16s ease, background 0.16s ease;
}

.sa-tlh-stats-toggle:hover {
    color: var(--sa-accent);
    border-color: var(--sa-accent);
    background: var(--sa-accent-soft);
}

.sa-tlh-stats-toggle:focus-visible {
    outline: 2px solid var(--sa-accent);
    outline-offset: 2px;
}

.sa-tlh-stats-toggle > i {
    font-size: 0.72rem;
    transition: transform 0.18s ease;
}

@media (prefers-reduced-motion: reduce) {
    .sa-tl-stat,
    .sa-tlh-stats.is-expanded .sa-tl-stat--more,
    .sa-tlh-stats-toggle > i { transition: none; animation: none; }
}

.sa-tl-stat > i {
    font-size: 0.78rem;
    color: var(--sa-accent);
    opacity: 0.95;
}

/* Per-stat icon colours, keyed off each chip's Bootstrap icon class, so the strip reads as a
   set of distinct metrics rather than a wall of one colour. Grouped by meaning:
   people = indigo/green, escalation = amber/red, sensing = blue, structure = slate. */
.sa-tl-stat > i.bi-envelope          { color: var(--sa-blue); }   /* time to notify */
.sa-tl-stat > i.bi-person-check      { color: var(--sa-green); }  /* time to attend */
.sa-tl-stat > i.bi-people            { color: var(--sa-accent); } /* staff notified */
.sa-tl-stat > i.bi-person-check-fill { color: var(--sa-green); }  /* staff attended */
.sa-tl-stat > i.bi-list-ol           { color: var(--sa-slate); }  /* total steps */
.sa-tl-stat > i.bi-person-badge      { color: var(--sa-blue); }   /* subjects */
.sa-tl-stat > i.bi-arrow-up-circle   { color: var(--sa-amber); }  /* peak escalation */
.sa-tl-stat > i.bi-exclamation-triangle { color: var(--sa-red); } /* escalations */
.sa-tl-stat > i.bi-activity          { color: var(--sa-red); }    /* anomaly readings */
.sa-tl-stat > i.bi-broadcast         { color: var(--sa-accent); } /* readers */
.sa-tl-stat > i.bi-geo-alt           { color: var(--sa-green); }  /* locations */

.sa-tl-stat-val {
    font-family: var(--sa-serif);
    font-optical-sizing: auto;
    font-weight: 600;
    font-size: 0.94rem;
    line-height: 1;
    color: var(--sa-text);
}

.sa-tl-stat-lbl {
    font-size: 0.62rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--sa-text-dim);
}

.sa-tlh-actions {
    gap: 0.4rem;
    /* Sits in the left identity column; push to the bottom so it fills the space beneath the
       info and lines up with the foot of the metric panel. */
    margin-top: auto;
    padding-top: 0.35rem;
}

.sa-tlh-actions .btn {
    border-radius: var(--sa-radius-sm);
    font-size: 0.78rem;
    font-weight: 600;
    transition: transform 0.14s var(--sa-ease), box-shadow 0.14s ease, background 0.14s ease;
}

.sa-tlh-actions .btn:hover:not(:disabled) {
    transform: translateY(-1px);
}

.sa-spin { display: inline-block; animation: sa-tl-spin 0.9s linear infinite; }
@keyframes sa-tl-spin { to { transform: rotate(360deg); } }

/* ======================================================================== */
/*  Interaction toolbar (filter chips + expand/collapse all)                 */
/* ======================================================================== */
.sa-tl-toolbar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem 0.75rem;
    justify-content: space-between;
    padding: 0.5rem 1rem 0.55rem;
    margin: 0;
    border-top: 1px solid var(--sa-hairline);
}

.sa-tl-filters { display: flex; flex-wrap: wrap; gap: 0.35rem; }

.sa-tl-filter {
    font-size: 0.73rem;
    font-weight: 600;
    letter-spacing: 0.01em;
    padding: 0.16rem 0.6rem;
    border-radius: 999px;
    border: 1px solid var(--sa-hairline-strong);
    background: transparent;
    color: var(--sa-text-dim);
    cursor: pointer;
    transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease, transform 0.12s var(--sa-ease);
}

.sa-tl-filter:hover { transform: translateY(-1px); color: var(--sa-text); }

.sa-tl-filter.is-active {
    background: var(--sa-accent-soft);
    border-color: rgba(var(--sa-accent-rgb), 0.45);
    color: var(--sa-accent);
}

.sa-tl-toolbar-actions { display: flex; gap: 0.15rem; }

.sa-tl-toolbar-actions .btn-link {
    font-size: 0.77rem;
    font-weight: 600;
    text-decoration: none;
    color: var(--sa-text-dim);
    padding: 0.12rem 0.45rem;
    border-radius: var(--sa-radius-sm);
    transition: color 0.14s ease, background 0.14s ease;
}

.sa-tl-toolbar-actions .btn-link:hover {
    color: var(--sa-accent);
    background: var(--sa-accent-soft);
}

/* ======================================================================== */
/*  The spine                                                                */
/* ======================================================================== */
.sa-timeline {
    list-style: none;
    margin: 0;
    padding: 0.35rem 0;
    position: relative;
}

/* Continuous spine with a soft inner highlight. */
.sa-timeline::before {
    content: "";
    position: absolute;
    top: 6px;
    bottom: 6px;
    left: 17px;
    width: 2px;
    border-radius: 2px;
    background: linear-gradient(to bottom,
        rgba(var(--sa-accent-rgb), 0.55),
        rgba(var(--sa-accent-rgb), 0.14));
}

/* ---- Alert-Status phases (main rail) ---- */
.sa-tl-phase {
    position: relative;
    padding: 0 0 0.4rem 2.7rem;
    animation: sa-tl-rise 0.5s var(--sa-ease) both;
}

.sa-tl-phase:nth-child(1) { animation-delay: 0.02s; }
.sa-tl-phase:nth-child(2) { animation-delay: 0.12s; }
.sa-tl-phase:nth-child(3) { animation-delay: 0.22s; }

.sa-tl-marker--phase {
    left: -1px;
    width: 36px;
    height: 36px;
    font-size: 0.95rem;
}

.sa-tl-phase-card { display: flex; flex-direction: column; align-items: flex-start; gap: 0.15rem; }

.sa-tl-phase-head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.3rem 0.85rem;
    padding: 0.3rem 0.2rem;
}

.sa-tl-phase-kicker {
    flex-basis: 100%;
    font-size: 0.6rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.16em;
    color: var(--sa-node-color);
    opacity: 0.9;
}

.sa-tl-phase-title {
    font-family: var(--sa-serif);
    font-optical-sizing: auto;
    font-weight: 600;
    font-size: 1.24rem;
    letter-spacing: -0.01em;
    color: var(--sa-text);
}

.sa-tl-phase-state {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    font-size: 0.74rem;
    font-weight: 600;
    color: var(--sa-text-dim);
    padding: 0.12rem 0.6rem;
    border-radius: 999px;
    background: var(--sa-node-soft);
}

.sa-tl-phase-state.is-live { color: var(--sa-amber); }

/* Indented tracking-step sub-rail beneath each phase. */
.sa-tl-substeps {
    list-style: none;
    margin: 0.35rem 0 0.6rem 1.55rem;
    padding: 0.1rem 0 0;
    position: relative;
}

.sa-tl-substeps::before {
    content: "";
    position: absolute;
    top: 2px;
    bottom: 8px;
    left: 16px;
    width: 2px;
    border-radius: 2px;
    background: repeating-linear-gradient(var(--sa-hairline-strong) 0 6px, transparent 6px 11px);
}

.sa-tl-substeps-empty {
    padding: 0.35rem 0 0.35rem 2.6rem;
    font-size: 0.8rem;
}

/* Spine colour shifts with criticality. */
.sa-timeline--crit-medium::before {
    background: linear-gradient(to bottom, rgba(var(--sa-amber-rgb), 0.70), rgba(var(--sa-amber-rgb), 0.14));
}
.sa-timeline--crit-high::before,
.sa-timeline--crit-critical::before {
    background: linear-gradient(to bottom, rgba(var(--sa-red-rgb), 0.72), rgba(var(--sa-red-rgb), 0.14));
}
.sa-timeline--crit-low::before {
    background: linear-gradient(to bottom, rgba(var(--sa-green-rgb), 0.60), rgba(var(--sa-green-rgb), 0.12));
}

.sa-tl-node {
    position: relative;
    padding: 0 0 0.95rem 2.6rem;
    min-height: 1.6rem;
    animation: sa-tl-rise 0.44s var(--sa-ease) both;
}

.sa-tl-node:last-child { padding-bottom: 0.15rem; }

/* Staggered entrance for the first several beats. */
.sa-tl-node:nth-child(1) { animation-delay: 0.02s; }
.sa-tl-node:nth-child(2) { animation-delay: 0.06s; }
.sa-tl-node:nth-child(3) { animation-delay: 0.10s; }
.sa-tl-node:nth-child(4) { animation-delay: 0.14s; }
.sa-tl-node:nth-child(5) { animation-delay: 0.18s; }
.sa-tl-node:nth-child(6) { animation-delay: 0.22s; }
.sa-tl-node:nth-child(7) { animation-delay: 0.26s; }
.sa-tl-node:nth-child(8) { animation-delay: 0.30s; }

@keyframes sa-tl-rise {
    from { opacity: 0; transform: translateY(9px); }
    to { opacity: 1; transform: none; }
}

/* Marker — ringed dot on the spine. */
.sa-tl-marker {
    position: absolute;
    left: 1px;
    top: 0.05rem;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--sa-surface);
    border: 2px solid var(--sa-node-color);
    color: var(--sa-node-color);
    font-size: 0.82rem;
    z-index: 1;
    box-shadow: 0 0 0 4px var(--sa-surface), 0 0 0 5px var(--sa-node-soft), 0 1px 4px rgba(15, 23, 42, 0.12);
    transition: transform 0.26s var(--sa-ease-spring), box-shadow 0.2s ease;
}

.sa-tl-node:hover .sa-tl-marker {
    transform: scale(1.1) rotate(-5deg);
    box-shadow: 0 0 0 4px var(--sa-surface), 0 0 0 7px var(--sa-node-soft), 0 5px 14px rgba(15, 23, 42, 0.2);
}

/* Card — left accent bar keyed to severity, expanding on hover (à la Counseling Journey). */
.sa-tl-card {
    position: relative;
    background: var(--sa-surface-raised);
    border: 1px solid var(--sa-hairline);
    border-radius: var(--sa-radius-md);
    padding: 0.65rem 0.9rem 0.65rem 1.05rem;
    box-shadow: var(--sa-shadow-sm);
    transition: transform 0.2s var(--sa-ease), box-shadow 0.2s ease, border-color 0.2s ease;
}

.sa-tl-card::before {
    content: "";
    position: absolute;
    left: 0;
    top: 12px;
    bottom: 12px;
    width: 3px;
    border-radius: 0 3px 3px 0;
    background: var(--sa-node-color);
    opacity: 0.85;
    transition: top 0.26s var(--sa-ease), bottom 0.26s var(--sa-ease);
}

.sa-tl-card:hover {
    transform: translateY(-2px);
    box-shadow: var(--sa-shadow-md);
    border-color: var(--sa-hairline-strong);
}

.sa-tl-card:hover::before {
    top: 6px;
    bottom: 6px;
}

.sa-tl-head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.sa-tl-head--toggle { cursor: pointer; user-select: none; }
.sa-tl-head--toggle:focus-visible {
    outline: 2px solid var(--sa-accent);
    outline-offset: 3px;
    border-radius: 6px;
}

.sa-tl-chevron {
    font-size: 0.72rem;
    color: var(--sa-text-faint);
    transition: transform 0.2s var(--sa-ease), color 0.15s ease;
}

.sa-tl-head--toggle:hover .sa-tl-chevron { color: var(--sa-accent); }
.sa-tl-head[aria-expanded="true"] .sa-tl-chevron { transform: rotate(90deg); color: var(--sa-accent); }

.sa-tl-title {
    font-family: var(--sa-serif);
    font-optical-sizing: auto;
    font-weight: 600;
    font-size: 1.02rem;
    letter-spacing: 0.005em;
    color: var(--sa-text);
}

.sa-tl-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.28rem;
    font-size: 0.71rem;
    font-weight: 600;
    padding: 0.1rem 0.48rem;
    border-radius: 999px;
    background: var(--sa-accent-soft);
    color: var(--sa-accent);
    white-space: nowrap;
}

.sa-tl-time {
    margin-left: auto;
    white-space: nowrap;
}

/* ---- Editorial date (shared with the Counseling Journey's serif plate) ---- */
.sa-date {
    display: inline-flex;
    align-items: baseline;
    gap: 0.4rem;
    font-family: var(--sa-sans);
}

.sa-date-day {
    font-family: var(--sa-serif);
    font-optical-sizing: auto;
    font-weight: 600;
    line-height: 1;
    color: var(--sa-text);
}

.sa-date-mon {
    font-family: var(--sa-serif);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--sa-accent);
}

.sa-date-yr {
    font-size: 0.72rem;
    font-weight: 600;
    color: var(--sa-text-faint);
}

.sa-date-time {
    display: inline-flex;
    align-items: center;
    gap: 0.2rem;
    font-weight: 600;
    color: var(--sa-text-dim);
}

.sa-date-time i { opacity: 0.7; }

/* Relative notation — "Now" / "Today" / "Yesterday" lead the date when the moment is recent. */
.sa-date-rel {
    font-family: var(--sa-serif);
    font-optical-sizing: auto;
    font-weight: 600;
    color: var(--sa-text);
}

/* Inline variant — the right-aligned date on each node head. */
.sa-date--inline { gap: 0.35rem; font-size: 0.78rem; }
.sa-date--inline .sa-date-day { font-size: 0.96rem; }
.sa-date--inline .sa-date-mon { font-size: 0.82rem; }
.sa-date--inline .sa-date-time { font-size: 0.74rem; margin-left: 0.1rem; }
.sa-date--inline .sa-date-rel { font-size: 0.9rem; }
.sa-date--plate .sa-date-rel { font-size: 1.5rem; line-height: 1; letter-spacing: -0.01em; }

/* Plate variant — a prominent editorial dateline reading "16 February 2026 · 19:08". */
.sa-date--plate {
    align-items: baseline;
    gap: 0.55rem;
    padding: 0.55rem 0.9rem;
    border: 1px solid var(--sa-hairline-strong);
    border-radius: var(--sa-radius-md);
    background: linear-gradient(180deg, var(--sa-surface-raised), var(--sa-surface-sunken));
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.6), 0 1px 2px rgba(15, 23, 42, 0.05);
}

[data-theme="dark"] .sa-date--plate {
    background: var(--sa-surface-sunken);
    box-shadow: none;
}

.sa-date--plate .sa-date-day {
    font-size: 1.85rem;
    letter-spacing: -0.02em;
}

/* Month + year now read inline ("February 2026") rather than stacked. */
.sa-date--plate .sa-date-rest {
    display: flex;
    flex-direction: row;
    align-items: baseline;
    gap: 0.4rem;
}

.sa-date--plate .sa-date-mon {
    font-size: 1.2rem;
    text-transform: none;
    letter-spacing: 0;
}

.sa-date--plate .sa-date-yr {
    font-size: 1.05rem;
    font-weight: 600;
    color: var(--sa-text-dim);
}

/* Time set off to the right with a hairline divider + mono numerals. */
.sa-date--plate .sa-date-time {
    align-self: center;
    gap: 0.3rem;
    margin-left: 0.15rem;
    padding-left: 0.65rem;
    font-family: var(--sa-mono);
    font-size: 0.92rem;
    border-left: 1px solid var(--sa-hairline-strong);
}

.sa-date--plate .sa-date-time i { color: var(--sa-accent); opacity: 0.85; }

.sa-tl-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem 1rem;
    margin-top: 0.35rem;
    font-size: 0.82rem;
    color: var(--sa-text-dim);
}

/* Reader + location context — shown on every beat (phase and step), forward-filled by the
   builder. Inherited values (carried from an earlier status) render faintly with a dashed edge. */
.sa-tl-context {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-top: 0.4rem;
}

.sa-tl-phase-card .sa-tl-context { margin-top: 0.1rem; }

.sa-tl-ctx {
    display: inline-flex;
    align-items: center;
    gap: 0.32rem;
    max-width: 100%;
    font-size: 0.74rem;
    font-weight: 600;
    padding: 0.16rem 0.5rem;
    border-radius: var(--sa-radius-sm);
    background: var(--sa-surface-sunken);
    border: 1px solid var(--sa-hairline);
    color: var(--sa-text);
}

.sa-tl-ctx i { font-size: 0.78rem; }
.sa-tl-ctx--loc i { color: var(--sa-green); }
.sa-tl-ctx--reader i { color: var(--sa-accent); }

.sa-tl-ctx-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.sa-tl-ctx.is-inherited {
    border-style: dashed;
    font-weight: 500;
    opacity: 0.72;
}

.sa-tl-ctx.is-inherited .sa-tl-ctx-text { font-style: italic; }

/* Nested snapshot summary list */
.sa-tl-snaps {
    list-style: none;
    margin: 0.5rem 0 0;
    padding: 0.35rem 0 0 0.8rem;
    border-left: 1px dashed var(--sa-hairline-strong);
}

.sa-tl-snap {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem;
    font-size: 0.78rem;
    padding: 0.15rem 0;
    color: var(--sa-text-dim);
}

.sa-tl-snap-icon { color: var(--sa-red); font-size: 0.68rem; }
.sa-tl-snap-time { font-family: var(--sa-mono); color: var(--sa-text-faint); }
.sa-tl-snap-summary { margin-left: auto; color: var(--sa-text-faint); }

/* ======================================================================== */
/*  Expanded step detail                                                     */
/* ======================================================================== */
.sa-tl-detail-wrap {
    margin-top: 0.65rem;
    padding-top: 0.65rem;
    border-top: 1px dashed var(--sa-hairline-strong);
    animation: sa-tl-reveal 0.26s var(--sa-ease) both;
}

@keyframes sa-tl-reveal {
    from { opacity: 0; transform: translateY(-5px); }
    to { opacity: 1; transform: none; }
}

/* .sa-tl-detail-loading removed — the timeline detail uses YbStateBlock (#233). */

.sa-tl-detail { display: flex; flex-direction: column; gap: 0.75rem; }
.sa-tl-detail-block { display: flex; flex-direction: column; gap: 0.4rem; }

.sa-tl-detail-label {
    font-size: 0.68rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--sa-text-faint);
}

.sa-tl-staff { display: flex; align-items: baseline; gap: 0.5rem; flex-wrap: wrap; }
.sa-tl-staff-pills { display: flex; flex-wrap: wrap; gap: 0.4rem; }

.sa-tl-wearables, .sa-tl-snapdetail { display: flex; flex-direction: column; gap: 0.35rem; }

.sa-tl-wearable {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.4rem 0.9rem;
    font-size: 0.82rem;
    padding: 0.4rem 0.6rem;
    background: var(--sa-surface-sunken);
    border: 1px solid var(--sa-hairline);
    border-radius: var(--sa-radius-sm);
}

.sa-tl-wearable-name { font-weight: 600; color: var(--sa-accent); }
.sa-tl-wearable-dist, .sa-tl-wearable-dir {
    color: var(--sa-text-dim);
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    font-family: var(--sa-mono);
    font-size: 0.78rem;
}

.sa-tl-snapdetail {
    padding: 0.5rem 0.6rem;
    background: var(--sa-surface-sunken);
    border: 1px solid var(--sa-hairline);
    border-radius: var(--sa-radius-sm);
}

.sa-tl-snapdetail-time {
    font-family: var(--sa-mono);
    font-size: 0.74rem;
    color: var(--sa-text-faint);
    margin-bottom: 0.35rem;
}

.sa-tl-anomaly {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.35rem 0.6rem;
    font-size: 0.82rem;
    padding: 0.18rem 0;
}

.sa-tl-anomaly-name { font-weight: 600; color: var(--sa-text); }

.sa-tl-vitals { display: inline-flex; flex-wrap: wrap; gap: 0.3rem; }

.sa-tl-vital {
    font-family: var(--sa-mono);
    font-size: 0.73rem;
    font-weight: 600;
    padding: 0.1rem 0.45rem;
    border-radius: var(--sa-radius-sm);
    background: var(--sa-accent-soft);
    color: var(--sa-accent);
    white-space: nowrap;
}

.sa-tl-vital--redacted {
    font-family: inherit;
    background: var(--sa-slate-soft);
    color: var(--sa-slate);
    font-style: italic;
}

/* Neighbours, grouped into Same class / Other classes / SPOCs. Each group keeps the same
   structure; the left accent + count pill are tinted per group. The roll-up variant adds the
   "how long they stayed" tag to each name. */
.sa-tl-neighbours {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    width: 100%;
}

.sa-tl-ngroup {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    padding-left: 0.55rem;
    border-left: 3px solid var(--ng-color, var(--sa-slate));
}

.sa-tl-ngroup--same { --ng-color: var(--sa-green); }
.sa-tl-ngroup--other { --ng-color: var(--sa-amber); }
.sa-tl-ngroup--spoc { --ng-color: var(--sa-accent); }

.sa-tl-ngroup-label {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    font-size: 0.64rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--sa-text-faint);
}

.sa-tl-ngroup-count {
    font-family: var(--sa-mono);
    font-size: 0.62rem;
    line-height: 1.4;
    padding: 0.02rem 0.34rem;
    border-radius: 999px;
    background: var(--ng-color, var(--sa-slate));
    color: #fff;
}

.sa-tl-ngroup-items { display: flex; flex-wrap: wrap; gap: 0.3rem; }

.sa-tl-neighbour {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    font-size: 0.76rem;
    padding: 0.14rem 0.5rem;
    border-radius: var(--sa-radius-sm);
    background: var(--sa-surface-sunken);
    border: 1px solid var(--sa-hairline);
    color: var(--sa-text);
}

.sa-tl-neighbour-name { font-weight: 600; }

.sa-tl-neighbour-stay {
    font-family: var(--sa-mono);
    font-size: 0.66rem;
    color: var(--sa-text-dim);
    padding-left: 0.4rem;
    border-left: 1px solid var(--sa-hairline-strong);
}

.sa-tl-detail-empty { font-size: 0.82rem; color: var(--sa-text-dim); }

/* ======================================================================== */
/*  Per-kind / per-status node colour (drives marker + card bar)             */
/* ======================================================================== */
.sa-tl-node { --sa-node-color: var(--sa-accent); --sa-node-soft: var(--sa-accent-soft); }

/* Alert-Status phase colours (red → amber → green progression). */
.sa-tl-phase--detected { --sa-node-color: var(--sa-red); --sa-node-soft: var(--sa-red-soft); }
.sa-tl-phase--inprogress { --sa-node-color: var(--sa-amber); --sa-node-soft: var(--sa-amber-soft); }
.sa-tl-phase--resolved { --sa-node-color: var(--sa-green); --sa-node-soft: var(--sa-green-soft); }

.sa-tl-node--detected,
.sa-tl-status--detected,
.sa-tl-status--escalated { --sa-node-color: var(--sa-red); --sa-node-soft: var(--sa-red-soft); }

.sa-tl-node--resolution,
.sa-tl-status--resolved { --sa-node-color: var(--sa-green); --sa-node-soft: var(--sa-green-soft); }

.sa-tl-status--closed { --sa-node-color: var(--sa-slate); --sa-node-soft: var(--sa-slate-soft); }

.sa-tl-status--attended,
.sa-tl-status--acknowledged,
.sa-tl-status--updated { --sa-node-color: var(--sa-blue); --sa-node-soft: var(--sa-blue-soft); }

.sa-tl-status--notified { --sa-node-color: var(--sa-amber); --sa-node-soft: var(--sa-amber-soft); }

.sa-tl-node--governance { --sa-node-color: var(--sa-accent); --sa-node-soft: var(--sa-accent-soft); }

/* Live (open) alert: the active In-Progress phase marker emits a calm radar pulse. */
.sa-timeline--open .sa-tl-phase--inprogress .sa-tl-marker--phase::after {
    content: "";
    position: absolute;
    inset: -3px;
    border-radius: 50%;
    border: 2px solid currentColor;
    opacity: 0.55;
    animation: sa-tl-radar 1.9s ease-out infinite;
    pointer-events: none;
}

@keyframes sa-tl-radar {
    0% { transform: scale(0.82); opacity: 0.55; }
    100% { transform: scale(1.75); opacity: 0; }
}

/* ======================================================================== */
/*  Motion / accessibility preferences                                       */
/* ======================================================================== */
@media (prefers-reduced-motion: reduce) {
    .sa-tl-node,
    .sa-tl-phase,
    .sa-tl-detail-wrap { animation: none; }
    .sa-timeline--open .sa-tl-phase--inprogress .sa-tl-marker--phase::after,
    .sa-spin { animation: none; }
    .sa-tl-card,
    .sa-tl-marker,
    .sa-tl-filter,
    .sa-tl-chevron { transition: none; }
}

/* ======================================================================== */
/*  Print / export (#34): print only the timeline dialog as a clean document */
/* ======================================================================== */
@media print {
    body * { visibility: hidden !important; }

    .sa-timeline-dialog,
    .sa-timeline-dialog * { visibility: visible !important; }

    .sa-timeline-dialog {
        position: absolute !important;
        left: 0 !important;
        top: 0 !important;
        width: 100% !important;
        max-width: 100% !important;
        box-shadow: none !important;
        border: none !important;
        transform: none !important;
    }

    .e-dlg-overlay,
    .e-dlg-container { background: transparent !important; }

    .sa-tl-toolbar,
    .sa-tl-filters,
    .sa-tlh-metrics-foot,
    .sa-tlh-actions,
    .sa-timeline-dialog .e-dlg-closeicon-btn { display: none !important; }

    .sa-tl-deck {
        position: static !important;
        backdrop-filter: none !important;
        box-shadow: none !important;
        margin: 0 0 0.85rem !important;
    }
    .sa-tlh { position: static !important; backdrop-filter: none !important; }

    .sa-timeline-dialog .e-dlg-content {
        max-height: none !important;
        overflow: visible !important;
    }

    .sa-tl-node { animation: none !important; }
    .sa-tl-node,
    .sa-tl-detail-wrap,
    .sa-tl-snapdetail,
    .sa-tl-wearable { break-inside: avoid; }
}

/*
 * ============================================================================
 *  Safety Alert RESULT screen (the cards list + the 3-tab detail panel)
 * ----------------------------------------------------------------------------
 *  Not the timeline dialog. `.sa-result` carries the full --sa-* token set in
 *  both themes (see the token blocks at the top), so everything below is styled
 *  from tokens and adapts to light/dark automatically. Bootstrap surfaces inside
 *  the tabs are re-themed here so dark mode is consistent across all three tabs.
 * ============================================================================
 */

/* ---- Left column panel: wraps the title + the scrolling alert list. Tokenised
   so it sits flush with the detail panel's surface/hairline language instead of
   the previous bare Bootstrap card. */
.sa-alert-aside { font-family: var(--sa-sans); }

.sa-alert-panel {
    display: flex;
    flex-direction: column;
    background: var(--sa-surface);
    border: 1px solid var(--sa-hairline);
    border-radius: var(--sa-radius);
    box-shadow: var(--sa-shadow-sm);
    padding: 0.6rem 0.6rem 0.5rem;
    margin-bottom: 1rem;
}

/* Header strip: icon + serif title + live count, divided from the list below. */
.sa-alert-panel-head {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    padding-bottom: 0.5rem;
    margin-bottom: 0.5rem;
    border-bottom: 1px solid var(--sa-hairline);
}

.sa-alert-panel-ic {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.65rem;
    height: 1.65rem;
    flex-shrink: 0;
    border-radius: var(--sa-radius-sm);
    background: var(--sa-accent-soft);
    color: var(--sa-accent);
    font-size: 0.95rem;
}

.sa-alert-panel-title {
    margin: 0;
    flex: 1 1 auto;
    font-family: var(--sa-serif);
    font-optical-sizing: auto;
    font-size: 1.12rem;
    font-weight: 600;
    letter-spacing: -0.01em;
    color: var(--sa-text);
}

/* Count pill — accent-soft chip with tabular numerals, echoes the detail facts. */
.sa-alert-count {
    flex-shrink: 0;
    min-width: 1.6rem;
    padding: 0.1rem 0.5rem;
    border-radius: 999px;
    background: var(--sa-accent-soft);
    color: var(--sa-accent-strong);
    font-family: var(--sa-mono);
    font-size: 0.8rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    text-align: center;
    line-height: 1.5;
}

/* Empty state — quiet, centred, token-coloured (replaces tailwind text-gray-*). */
.sa-alert-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.45rem;
    padding: 1.6rem 0.75rem;
    color: var(--sa-text-faint);
    text-align: center;
    font-size: 0.85rem;
}
.sa-alert-empty i { font-size: 1.4rem; color: var(--sa-green); opacity: 0.8; }

/* Detail-pane empty state (#116 follow-up): a composed icon + title + hint block
   so an un-drilled tab reads as an intentional prompt, not a bare grey sentence in
   a big void. Shared by all three detail tabs. */
.sa-pane-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    min-height: 200px;
    padding: 2.4rem 1.25rem;
    text-align: center;
}
.sa-pane-empty-ic {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 3rem;
    height: 3rem;
    border-radius: 50%;
    background: var(--sa-accent-soft);
    color: var(--sa-accent);
    font-size: 1.4rem;
    box-shadow: 0 0 0 6px rgba(var(--sa-accent-rgb), 0.06);
}
.sa-pane-empty-title {
    font-family: var(--sa-serif);
    font-size: 1.05rem;
    font-weight: 600;
    color: var(--sa-text);
}
.sa-pane-empty-hint {
    font-size: 0.82rem;
    color: var(--sa-text-faint);
    max-width: 30ch;
}

/* (#2) Alert-list: capped, independently-scrolling column. On phones it must not
   dominate the viewport and push the detail panel off-screen. */
.sa-alert-list {
    overflow-y: auto;
    max-height: calc(100vh - 180px);
    padding-right: 0.5rem;
    scrollbar-width: thin;
}

@media (max-width: 991.98px) {
    /* Below Bootstrap's lg the columns stack — keep the list short so Load's
       target (the detail panel) is reachable without a long scroll past cards. */
    .sa-alert-list { max-height: 45vh; }
}

/* ---- Two-pane shell: sticky, self-scrolling detail panel beside the list ---
   The detail column pins to the top of the viewport and scrolls its own content
   (the active tab pane) internally, so the tab strip stays visible and the panel
   never drifts away while the alert list is browsed. A clear gutter separates the
   two columns. Disabled below lg, where the columns stack and flow naturally. */
.sa-result-row {
    --bs-gutter-x: 0.75rem;
    align-items: flex-start;
}

@media (min-width: 992px) {
    .sa-result-row .sa-result {
        position: sticky;
        top: 1rem;
        align-self: flex-start;
        max-height: calc(100vh - 1.5rem);
    }

    /* Make the tab card a column: fixed tab strip on top, scrolling pane below. */
    .sa-result-row .sa-result .tab-container {
        display: flex;
        flex-direction: column;
        max-height: calc(100vh - 1.5rem);
    }
    .sa-result-row .sa-result .modern-tabs { flex: 0 0 auto; }
    .sa-result-row .sa-result .modern-tab-content {
        flex: 1 1 auto;
        min-height: 0;
        overflow-y: auto;
        scrollbar-width: thin;
        scroll-behavior: smooth;
    }
}

/* ---- Where the list column is allowed to take its xxl proportion (#285) ---
   The two panes are col-md-4/col-md-8 stepping to col-xxl-3/col-xxl-9, so at
   Bootstrap's xxl breakpoint the list drops from 33.3% to 25% — it gets NARROWER
   as the screen gets WIDER. Between 1400px and ~1531px that put the rail below
   what a card's line 1 needs and the timestamp's minutes were clipped on every
   card: "16-02-2026 19:08" rendered as "16-02-2026 19:".

   Line 1 cannot absorb it. It is flex-wrap: nowrap with every slot flex: 0 0 auto,
   so it needs a fixed 232px — id 54 + category 17 + severity 40 + when 102 + three
   0.4rem gaps — and .alert-card's overflow-x: hidden clips whatever will not fit.
   The timestamp is last and margin-left: auto, so the whole shortfall lands on its
   right edge.

   Measured: the chrome between this column's edge and that row's content box is
   73px, so the column needs 305px; 320px leaves headroom for a longer alert id and
   for font metrics that differ between themes. 25% first yields 320px at a 1592px
   viewport, which is where the xxl proportion becomes safe and this override ends.

   It has to move the PAIR. A min-width on the list column alone does not shrink
   its fixed-width sibling — the row overflows and the detail pane wraps below the
   list. Keeping the two percentages summing to 100% is what makes this safe. */
@media (min-width: 1400px) and (max-width: 1591.98px) {
    .sa-result-row .sa-alert-aside.col-xxl-3 { width: 33.333333%; }
    .sa-result-row .sa-result.col-xxl-9 { width: 66.666667%; }
}

/* ---- Detail panel base + headings --------------------------------------- */
.sa-result { font-family: var(--sa-sans); color: var(--sa-text); }

.sa-result h5,
.sa-result h6 {
    font-family: var(--sa-serif);
    font-optical-sizing: auto;
    letter-spacing: -0.005em;
}

/* Bootstrap text utilities used by the headings — re-pointed at tokens so they
   keep contrast in dark mode (text-secondary/-dark would otherwise stay dark). */
.sa-result .text-secondary { color: var(--sa-text-dim) !important; }
.sa-result h5 .text-secondary,
.sa-result h6 { color: var(--sa-text-dim); }
.sa-result .border,
.sa-result .border-top,
.sa-result .border-bottom { border-color: var(--sa-hairline) !important; }

/* ---- Unified fact card (label + value) — replaces the divergent claim-cards - */
.sa-result .sa-facts { display: flex; flex-direction: column; gap: 0.3rem; }

.sa-result .sa-fact {
    position: relative;
    text-align: left;            /* label + value both left, regardless of inherited centering */
    background: var(--sa-surface);
    border: 1px solid var(--sa-hairline);
    border-left: 3px solid rgba(var(--sa-accent-rgb), 0.5);
    border-radius: var(--sa-radius-sm);
    box-shadow: var(--sa-shadow-sm);
    padding: 0.22rem 0.6rem 0.26rem;
    /* Squeezes the run of fact cards: the pane inherits a 36px line-height, which was
       adding ~18px of leading above and below every value on top of this padding. */
    line-height: 1.35;
    transition: border-color 0.16s var(--sa-ease, ease), box-shadow 0.16s ease;
}

.sa-result .sa-fact-label,
.sa-result .sa-fact-value { text-align: left; }

.sa-result .sa-fact:hover {
    border-left-color: var(--sa-accent);
    box-shadow: var(--sa-shadow-md);
}

.sa-result .sa-fact-label {
    display: block;
    font-size: 0.62rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    color: var(--sa-text-faint);
    margin-bottom: 0.12rem;
}

.sa-result .sa-fact-value {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.4rem;
    font-weight: 600;
    color: var(--sa-text);
    word-break: break-word;
}

.sa-result .sa-fact-ic { color: var(--sa-accent); font-size: 0.92em; }

/* The derived "Time taken / Elapsed" fact reads as the headline number. */
.sa-result .sa-fact--accent {
    border-left-color: var(--sa-accent);
    background: linear-gradient(0deg, rgba(var(--sa-accent-rgb), 0.06), var(--sa-surface));
}
.sa-result .sa-fact--accent .sa-fact-value {
    font-family: var(--sa-serif);
    font-size: 1.1rem;
    letter-spacing: -0.01em;
}

/* ---- WHY the alert fired, as its own full-width plate above the trackings.
   It was a chip in the identity line, where being the one SENTENCE among fixed-width
   chips wrapped that row onto two and three lines. Here it can run as long as the
   cause needs. Still the only filled element in the pane — solid against soft is a
   difference that survives greyscale and colour-blindness, and it is what a
   responder actually acts on. ---- */
.sa-result .sa-why {
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
    margin-bottom: 0.4rem;
    padding: 0.28rem 0.55rem;
    border-radius: var(--sa-radius-sm);
    background: var(--sa-why-bg);
    color: var(--sa-why-ink);
    line-height: 1.35;
}
.sa-result .sa-why > i { color: var(--sa-why-ink-dim); font-size: 0.8rem; flex-shrink: 0; }
.sa-result .sa-why-k {
    font-size: 0.58rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    color: var(--sa-why-ink-dim);
    flex-shrink: 0;
}
.sa-result .sa-why-v { font-size: 0.8rem; font-weight: 600; }

/* Provenance chip ("latest" / "carried") — quiet, neutral. */
.sa-result .sa-fact-inherited {
    font-size: 0.58rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--sa-text-faint);
    background: rgba(var(--sa-slate-rgb), 0.16);
    border-radius: 999px;
    padding: 0.05rem 0.4rem;
    line-height: 1.5;
}

/* ---- In-tab prev/next step navigation ----------------------------------- */
.sa-result .sa-stepnav { display: inline-flex; gap: 0.35rem; }

.sa-result .sa-stepnav .btn {
    border-radius: var(--sa-radius-sm);
    color: var(--sa-text-dim);
    border-color: var(--sa-hairline-strong);
}
.sa-result .sa-stepnav .btn:hover:not(:disabled) {
    color: var(--sa-accent);
    border-color: var(--sa-accent);
    background: rgba(var(--sa-accent-rgb), 0.08);
}

/* The .card / .card-header / .wearable-section retheming that lived here is gone with
   the last of those elements (#253): every list on this screen is a .sa-subsec now, and
   rules for classes the markup no longer emits only mislead the next reader. */

/* ============================================================================
   IN-PANE SECTIONS (#253 item 5b)
   ----------------------------------------------------------------------------
   Replaces .wearable-section on the Tracking tab. Between the pane edge and an
   actual value there were FOUR stacked containers, each paying for its own
   inset: the pane's padding, a filled and rounded .wearable-section, 1rem under
   the heading, then a bordered card per row. A section is a heading and its
   content — separated by space, not by another surface. Dropping the box is the
   single biggest win here, and it also removes a raw --bs-light background from
   outside the --sa-* token system.
   ========================================================================== */
.sa-result .sa-subsec { margin-bottom: 0.6rem; }
.sa-result .sa-subsec:last-child { margin-bottom: 0; }

/* A FRAMED section: a hairline and a radius, so two stacked sections read as two
   distinct things rather than one run of rows. Deliberately a border and not the old
   .wearable-section's filled, 1rem-padded surface — that was a box inside an already
   padded pane and it is what made these sections twice as tall as their content. The
   frame costs 1px and 0.4rem; the box cost ~2rem. */
.sa-result .sa-subsec--framed {
    border: 1px solid var(--sa-hairline);
    border-radius: var(--sa-radius-md);
    padding: 0.4rem 0.5rem 0.45rem;
    background: var(--sa-surface);
}
.sa-result .sa-subsec-h {
    display: flex;
    align-items: baseline;
    gap: 0.45rem;
    margin: 0 0 0.35rem;
    font-family: var(--sa-serif);
    font-size: 0.82rem;
    font-weight: 700;
    color: var(--sa-text-dim);
    line-height: 1.35;
}
/* Inside a frame the heading gets a hairline of its own, so the section reads as
   heading-then-content rather than a floating title above a list. */
.sa-result .sa-subsec--framed .sa-subsec-h {
    padding-bottom: 0.3rem;
    border-bottom: 1px solid var(--sa-hairline);
}
/* The count says what it counts, in words. Three wearable-ish numbers sat near
   each other as two instances of one badge in bg-success and bg-warning — raw
   Bootstrap contextual colours, and nothing saying whether one was the sum of
   another. Quiet, tokenised, and self-describing. */
.sa-result .sa-subsec-n {
    font-family: var(--sa-sans);
    font-size: 0.68rem;
    font-weight: 600;
    color: var(--sa-text-faint);
    white-space: nowrap;
}
.sa-result .sa-subsec-empty {
    margin: 0;
    font-size: 0.78rem;
    font-style: italic;
    color: var(--sa-text-faint);
}

/* One name treatment wherever a person appears — the sighting list on the
   Tracking tab and the snapshot subject on the Snapshot tab. They are joined by
   MAC in the resolver already; this is that join reaching the screen. */
.sa-result .sa-person {
    font-weight: 600;
    font-size: 0.82rem;
    color: var(--sa-text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ---- Tab nav (scoped) — cohesive indigo in both themes -------------------
   Overrides the global modern-tabs colours (a muddy-rose inactive + garish
   yellow active in dark) with the panel's accent tokens. Scoped to .sa-result
   so other screens' tabs are untouched. */

/* ONE inset for the strip and the pane (#253 item 3c). They used to be written
   out separately and drifted: 1rem on the strip against 0.95rem on the pane, and
   0.7rem against 1rem once the ≤1199px step-down fired — so the first tab's left
   edge sat ~5px inside the content edge below it on smaller laptops. A single
   custom property means the two cannot disagree again, whatever the breakpoint. */
.sa-result { --sa-pane-pad: 0.95rem; }

.sa-result .modern-tabs {
    background: var(--sa-surface-sunken);
    border-bottom: 1px solid var(--sa-hairline);
    padding: 0.75rem var(--sa-pane-pad);
    /* The global strip is flex-wrap: nowrap, so between 768px and md the tabs
       could not wrap and simply overflowed the panel. */
    flex-wrap: wrap;
    row-gap: 0.4rem;
}

/* ONE divider between strip and pane (#253 item 3d). Three stacked here: the
   global gradient pip on .modern-tabs::after, this strip's own hairline, and the
   pane's border-top. Each deliberate on its own, a smudge together. The hairline
   survives because it belongs to the strip, which is the element doing the
   separating; the other two are switched off inside .sa-result. */
.sa-result .modern-tabs::after { display: none; }
.sa-result .modern-tab-content { border-top: 0; }

/* Inactive tabs read as defined, clickable chips — a solid surface, a real border,
   full-strength text and a lively accent icon — instead of the faint dim-grey links
   that looked disabled (#116 follow-up). */
.sa-result .modern-tabs .nav-link,
.sa-result .modern-tabs .nav-link:not(.active) {
    color: var(--sa-text);
    font-weight: 600;
    background: var(--sa-surface);
    border: 1px solid var(--sa-hairline-strong);
    box-shadow: var(--sa-shadow-sm);
}

/* Accent icon gives each inactive tab a spot of colour so the strip isn't flat grey. */
.sa-result .modern-tabs .nav-link i { color: var(--sa-accent); }

.sa-result .modern-tabs .nav-link:hover:not(.active) {
    color: var(--sa-accent-strong);
    background: rgba(var(--sa-accent-rgb), 0.10);
    border-color: rgba(var(--sa-accent-rgb), 0.40);
    box-shadow: 0 5px 16px rgba(var(--sa-accent-rgb), 0.20);
}

.sa-result .modern-tabs .nav-link.active,
[data-theme="dark"] .sa-result .modern-tabs .nav-link.active {
    color: #fff;
    background: linear-gradient(145deg, var(--sa-accent), var(--sa-accent-strong));
    box-shadow: 0 4px 14px rgba(var(--sa-accent-rgb), 0.4);
}

/* ONE BASELINE (#253 items 3a, 3b).
   site.css lifts the active tab by translateY(-2px) and hovered tabs by
   translateY(-1px) scale(1.02). This scope restyled the active tab's colour, fill
   and shadow but not its transform, so the lift survived: three tabs at three
   vertical positions — 0, −1px and −2px — depending on which was active and where
   the pointer was, and which tab was out of line changed as you clicked. The
   active state is already unmistakable from the solid indigo fill and white ink;
   it does not also need to move. Nothing else on this screen moves on hover. */
.sa-result .modern-tabs .nav-link,
.sa-result .modern-tabs .nav-link:hover,
.sa-result .modern-tabs .nav-link:hover:not(.active),
.sa-result .modern-tabs .nav-link.active {
    transform: none;
}

/* The tab id suffix. Was styled through `.nav-link.active text`, a selector that
   never matched anything: Razor's <text> is a compiler directive that emits its
   CONTENTS with no element, so the id never took the active tab's white ink
   (#253 item 3, "two more found in the same block"). It is a real span now.

   The slot is fixed and tabular, and it is rendered whether or not a record is
   loaded, so the strip is the same width before and after an alert arrives and
   the tabs do not resize under the cursor. */
.sa-result .modern-tabs .sa-tab-id {
    display: inline-block;
    min-width: 3.3rem;
    font-family: var(--sa-mono);
    font-variant-numeric: tabular-nums;
    font-size: 0.78em;
    opacity: 0.85;
}
.sa-result .modern-tabs .nav-link.active i,
.sa-result .modern-tabs .nav-link.active .sa-tab-id { color: #fff; }

.sa-result .modern-tabs .nav-link:focus-visible {
    outline: 2px solid var(--sa-accent);
    outline-offset: 2px;
}

/* The global underline pip is redundant on the solid fill. */
.sa-result .modern-tabs .nav-link.active::after { background: transparent; }

/* ---- Tab shell: lift the 3 tabs onto a single token-themed card so the panes
   read as one surface rather than loose Bootstrap content. Scoped to .sa-result
   so the app's other .tab-container usages are untouched. */
.sa-result .tab-container {
    background: var(--sa-surface);
    border: 1px solid var(--sa-hairline);
    border-radius: var(--sa-radius);
    box-shadow: var(--sa-shadow-sm);
    overflow: hidden;
    /* Match body's 0.3s theme cross-fade (global .tab-container uses 0.4s, whose lag
       briefly reveals the lighter body colour through the rounded corners on switch).
       Border included so the hairline fades in rather than snapping. */
    transition: background-color 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease;
}
.sa-result .modern-tab-content {
    padding: 0.85rem var(--sa-pane-pad) 1rem;
    background: var(--sa-surface);
    transition: background-color 0.3s ease;
}

/* Bootstrap rows carry negative side margins of half the gutter, which push them
   past the pane's content box and give every tab a few pixels of horizontal scroll —
   a scrollbar under content that looked like it fitted. Zeroed here; the columns'
   own padding still provides the gutter between them. */
.sa-result .modern-tab-content .row { margin-left: 0; margin-right: 0; }
.sa-result .modern-tabs { transition: background-color 0.3s ease, border-color 0.3s ease; }

/* ---- Count badges: retheme Bootstrap's contextual pills to soft token tints
   so the numbers stay legible in dark mode (raw bg-warning/#ffc107 is a glaring,
   low-contrast yellow on the dark panel). Soft fill + strong-coloured text. */
.sa-result .badge.bg-warning {
    background: var(--sa-amber-soft) !important;
    color: var(--sa-amber) !important;
}
.sa-result .badge.bg-success {
    background: var(--sa-green-soft) !important;
    color: var(--sa-green) !important;
}
.sa-result .badge.bg-info {
    background: var(--sa-accent-soft) !important;
    color: var(--sa-accent-strong) !important;
}
.sa-result .badge {
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}
@media (max-width: 575.98px) {
    .sa-result .modern-tab-content { padding: 0.7rem 0.6rem 0.8rem; }
}

/* ---- Active alert card (left list): single rounded accent frame + chip ------
   The wrapper border is rounded to match the inner alert-card radius so the
   active state reads as one accent frame, not a square ring over a rounded card. */
.sa-alert-cardwrap {
    position: relative;
    border-radius: var(--sa-radius-md);
    transition: border-color 0.18s var(--sa-ease, ease), box-shadow 0.18s ease;
}
.sa-alert-cardwrap.is-active {
    border-color: var(--sa-accent) !important;
    box-shadow: 0 0 0 1px var(--sa-accent), 0 6px 18px -6px rgba(var(--sa-accent-rgb), 0.45);
}
/* Eye-icon badge beside the alert id / zone icon (Row 1 of the card).
   Accent = loaded in the detail panel; amber variant = timeline open for it. */
.sa-alert-active-tag {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.2rem;
    height: 1.2rem;
    border-radius: 999px;
    background: var(--sa-accent);
    color: #fff;
    line-height: 1;
    flex-shrink: 0;
    box-shadow: 0 0 0 2px var(--sa-accent-soft);
}
.sa-alert-active-tag i { font-size: 0.68rem; }
.sa-alert-active-tag--timeline {
    background: var(--sa-amber);
    box-shadow: 0 0 0 2px var(--sa-amber-soft);
}

/* ---- Per-tab single-row header: actions | identity | navigation -----------
   One compact row across all three tabs to save vertical space. The title flexes
   to fill the middle; the nav is pushed to the far right. Wraps on narrow panes. */
.sa-tabhead {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.35rem 0.5rem;
    margin-bottom: 0.45rem;
}
.sa-tabhead-actions {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    flex-shrink: 0;
}
/* The action squares sized to the CHIPS they sit beside, not to the panel buttons.
   At 1.95rem the Timeline square stood a head taller than every id chip in the same
   row and read as the loudest thing in a row of quiet labels. This matches the chip
   height so the row has one rhythm. Scoped to the tab header — the step-nav and the
   panel's own buttons keep their sizes. */
.sa-result .sa-tabhead-actions .sa-iconbtn {
    width: 1.3rem;
    height: 1.3rem;
}
.sa-result .sa-tabhead-actions .sa-iconbtn i { font-size: 0.68rem; }
/* Identity line: a wrapping row of compact label/value chips (not the big serif
   sentence) so it stays dense and scannable in the narrow detail column. */
.sa-result .sa-tabhead-title {
    flex: 1 1 200px;
    min-width: 0;
    margin: 0;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.3rem 0.35rem;
    font-family: var(--sa-sans);
}
.sa-idchip {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.1rem 0.45rem;
    border-radius: var(--sa-radius-sm);
    background: var(--sa-surface-sunken);
    border: 1px solid var(--sa-hairline);
    line-height: 1.3;
    white-space: nowrap;
}
.sa-idchip .k {
    font-size: 0.55rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--sa-text-faint);
}
.sa-idchip .v {
    display: inline-flex;
    align-items: center;
    gap: 0.22rem;
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--sa-text);
    font-variant-numeric: tabular-nums;
}
.sa-tabhead .sa-stepnav { flex-shrink: 0; margin-left: auto; }

/* ---- Square icon-only buttons (Timeline, closure, Back, Prev/Next, Load) ----
   Uniform compact squares so the action/nav clusters take minimal width. Labels
   move to the title tooltip. */
.sa-iconbtn {
    display: inline-flex !important;
    align-items: center;
    justify-content: center;
    width: 1.95rem;
    height: 1.95rem;
    padding: 0 !important;
    line-height: 1;
    flex-shrink: 0;
}
.sa-iconbtn i { font-size: 0.95rem; margin: 0 !important; }
.sa-stepnav .sa-iconbtn { width: 1.85rem; height: 1.85rem; }

/* ---- Back-to-parent-tab button — lives at the START of the step-nav group
   (Tracking → Summary, Details → Tracking) so it's right where the cursor
   already is after using Prev/Next, not back up by the tab strip. ----------- */
.sa-back-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.28rem 0.7rem;
    border-radius: var(--sa-radius-sm);
    border: 1px solid var(--sa-hairline-strong);
    background: var(--sa-surface);
    color: var(--sa-text-dim);
    font-size: 0.8rem;
    font-weight: 600;
    line-height: 1.2;
    transition: all 0.16s var(--sa-ease, ease);
}
.sa-back-btn:hover {
    color: var(--sa-accent);
    border-color: var(--sa-accent);
    background: rgba(var(--sa-accent-rgb), 0.08);
    transform: translateX(-1px);
}
.sa-back-btn i { font-size: 0.92em; }

/* Hairline divider between the Back action and the Prev/Next pager. */
.sa-stepnav .sa-back-btn { margin-right: 0.5rem; position: relative; }
.sa-stepnav .sa-back-btn::after {
    content: "";
    position: absolute;
    right: -0.3rem;
    top: 50%;
    transform: translateY(-50%);
    height: 1.1rem;
    width: 1px;
    background: var(--sa-hairline-strong);
}

/* ---- Step-nav position pill ("2 / 5") between Prev / Next ------------------ */
.sa-result .sa-stepnav { align-items: center; }
.sa-stepnav-pos {
    display: inline-flex;
    align-items: baseline;
    gap: 0.12rem;
    min-width: 2.6rem;
    justify-content: center;
    padding: 0.12rem 0.5rem;
    border-radius: 999px;
    background: var(--sa-accent-soft);
    color: var(--sa-accent-strong);
    font-family: var(--sa-mono);
    font-weight: 700;
    font-size: 0.82rem;
    font-variant-numeric: tabular-nums;
}
.sa-stepnav-sep { color: var(--sa-text-faint); margin: 0 0.05rem; font-weight: 600; }

/* ---- Enriched "View" buttons (#trackId / #detailId) ----------------------- */
.sa-view-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.26rem 0.7rem;
    border-radius: var(--sa-radius-sm);
    border: 1px solid rgba(var(--sa-accent-rgb), 0.45);
    background: rgba(var(--sa-accent-rgb), 0.07);
    color: var(--sa-accent-strong);
    font-size: 0.82rem;
    font-variant-numeric: tabular-nums;
    line-height: 1.2;
    transition: all 0.16s var(--sa-ease, ease);
}
.sa-view-btn i { font-size: 0.95em; transition: transform 0.16s var(--sa-ease, ease); }
.sa-view-btn:hover {
    background: linear-gradient(145deg, var(--sa-accent), var(--sa-accent-strong));
    border-color: var(--sa-accent-strong);
    color: #fff;
    box-shadow: 0 3px 12px rgba(var(--sa-accent-rgb), 0.3);
    transform: translateY(-1px);
}
.sa-view-btn:hover i { transform: translateX(2px); }
/* Currently-open track/detail keeps the filled accent so the list shows selection. */
.sa-view-btn.is-active {
    background: linear-gradient(145deg, var(--sa-accent), var(--sa-accent-strong));
    border-color: var(--sa-accent-strong);
    color: #fff;
}

/* ---- Compass dial (reader bearing) — token-themed, light/dark aware -------- */
.sa-compass { display: inline-flex; align-items: center; gap: 0.45rem; }
.sa-compass-dial { flex-shrink: 0; display: block; }
.sa-compass-ring { fill: var(--sa-surface-sunken); stroke: var(--sa-hairline-strong); stroke-width: 1.4; }
.sa-compass-tick { stroke: var(--sa-text-faint); stroke-width: 1.3; stroke-linecap: round; }
.sa-compass-north { stroke: rgba(var(--sa-red-rgb), 0.85); stroke-width: 1.8; stroke-linecap: round; }
.sa-compass-needle { stroke: var(--sa-accent); stroke-width: 2; stroke-linecap: round; }
.sa-compass-arrow { fill: var(--sa-accent); }
.sa-compass-hub { fill: var(--sa-accent-strong); }
.sa-compass-read { display: inline-flex; flex-direction: column; line-height: 1.05; }
.sa-compass-deg { font-family: var(--sa-mono); font-weight: 700; font-size: 0.82rem; color: var(--sa-text); }
.sa-compass-abbr {
    font-size: 0.6rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--sa-text-faint);
}

/* Compact dial for list density (#253 item 5b). At 38px the dial, not the text,
   set the row height. The bearing stays readable as TEXT — the abbreviation and
   the degrees are both still there, so nothing is conveyed by rotation alone. */
.sa-result .sa-compass--sm { gap: 0.3rem; }
.sa-result .sa-compass--sm .sa-compass-read { flex-direction: row; align-items: baseline; gap: 0.25rem; }
/* The abbreviation IS the value at list density; the exact bearing is detail for
   the timeline, and it stays available here on the dial's tooltip. Dropping it
   also gives the person's name back the width it was losing to a number nobody
   reads off a list. */
.sa-result .sa-compass--sm .sa-compass-deg { display: none; }
.sa-result .sa-compass--sm .sa-compass-abbr { font-size: 0.68rem; color: var(--sa-text-dim); }

/* ============================================================================
 *  Density pass — fit the narrow detail column on 13"–16" laptops
 * ----------------------------------------------------------------------------
 *  The left alert cards were cramped (status wrapped) and the section headers
 *  ran large. Tighten the card internals and shrink the serif section headers.
 * ========================================================================== */

/* Left alert cards: smaller type + padding, status never wraps. */
.sa-alert-aside .alert-card { padding: 0.4rem 0.55rem !important; font-size: 0.8rem; }
/* The .gap-2 / .sa-card-status / .small rules that lived here are gone with the
   flex-wrap cluster they were propping up (#253 item 1); the card's slots now carry
   their own sizes. */
/* Keep the card's own Timeline square a touch smaller than the panel ones. */
.sa-alert-aside .sa-iconbtn { width: 1.7rem; height: 1.7rem; }
.sa-alert-aside .sa-iconbtn i { font-size: 0.85rem; }

/* (The .wearable-section / .card-header size step-downs that sat here went with the
   elements themselves — see the note above .sa-subsec.) */

/* ---- Responsive step-downs: scale the identity line + paddings as the column
   narrows on smaller laptops, so the single-row header keeps fitting. -------- */
@media (max-width: 1399.98px) {
    .sa-result .sa-idchip .v { font-size: 0.74rem; }
}
@media (max-width: 1199.98px) {
    /* Steps the strip and the pane down together — see --sa-pane-pad above. */
    .sa-result { --sa-pane-pad: 0.7rem; }
    .sa-result .modern-tab-content { padding-top: 0.7rem; padding-bottom: 0.85rem; }
    .sa-result .sa-tabhead-title { gap: 0.25rem 0.3rem; }
    .sa-result .sa-idchip { padding: 0.08rem 0.38rem; }
    .sa-result .sa-idchip .v { font-size: 0.7rem; }
    .sa-result .sa-idchip .k { font-size: 0.52rem; }
    .sa-iconbtn { width: 1.8rem; height: 1.8rem; }
    .sa-stepnav .sa-iconbtn { width: 1.7rem; height: 1.7rem; }
}

/* ============================================================================
 *  List tables — CSS-grid rewrite (Trackings / Tracking-details)
 * ----------------------------------------------------------------------------
 *  Replaces the Bootstrap 12-col .row layout that right-aligned the status into
 *  a wide column (huge empty left gutter) and starved the View column (col-2),
 *  pushing the #id action button outside the card. Now: Status / Lvl / Anomaly
 *  are fixed-width, and View flexes and carries a full-width action button so it
 *  fills the freed space instead of overflowing.
 * ========================================================================== */
/* One hairline rhythm, no gap: the rows are a TABLE (#253 item 5b). Each row used
   to carry a border, a radius, a shadow and its own 0.35rem gap, so a four-column
   table rendered as a stack of little cards — which costs height twice over, once
   in the padding and again in losing the aligned-column rhythm that makes a table
   scannable in the first place. */
.sa-result .sa-ltable { display: flex; flex-direction: column; gap: 0; }

.sa-result .sa-ltable-head,
.sa-result .sa-ltable-row {
    display: grid;
    align-items: center;
    column-gap: 0.55rem;
    /* The pane inherits a 36px line-height from the app shell, which was adding
       ~18px of leading to every row on top of its own padding — a bigger share of
       the section's height than any padding value in it (#253 item 5b). */
    line-height: 1.35;
}

.sa-result .sa-ltable-head {
    padding: 0 0.65rem 0.4rem;
    margin-bottom: 0.1rem;
    border-bottom: 1px solid var(--sa-hairline);
    font-size: 0.58rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--sa-text-faint);
}

.sa-result .sa-ltable-row {
    padding: 0.3rem 0.65rem;
    background: transparent;
    border: 0;
    border-bottom: 1px solid var(--sa-hairline);
    font-size: 0.82rem;
    transition: background 0.16s var(--sa-ease, ease), box-shadow 0.16s ease;
}
.sa-result .sa-ltable-row:last-child { border-bottom: 0; }
/* Hover and active are a background tint and an accent edge — no lift, no
   per-row shadow, so the rows keep one rhythm in every state. */
.sa-result .sa-ltable-row:hover {
    background: rgba(var(--sa-accent-rgb), 0.06);
}
.sa-result .sa-ltable-row.is-active {
    background: rgba(var(--sa-accent-rgb), 0.10);
    box-shadow: inset 2px 0 0 var(--sa-accent);
}

/* Cells */
.sa-result .sa-lt-status {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    min-width: 0;
    font-weight: 600;
    color: var(--sa-text);
    white-space: nowrap;
}
.sa-result .sa-lt-num {
    text-align: center;
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    color: var(--sa-text-dim);
}
.sa-result .sa-lt-date {
    min-width: 0;
    font-size: 0.74rem;
    color: var(--sa-text-dim);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.sa-result .sa-lt-c { text-align: center; }
.sa-result .sa-lt-e { text-align: right; }
.sa-result .sa-lt-view { display: flex; justify-content: flex-end; min-width: 0; }
.sa-result .sa-lt-view .sa-view-btn { flex-shrink: 0; }

/* Column templates: Status sized to the longest label ("Acknowledged"), Lvl &
   Anomaly tight, View takes the rest. */
/* Sized to the column it actually lives in. At 8.5rem + 4.25rem + 5rem the row was
   ~30px wider than the half-column that holds it, so every Open button hung off the
   right edge behind a scrollbar. The status words ("Escalated" is the longest) need
   ~6.5rem, and the two counts are single digits. */
.sa-result .sa-ltable--tracks .sa-ltable-head,
.sa-result .sa-ltable--tracks .sa-ltable-row {
    grid-template-columns: minmax(0, 1fr) 2.4rem 3.6rem minmax(3.4rem, 4rem);
}
/* The Open column held up to 7rem for a 3–4 character id, which truncated both
   timestamps once the rail took its width back (#253 item 1's col-md-4). The
   button needs ~3.4rem; the dates need ~4.7rem each and now get it, and the People
   column is sized to its own header rather than to the old "Anom." abbreviation. */
.sa-result .sa-ltable--details .sa-ltable-head,
.sa-result .sa-ltable--details .sa-ltable-row {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 2.9rem minmax(3.4rem, 4rem);
}

@media (max-width: 1199.98px) {
    .sa-result .sa-ltable--tracks .sa-ltable-head,
    .sa-result .sa-ltable--tracks .sa-ltable-row {
        grid-template-columns: 7rem 2.4rem 3.75rem minmax(4.5rem, 1fr);
        column-gap: 0.4rem;
    }
    .sa-result .sa-ltable-row { font-size: 0.78rem; padding: 0.35rem 0.5rem; }
}

/* ============================================================================
 *  "Location from reader" — wearable proximity cards
 * ----------------------------------------------------------------------------
 *  Replaces the 3-col table with a row card: subject on the left, a distance
 *  pill + compass dial grouped on the right. Reads at a glance and never
 *  overflows the narrow tracking-tab column.
 * ========================================================================== */
/* Same hairline rhythm as the snapshot table beside it — one row per person, not
   one card per person (#253 item 5b). */
.sa-result .sa-loclist { display: flex; flex-direction: column; gap: 0; }
.sa-result .sa-loccard {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.6rem;
    padding: 0.22rem 0.6rem;
    /* See .sa-ltable-row — same inherited 36px leading. */
    line-height: 1.35;
    background: transparent;
    border-bottom: 1px solid var(--sa-hairline);
    transition: background 0.16s var(--sa-ease, ease);
}
.sa-result .sa-loccard:last-child { border-bottom: 0; }
.sa-result .sa-loccard:hover {
    background: rgba(var(--sa-accent-rgb), 0.05);
}
.sa-result .sa-loccard-who { display: inline-flex; align-items: center; gap: 0.4rem; min-width: 0; }
.sa-result .sa-loccard-who i { color: var(--sa-accent); flex-shrink: 0; }
.sa-result .sa-loccard-metrics { display: inline-flex; align-items: center; gap: 0.7rem; flex-shrink: 0; }
.sa-result .sa-loc-dist {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.1rem 0.5rem;
    border-radius: 999px;
    background: var(--sa-surface-sunken);
    border: 1px solid var(--sa-hairline);
    font-family: var(--sa-mono);
    font-size: 0.76rem;
    font-weight: 600;
    color: var(--sa-text-dim);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.sa-result .sa-loc-dist i { color: var(--sa-accent); font-size: 0.9em; }

/* ============================================================================
 *  "Neighborhood information" — per-subject neighbour cards
 * ----------------------------------------------------------------------------
 *  Each stressed wearable becomes a card with its id as the header and its
 *  grouped neighbours (Same class / Other classes / SPOCs) full-width below,
 *  instead of being squeezed into a centred two-column table cell.
 * ========================================================================== */
.sa-result .sa-nbrlist { display: flex; flex-direction: column; gap: 0.5rem; }
.sa-result .sa-nbrcard {
    background: var(--sa-surface);
    border: 1px solid var(--sa-hairline);
    border-radius: var(--sa-radius-sm);
    box-shadow: var(--sa-shadow-sm);
    overflow: hidden;
}
.sa-result .sa-nbrcard-subject {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.4rem 0.65rem;
    background: var(--sa-surface-sunken);
    border-bottom: 1px solid var(--sa-hairline);
    font-weight: 700;
    font-size: 0.82rem;
    color: var(--sa-text);
}
.sa-result .sa-nbrcard-subject i { color: var(--sa-red); flex-shrink: 0; }
.sa-result .sa-nbrcard-body { padding: 0.55rem 0.65rem; }
.sa-result .sa-nbr-empty { font-size: 0.78rem; font-style: italic; color: var(--sa-text-faint); }
/* The reused timeline neighbour groups should fill the card width here. */
.sa-result .sa-nbrcard-body .sa-tl-neighbours { margin: 0; }

/* ============================================================================
   ALERT-LIST ENERGY PASS (#116)
   Zone colour stays the card's identity axis, but moved onto the --sa-* tokens
   (was raw Bootstrap hex in site.css) and expressed as a left accent rail so it
   reads as intentional, not a flat outline. Severity/status get their own
   dedicated, colourful signals on top so the screen carries urgency at a glance.
   ========================================================================== */

/* ---- Zone rail: tokenised replacement for site.css .alert-card.red/.blue/.green.
   Higher specificity (.sa-alert-aside prefix) so these win over site.css, and a
   left rail + soft hover wash instead of a full loud Bootstrap-hex border.

   The zone KEEPS the rail (owner decision, 2026-07-29). #135 proposed handing it to
   severity; it is not being taken — the zone is a real fact about where the alert
   happened and the colour coding is read that way by the people using this screen.
   What #135 does change is that the zone no longer says what KIND of alert it is;
   that is the category chip below, and the two are separate facts. ---- */
.sa-alert-aside .alert-card {
    border-left: 3px solid var(--sa-slate) !important;   /* fallback / unknown zone */
}
.sa-alert-aside .alert-card.red    { border-color: var(--sa-hairline) !important; border-left-color: var(--sa-red) !important; }
.sa-alert-aside .alert-card.blue   { border-color: var(--sa-hairline) !important; border-left-color: var(--sa-blue) !important; }
.sa-alert-aside .alert-card.green  { border-color: var(--sa-hairline) !important; border-left-color: var(--sa-green) !important; }
.sa-alert-aside .alert-card.amber,
.sa-alert-aside .alert-card.yellow { border-color: var(--sa-hairline) !important; border-left-color: var(--sa-amber) !important; }

.sa-alert-aside .alert-card.red:hover    { background: var(--sa-red-soft) !important; box-shadow: none; }
.sa-alert-aside .alert-card.blue:hover   { background: var(--sa-blue-soft) !important; box-shadow: none; }
.sa-alert-aside .alert-card.green:hover  { background: var(--sa-green-soft) !important; box-shadow: none; }
.sa-alert-aside .alert-card.amber:hover,
.sa-alert-aside .alert-card.yellow:hover { background: var(--sa-amber-soft) !important; box-shadow: none; }

/* ---- Category wash: the card's FILL says what kind of alert this is, while the
   rail above keeps saying where it happened. Two channels, two facts.

   Scanning a list of forty cards, the question "which of these are location
   alerts?" was answerable only by reading a chip on every row. A tint answers it
   without reading. It is kept at 5% (10% on dark) on purpose: enough to group
   cards that sit together, never enough to compete with the severity badge or to
   look like a state of its own.

   These come AFTER the zone hover rules deliberately. Hover used to wash the card
   in the ZONE colour, so a violet safety card turned red under the cursor and
   appeared to change category — the fill has one meaning and hover must deepen it,
   not replace it. Unclassified and Ops are absent from this block by design: no
   wash, because "we cannot say" must not be given a colour that looks like an
   answer. ---- */
.sa-alert-aside .alert-card.cat-safety   { background: var(--sa-cat-safety-wash) !important; }
.sa-alert-aside .alert-card.cat-location { background: var(--sa-cat-location-wash) !important; }

.sa-alert-aside .alert-card.cat-safety:hover   { background: var(--sa-cat-safety-wash-strong) !important; box-shadow: none; }
.sa-alert-aside .alert-card.cat-location:hover { background: var(--sa-cat-location-wash-strong) !important; box-shadow: none; }

/* The same wash on the timeline header, so opening an alert from a violet card lands
   on a violet header — one object, not two screens. Layered OVER the header's own
   vertical gradient rather than replacing it, so the surface keeps its depth. */
.sa-tlh.cat-safety {
    background:
        linear-gradient(180deg, var(--sa-cat-safety-wash), var(--sa-cat-safety-wash)),
        linear-gradient(180deg, var(--sa-surface-raised), var(--sa-surface));
}
.sa-tlh.cat-location {
    background:
        linear-gradient(180deg, var(--sa-cat-location-wash), var(--sa-cat-location-wash)),
        linear-gradient(180deg, var(--sa-surface-raised), var(--sa-surface));
}

/* ---- Category chip (#135): what KIND of event this was.

   The palette is NOMINAL and deliberately not the severity ramp — a location alert
   is not "worse" than a safety alert, it is a different kind of thing, and
   red/amber/green would say otherwise. Indigo / blue / slate, each carrying an icon
   and a word as well as a colour, so the distinction survives a colour-blind reader
   and a greyscale print.

   These tokens are declared on .sa-alert-aside and its siblings, NOT :root, so the
   chip must be mounted inside that subtree or it renders unstyled in both themes. */
/* The zone icon in the detail pane, on the --sa-* tokens rather than Bootstrap's
   text-danger / text-primary / text-success (#253 item 2, decision A). Same fact,
   same colour, wherever it is drawn. */
.sa-zone-ic--red     { color: var(--sa-red); }
.sa-zone-ic--blue    { color: var(--sa-blue); }
.sa-zone-ic--green   { color: var(--sa-green); }
.sa-zone-ic--unknown { color: var(--sa-slate); }

/* ============================================================================
   RAIL CARD — TWO LINES, ALWAYS (#253 item 1)
   ----------------------------------------------------------------------------
   The card used to wrap to three rows at 14–16": four items of equal weight in a
   flex-wrap cluster, ~200px of chips in ~180px of column. Wrapping was the
   symptom; the cause was that nothing aligned from card to card, so the rail was
   four paragraphs you re-read rather than one column you scan.

   Every slot below is fixed-width and tabular, and the order answers the triage
   question in the order it is asked: how bad · what kind · when · where in its
   lifecycle.
   ========================================================================== */
.sa-alert-aside .alert-card {
    position: relative;
    display: block;
    border-radius: var(--sa-radius-md);
}

/* The stretched hit target. Invisible, sits UNDER the content, and catches every
   click the content lets through — see .sa-card-body's pointer-events below. It
   carries the whole card's accessible name, so a screen reader gets one control
   with one sentence rather than a dozen unlabelled spans. */
.sa-alert-aside .sa-card-hit {
    position: absolute;
    inset: 0;
    width: 100%;
    padding: 0;
    border: 0;
    background: transparent;
    border-radius: inherit;
    cursor: pointer;
    z-index: 0;
}
.sa-alert-aside .sa-card-hit:focus-visible {
    outline: 2px solid var(--sa-accent);
    outline-offset: 2px;
}
.sa-alert-aside .sa-card-hit:disabled { cursor: progress; }

/* Content sits above the hit target but is transparent to the pointer, so a click
   anywhere on the card reaches the button beneath. The two exceptions opt back in:
   the category glyph, whose tooltip is where its word now lives, and the timeline
   button, which must stay a button. */
.sa-alert-aside .sa-card-body {
    position: relative;
    z-index: 1;
    pointer-events: none;
    display: flex;
    flex-direction: column;
    gap: 0.18rem;
    /* The app inherits a 36px line-height down to here, which was adding ~18px of
       leading to each of the two lines — the single largest remaining contributor
       to card height once the wrapping was fixed. Set explicitly rather than left
       to whatever an ancestor happens to declare. */
    line-height: 1.35;
}
.sa-alert-aside .sa-card-cat,
.sa-alert-aside .sa-card-actions { pointer-events: auto; }

.sa-alert-aside .sa-card-l1,
.sa-alert-aside .sa-card-l2 {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex-wrap: nowrap;
    min-width: 0;
}

/* A fixed gutter and tabular numerals, so #51, #248 and #1049 start and end on the
   same x down the whole rail. This is the change that turns the list into a column. */
.sa-alert-aside .sa-card-id {
    flex: 0 0 auto;
    min-width: 3.4rem;
    font-family: var(--sa-mono);
    font-variant-numeric: tabular-nums;
    font-size: 0.78rem;
    font-weight: 700;
    color: var(--sa-text);
    letter-spacing: -0.01em;
}

/* Category, as its glyph. Nominal hue — never the severity ramp: a location alert
   is not WORSE than a safety alert, it is a different kind of thing. */
.sa-alert-aside .sa-card-cat {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.05rem;
    line-height: 1;
    cursor: help;
}
.sa-alert-aside .sa-card-cat i { font-size: 0.86rem; }
.sa-alert-aside .sa-card-cat--safety i   { color: var(--sa-cat-safety); }
.sa-alert-aside .sa-card-cat--location i { color: var(--sa-cat-location); }
.sa-alert-aside .sa-card-cat--ops i      { color: var(--sa-slate); }
/* Unclassified keeps its deliberate absence of colour: an alert nobody classified
   is the one case where a hue would be a claim rather than a fact. */
.sa-alert-aside .sa-card-cat--unclassified i { color: var(--sa-text-faint); }

/* The severity tick. The slot keeps its width when the criticality is unknown and
   nothing is rendered — #116's "show nothing rather than guess" survives, and the
   column still lines up underneath. */
.sa-alert-aside .sa-card-sev {
    flex: 0 0 auto;
    min-width: 2.5rem;
    display: inline-flex;
    align-items: center;
    font-family: var(--sa-mono);
    font-variant-numeric: tabular-nums;
    font-size: 0.64rem;
    font-weight: 800;
    letter-spacing: 0.04em;
    line-height: 1.35;
}
.sa-alert-aside .sa-card-sev > span {
    display: inline-block;
    padding: 0.02rem 0.26rem;
    border-radius: var(--sa-radius-sm);
    cursor: help;
}
/* CRITICAL is the ONLY filled element on the card (#253 item 2, decision B). High
   sat on the same --sa-red and was told apart by its text alone; solid-against-flat
   is a difference that survives greyscale and colour-blindness, which a second red
   never did. */
.sa-alert-aside .sa-card-sev--critical > span {
    background: var(--sa-sev-crit-bg);
    color: var(--sa-sev-crit-ink);
}
.sa-alert-aside .sa-card-sev--high > span   { color: var(--sa-red); }
.sa-alert-aside .sa-card-sev--medium > span { color: var(--sa-amber); }
.sa-alert-aside .sa-card-sev--low > span    { color: var(--sa-green); }

/* When. Pushed to the right edge and tabular, so the timestamps form their own
   column against the ragged status text below them. */
.sa-alert-aside .sa-card-when {
    flex: 0 0 auto;
    margin-left: auto;
    font-family: var(--sa-mono);
    font-variant-numeric: tabular-nums;
    font-size: 0.68rem;
    color: var(--sa-text-dim);
    white-space: nowrap;
}

.sa-alert-aside .sa-card-actions {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    flex-shrink: 0;
}

/* The card's timeline button goes quiet IN THE RAIL ONLY.
   It is a filled teal gradient everywhere else, which was proportionate when it
   was one of two buttons on a busy three-row card. Now that the card itself is
   the primary target and this is the only button left, 49 identical bright teal
   squares running down the rail are the same "wallpaper" problem the severity
   badge had — the loudest thing in the column, on every row, saying nothing that
   distinguishes one alert from another. Quiet by default, full accent on hover
   and focus. The detail header's Timeline button is untouched: there it is one
   button on one screen, and it should look like the action it is. */
.sa-alert-aside .sa-timeline-btn {
    background: transparent;
    color: var(--sa-text-faint);
    border: 1px solid transparent;
    box-shadow: none;
}
.sa-alert-aside .sa-timeline-btn:hover,
.sa-alert-aside .sa-timeline-btn:focus-visible {
    background: var(--sa-accent-soft);
    border-color: rgba(var(--sa-accent-rgb), 0.45);
    color: var(--sa-accent-strong);
    box-shadow: none;
    transform: none;
    filter: none;
}
.sa-alert-aside .sa-card-busy {
    display: inline-flex;
    align-items: center;
    color: var(--sa-text-dim);
}
.sa-alert-aside .sa-card-busy .spinner-border { width: 0.75rem; height: 0.75rem; border-width: 0.12em; }

.sa-cat-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.24rem;
    padding: 0.05rem 0.4rem;
    border-radius: var(--sa-radius-sm);
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    line-height: 1.35;
    white-space: nowrap;
}
.sa-cat-chip i { font-size: 0.74rem; }

/* ---- WHY the alert fired (#135), on the detail pane and the timeline header.

   The chips around it are TAXONOMY — which severity, which category, which zone,
   each a soft tint of its family. This is the one element that is a SENTENCE, and
   the only one that tells a responder what they are walking into, so it is the one
   element that gets filled: brand-indigo plate, light ink, sentence case, no
   uppercase tracking. Solid against soft is a difference you cannot miss and one
   that survives greyscale and colour-blindness, which a seventh tint would not.
   It is also why the plate does not borrow any state colour: it is not a state. ---- */
.sa-idchip--cause,
.sa-tlh-cause {
    background: var(--sa-why-bg);
    border-color: transparent;
    color: var(--sa-why-ink);
}
.sa-idchip--cause .k { color: var(--sa-why-ink-dim); }

/* ---- The foot of the alert list (#239): where the list ends, and whether that is
   also where the ALERTS end. The button is quiet by default — it is a continuation,
   not something anyone came here to do — and the closing line quieter still. ---- */
.sa-alert-more {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    padding: 0.5rem 0 0.15rem;
}
.sa-alert-more-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.35rem;
    width: 100%;
    padding: 0.34rem 0.6rem;
    border: 1px dashed var(--sa-hairline-strong);
    border-radius: var(--sa-radius-sm);
    background: transparent;
    color: var(--sa-text-dim);
    font-size: 0.76rem;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.sa-alert-more-btn:hover:not(:disabled) {
    background: var(--sa-accent-soft);
    border-color: var(--sa-accent);
    border-style: solid;
    color: var(--sa-accent-strong);
}
.sa-alert-more-btn:focus-visible { outline: 2px solid var(--sa-accent); outline-offset: 2px; }
.sa-alert-more-btn:disabled { opacity: 0.7; cursor: default; }
.sa-alert-more-error {
    margin: 0;
    font-size: 0.72rem;
    line-height: 1.35;
    color: var(--sa-red);
}
.sa-alert-end {
    margin: 0.55rem 0 0.15rem;
    text-align: center;
    font-size: 0.72rem;
    color: var(--sa-text-faint);
}

/* Which population the severity ribbon is counting, shown only while the list is
   partial — beside the category chips, which count the whole result set, an
   unqualified severity tally is two numbers describing two different things. */
.sa-sev-scope {
    align-self: center;
    font-size: 0.68rem;
    font-style: italic;
    color: var(--sa-text-faint);
}
.sa-idchip--cause .v,
.sa-tlh-cause {
    text-transform: none;
    letter-spacing: 0;
    font-weight: 600;
    color: var(--sa-why-ink);
}
.sa-idchip--cause .v i,
.sa-tlh-cause i { color: var(--sa-why-ink); opacity: 0.85; }

.sa-tlh-cause {
    display: inline-flex;
    align-items: center;
    gap: 0.32rem;
    padding: 0.14rem 0.5rem;
    border-radius: var(--sa-radius-sm);
    font-size: 0.76rem;
    line-height: 1.3;
    white-space: nowrap;
}

/* ---- Category count chips: the summary above the list, and the category filter.
   Clicking one narrows the SEARCH (server-side), so these are real buttons — they
   change what the API returns, not what the page hides. ---- */
.sa-cat-ribbon {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    padding-bottom: 0.5rem;
    margin-bottom: 0.5rem;
}
.sa-cat-count {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.16rem 0.5rem;
    border: 1px solid var(--sa-hairline);
    border-radius: var(--sa-radius-sm);
    background: transparent;
    font-size: 0.72rem;
    font-weight: 600;
    line-height: 1.3;
    color: var(--sa-text-dim);
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.sa-cat-count:hover { border-color: var(--sa-hairline-strong); color: var(--sa-text); }
.sa-cat-count:focus-visible { outline: 2px solid var(--sa-accent); outline-offset: 2px; }
.sa-cat-count-n { font-weight: 800; font-size: 0.82rem; }

/* Same nominal palette as the per-card chip, so a chip and the cards it filters to
   are visibly the same thing. */
.sa-cat-count--safety   .sa-cat-count-n, .sa-cat-count--safety i   { color: var(--sa-cat-safety); }
.sa-cat-count--location .sa-cat-count-n, .sa-cat-count--location i { color: var(--sa-cat-location); }
.sa-cat-count--ops      .sa-cat-count-n, .sa-cat-count--ops i      { color: var(--sa-slate); }
.sa-cat-count--unclassified { border-style: dashed; }

/* The active filter has to be unmistakable: it is the reason the list is short. */
.sa-cat-count.is-active {
    color: var(--sa-text);
    border-color: currentColor;
    box-shadow: inset 0 0 0 1px currentColor;
}
.sa-cat-count--safety.is-active   { background: var(--sa-cat-safety-soft); border-color: var(--sa-cat-safety); }
.sa-cat-count--location.is-active { background: var(--sa-cat-location-soft); border-color: var(--sa-cat-location); }
.sa-cat-count--ops.is-active      { background: var(--sa-slate-soft);  border-color: var(--sa-slate); }
.sa-cat-count--unclassified.is-active {
    background: var(--sa-surface-sunken);
    border-color: var(--sa-hairline-strong);
    border-style: solid;
}
/* Chip and card wash share a hue, so a tinted card and the chip on it read as one
   statement rather than two coincidences. */
.sa-cat-chip--safety   { color: var(--sa-cat-safety);   background: var(--sa-cat-safety-soft); }
.sa-cat-chip--location { color: var(--sa-cat-location); background: var(--sa-cat-location-soft); }
/* Ops is expected to live in its own tables on the edge rather than arriving here, so it
   keeps the neutral treatment and never gets a wash. */
.sa-cat-chip--ops      { color: var(--sa-slate);        background: var(--sa-slate-soft); }
/* Unclassified reads as an open question, not a resolved state: dashed edge, muted
   fill, no colour of its own. It must never look like a calm answer. */
.sa-cat-chip--unclassified {
    color: var(--sa-text-dim);
    background: transparent;
    border: 1px dashed var(--sa-hairline-strong);
}

/* ---- Severity summary ribbon: colour-coded count chips above the list, echoing
   the dashboard's stat tiles. Instant colour + real triage info (how many of each
   severity are in view). Only chips with a non-zero count are rendered. ---- */
.sa-sev-ribbon {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    padding-bottom: 0.55rem;
    margin-bottom: 0.5rem;
    border-bottom: 1px solid var(--sa-hairline);
}
.sa-sev-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.16rem 0.5rem;
    border-radius: var(--sa-radius-sm);
    border: 1px solid transparent;
    font-family: var(--sa-sans);
    font-size: 0.72rem;
    font-weight: 600;
    line-height: 1.2;
    letter-spacing: 0.01em;
    white-space: nowrap;
}
.sa-sev-chip .sa-sev-chip-n {
    font-family: var(--sa-mono);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}
.sa-sev-chip--critical { color: var(--sa-red);   background: var(--sa-red-soft);   border-color: rgba(var(--sa-red-rgb), 0.30); }
.sa-sev-chip--high     { color: var(--sa-red);   background: var(--sa-red-soft);   border-color: rgba(var(--sa-red-rgb), 0.22); }
.sa-sev-chip--medium   { color: var(--sa-amber); background: var(--sa-amber-soft); border-color: rgba(var(--sa-amber-rgb), 0.28); }
.sa-sev-chip--low      { color: var(--sa-green); background: var(--sa-green-soft); border-color: rgba(var(--sa-green-rgb), 0.28); }
.sa-sev-chip--unknown  { color: var(--sa-slate); background: var(--sa-slate-soft); border-color: rgba(var(--sa-slate-rgb), 0.24); }
/* Critical gets a soft pulsing ring so a genuine emergency stands out in the ribbon. */
.sa-sev-chip--critical { box-shadow: 0 0 0 0 rgba(var(--sa-red-rgb), 0.5); animation: sa-sev-pulse 2.4s ease-in-out infinite; }
@keyframes sa-sev-pulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(var(--sa-red-rgb), 0.0); }
    50%      { box-shadow: 0 0 0 3px rgba(var(--sa-red-rgb), 0.18); }
}
@media (prefers-reduced-motion: reduce) { .sa-sev-chip--critical { animation: none; } }

/* The per-card severity badge from #116 is gone — it is the .sa-card-sev tick above
   now (#253 item 1, move 3). Its rules are deleted rather than left orphaned, so the
   next reader does not have to work out which of two severity treatments is live. */

/* ---- Status pill: the card's lifecycle state.
   ----------------------------------------------------------------------------
   NEUTRAL, NOT SEMANTIC (#253 item 2, decision C). This used to run the same
   red/amber/green ramp as the severity badge, two inches away in the same row —
   red meant "Critical" on the left and "Detected" on the right, amber meant both
   "Medium" and "In progress", green both "Low" and "Closed". Two unrelated axes,
   one palette. Severity is the triage signal, so it keeps the semantic ramp and
   status gives it up.

   What replaces the hue is an INK ramp, which is a real signal and cannot be
   confused with severity: an open alert is full-strength text, one being worked is
   dimmer, a closed one recedes. That ordering survives greyscale, and the state's
   own icon and word are still there to name it. ---- */
.sa-status-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.02rem 0.4rem;
    /* Rounded rect, not a 999px oval — house rule. */
    border-radius: var(--sa-radius-sm);
    border: 1px solid var(--sa-hairline-strong);
    background: transparent;
    font-size: 0.7rem;
    font-weight: 600;
    line-height: 1.4;
    white-space: nowrap;
}
/* RenderStatusIcon emits Bootstrap's text-danger/-success/-warning and a status-glow.
   Inside the pill they would smuggle the ramp straight back in, so the icon takes the
   pill's own ink and the glow is dropped. */
.sa-status-pill i {
    color: inherit !important;
    animation: none !important;
    text-shadow: none !important;
    filter: none !important;
}
.sa-status-pill--danger   { color: var(--sa-text); font-weight: 700; border-color: var(--sa-text-faint); }
.sa-status-pill--progress { color: var(--sa-text-dim); }
.sa-status-pill--done     { color: var(--sa-text-faint); }
.sa-status-pill--neutral  { color: var(--sa-text-faint); font-style: italic; }
