/*
 * RipTideMerTurtle management console — "Cyber Gold" theme.
 *
 * Dark-mode-only, hand-authored to reproduce the sibling project's High-Tech Security SaaS look
 * (larvitar_aio/website_design_rules.md) WITHOUT its delivery mechanism: no Tailwind, no CDN, no
 * Google Fonts, no build step. The system-font stack below is the whole typography dependency.
 * Every colour is a token here so the palette stays consistent and auditable.
 */

:root {
  /* Surfaces */
  --bg: #0f0f0f;
  --card-from: #0f0f0f;
  --card-to: #1a1a1a;
  --panel: #161616;
  --panel-deep: #101012; /* zinc-950-ish */
  --glass: rgba(255, 255, 255, 0.05);

  /* Accents */
  --gold: #ffd700;
  --gold-2: #f2c500;
  --gold-soft: rgba(255, 215, 0, 0.14);

  /* Text — ticket 032 (console legibility): --ink-dim already clears WCAG 2.2 AA's 4.5:1 normal-
     text minimum against every surface here (~7.5:1 on --bg). The original --ink-muted
     (zinc-500 #71717a) did not (~4.0:1 on --bg, worse on the lighter end of --card-to) — raised
     to a lighter zinc-ish gray that clears 4.5:1 against --bg through --card-to's #1a1a1a (~5.4:1
     at the lightest surface). It happens to land near --st-unknown/--rstatus-unknown (#8b8b93) —
     coincidence, not a merge: those are status-hue tokens always paired with a border + text
     label, --ink-muted is a foreground-only text tone, and nothing here renders one on top of the
     other. --ink-muted is still the console's lowest-contrast text tone — timestamps, evidence
     values, and error/alert copy use --ink-dim (or a dedicated alert color) instead; see the
     per-selector notes below where that promotion was made. */
  --ink: #f4f4f5; /* zinc-100 */
  --ink-body: #d4d4d8; /* zinc-300 */
  --ink-dim: #a1a1aa; /* zinc-400 */
  --ink-muted: #8f8f97; /* raised from zinc-500 #71717a for AA contrast — see note above */

  /* Borders */
  --border: rgba(255, 255, 255, 0.07);
  --border-strong: rgba(255, 255, 255, 0.11);
  --border-gold: rgba(255, 215, 0, 0.26);

  /* Status hues — a generic OK/info/caution/danger/unknown "traffic light" palette used across the
     dashboard for states that are NOT a captured HTTP response code: role/active-state badges,
     bait-coverage hit/untouched, audit severity, scenario runtime state, a validated hackback
     replay. Each is always paired with a text label, never colour alone. */
  --st-2xx: #3fb950;
  --st-3xx: #58a6ff;
  --st-4xx: #e3b341;
  --st-5xx: #f85149;
  --st-unknown: #8b8b93;

  /* Captured HTTP response-status-class hues (ticket 20260831230223) — Live/Detail/War room/
     Sessions row pills and the Overview/War room status-mix breakdown, via dom.js's shared
     STATUS_COLORS/statusPill() and charts.js's statusBreakdown(). Deliberately its OWN palette,
     not --st-* above: a captured status code is protocol telemetry about what the decoy answered
     back, never a verdict on defender/attacker outcome, so 2xx gets a neutral steel tone instead
     of borrowing --st-2xx's green "success" meaning. 3xx/4xx/5xx/unknown keep the same hues as
     --st-* since "redirect"/"client error"/"server error" read as literal, truthful protocol
     descriptions either way — only the 2xx/green pairing was the mislabel.
     Reserve actual green (--health-ok below) for genuine system/collector health and a completed
     defensive-positive event; never derive it from a captured status code alone. */
  --rstatus-2xx: #7c93a8;
  --rstatus-3xx: #58a6ff;
  --rstatus-4xx: #e3b341;
  --rstatus-5xx: #f85149;
  --rstatus-unknown: #8b8b93;

  /* Reserved exclusively for genuine system/collector health (the console API reachable) and a
     completed defensive-positive event (e.g. a validated hackback callback) — never derived from a
     captured HTTP status code alone. */
  --health-ok: #3fb950;
  --health-down: #f85149;

  /* Type */
  --font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, Helvetica, Arial,
    sans-serif;
  --font-mono: "SF Mono", "JetBrains Mono", "Fira Code", ui-monospace, Menlo, Monaco,
    "Cascadia Code", Consolas, monospace;

  /* Type scale (ticket 032 — console legibility). The console shipped with ~25 near-duplicate
     magic font-size values between 0.65rem and 0.95rem, several under 13px — fine on a
     developer's laptop, illegible across a table or on a meeting-room projector. This scale is
     the documented minimum: --fs-2xs (13px) is the floor for every informational text context —
     metadata, hints, table headers/cells, pills, form labels, timestamps, footers. Nothing that
     conveys information should render smaller than that. The one exception is genuinely
     decorative glyphs that carry no information of their own (e.g. the .feed-card__chevron /
     .warroom-journey__node connector arrows, which duplicate what DOM order already conveys, and
     the .hero__mark-text wordmark SVG, owned by a separate ticket) — those stay off this scale.
     Fluid clamp()-based hero/KPI/ticker display text is also exempt: its minimum edge already
     sits above --fs-2xs, so snapping it to a fixed step would only reduce how it scales. */
  --fs-2xs: 0.8125rem; /* 13px — floor: metadata, hints, table headers/cells, pills, timestamps */
  --fs-xs: 0.875rem; /* 14px — secondary UI text: dense labels, small buttons, form hints */
  --fs-sm: 0.9375rem; /* 15px — body copy default (matches body's base size below) */
  --fs-base: 1rem; /* 16px — emphasized body, card/service identifiers */
  --fs-md: 1.125rem; /* 18px — subheadings, panel/section/dialog titles */
  --fs-lg: 1.375rem; /* 22px — larger headings (login title, War room title) */
  --fs-xl: 2rem; /* 32px — non-fluid executive-display numbers (Overview stat tiles) */

  --shadow-gold: 0 10px 30px -10px rgba(255, 215, 0, 0.7);
  --radius: 1rem;
  --radius-lg: 1.4rem;
  --maxw: 80rem;

  /* Motion system (ticket 033) — one shared duration/easing vocabulary instead of ad-hoc per-rule
     values scattered across this file, particles.js, and view-specific code (live.js/warroom.js).
     Every animation/transition built from these is CSS-only, so the site-wide
     `@media (prefers-reduced-motion: reduce)` rule further down (a universal `*` selector) already
     collapses it to an instant final state — nothing that only uses these tokens needs its own
     reduced-motion override. */
  --dur-fast: 0.12s; /* hover/press micro-feedback */
  --dur-base: 0.18s; /* route/menu/sheet entrances, toast, filter-applied feedback */
  --dur-slow: 0.45s; /* card/panel reveal sequencing */
  --dur-emphasis: 1.2s; /* finite value-change / live-evidence arrival emphasis — never a loop */
  --ease-standard: cubic-bezier(0.4, 0, 0.2, 1); /* general-purpose transitions */
  --ease-emphasized: cubic-bezier(0.16, 1, 0.3, 1); /* decelerated reveal — menus, sheets, routes */
}

* {
  box-sizing: border-box;
}

html {
  color-scheme: dark;
}

body {
  margin: 0;
  min-height: 100vh;
  background: var(--bg);
  color: var(--ink-body);
  font-family: var(--font-ui);
  font-size: var(--fs-sm); /* 15px, unchanged — now the scale's documented body tier */
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden; /* the page never scrolls sideways; wide tables scroll inside their own box */
}

a {
  color: var(--gold);
  text-decoration: none;
}

h1,
h2,
h3 {
  color: var(--ink);
  letter-spacing: -0.02em;
  font-weight: 600;
  margin: 0;
}

::selection {
  background: var(--gold-soft);
  color: var(--ink);
}

:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
  border-radius: 4px;
}

/* ---- Ambient particle field ------------------------------------------------------------- */

#particle-field {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
  opacity: 0.55;
}

/* ---- Navbar ----------------------------------------------------------------------------- */

.navbar {
  position: sticky;
  top: 0;
  z-index: 30;
  background: rgba(15, 15, 15, 0.72);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border-bottom: 1px solid var(--border);
}

.navbar__inner {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 0.7rem 1.5rem;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  row-gap: 0.5rem;
  gap: 1rem;
}

.brand {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  color: var(--ink);
  font-weight: 600;
  letter-spacing: -0.02em;
}

.brand__mark {
  width: 22px;
  height: 22px;
  border-radius: 7px;
  background: radial-gradient(circle at 30% 30%, var(--gold), var(--gold-2));
  box-shadow: 0 0 14px -2px rgba(255, 215, 0, 0.75);
}

.brand__accent {
  color: var(--gold);
}

.brand__tag {
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--ink-muted);
  border: 1px solid var(--border);
  border-radius: 9999px;
  padding: 0.1rem 0.5rem;
}

.navbar__nav {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin-left: auto;
}

.nav-primary {
  display: none;
  align-items: center;
  gap: 0.35rem;
}

/* Same author-`display`-beats-UA-`[hidden]` gap as `.nav-link[hidden]` below — without this,
   the desktop-nav breakpoint's `display: flex` (equal specificity, later in source) would
   keep a signed-out `.nav-primary` visible on wide layouts (ticket 003). */
.nav-primary[hidden] {
  display: none;
}

.nav-more {
  position: relative;
}

.nav-trigger {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.4rem 0.8rem;
  border-radius: 9999px;
  color: var(--ink-dim);
  font-size: var(--fs-sm);
  font-weight: 500;
  font-family: inherit;
  background: transparent;
  border: 1px solid transparent;
  cursor: pointer;
  transition:
    color 0.15s ease,
    background 0.15s ease,
    border-color 0.15s ease;
}

.nav-trigger:hover {
  color: var(--ink);
  background: var(--glass);
}

.nav-trigger.is-active {
  color: var(--gold);
  background: var(--gold-soft);
  border-color: var(--border-gold);
}

.nav-trigger__icon {
  width: 16px;
  height: 16px;
}

.nav-trigger__caret {
  width: 12px;
  height: 12px;
  transition: transform 0.15s ease;
}

.nav-trigger[aria-expanded="true"] .nav-trigger__caret {
  transform: rotate(180deg);
}

/* The overflow/navigation menu. A plain disclosure panel — no `role="menu"`, since the links
   inside keep normal tab order rather than implementing the ARIA menu widget's arrow-key contract. */
.nav-panel {
  position: absolute;
  top: calc(100% + 0.6rem);
  right: 0;
  z-index: 40;
  min-width: 15rem;
  max-width: calc(100vw - 2rem);
  max-height: calc(100vh - 5rem);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  background: var(--panel);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  padding: 0.6rem;
  box-shadow: 0 20px 45px -15px rgba(0, 0, 0, 0.6);
  /* Same display:none-resets-the-animation mechanism as `.detail-overlay`'s overlay-in above — every
     time app.js clears `hidden` this replays from the start, so opening the menu gets a restrained
     reveal instead of an instant pop-in (ticket 033), with no JS beyond the existing hidden toggle. */
  animation: sheet-in var(--dur-base) var(--ease-emphasized);
}

.nav-panel[hidden] {
  display: none;
}

@keyframes sheet-in {
  from {
    opacity: 0;
    transform: translateY(-6px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.nav-panel__group {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}

/* Same author-`display`-beats-UA-`[hidden]` gap as `.nav-link[hidden]` below — app.js's
   applyRoleNav() hides the whole "Admin"/"Account" group once every link inside it is hidden
   (signed out, or non-admin), so an empty heading with a divider above it doesn't linger. */
.nav-panel__group[hidden] {
  display: none;
}

.nav-panel__group + .nav-panel__group {
  padding-top: 0.5rem;
  border-top: 1px solid var(--border);
}

.nav-panel__label {
  margin: 0.2rem 0.6rem 0.25rem;
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--ink-muted);
}

.nav-panel__group.is-active > .nav-panel__label {
  color: var(--gold);
}

.nav-panel .nav-link {
  justify-content: flex-start;
  border-radius: 0.55rem;
  padding: 0.5rem 0.6rem;
}

/* The panel's own "Go to" group duplicates the primary destinations for narrow layouts, where
   `.nav-primary` is hidden and this menu is the only way to reach them; hidden again once the
   primary row itself is visible (see the desktop breakpoint below). */
.nav-panel [data-group="primary"] {
  display: flex;
}

.nav-link {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.4rem 0.8rem;
  border-radius: 9999px;
  color: var(--ink-dim);
  font-size: var(--fs-sm);
  font-weight: 500;
  border: 1px solid transparent;
  transition:
    color 0.15s ease,
    background 0.15s ease,
    border-color 0.15s ease;
}

.nav-link:hover {
  color: var(--ink);
  background: var(--glass);
}

.nav-link.is-active {
  color: var(--gold);
  background: var(--gold-soft);
  border-color: var(--border-gold);
}

/* The author `display:inline-flex` above beats the UA `[hidden] { display:none }` — a role-hidden
   nav link (ticket 026 — a viewer has no Credentials view) would otherwise stay visible. */
.nav-link[hidden] {
  display: none;
}

.nav-link__icon {
  width: 16px;
  height: 16px;
}

.api-status {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  color: var(--ink-dim);
  border: 1px solid var(--border);
  border-radius: 9999px;
  padding: 0.25rem 0.7rem;
  white-space: nowrap;
}

.api-status__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--ink-muted);
  box-shadow: 0 0 0 0 rgba(63, 185, 80, 0);
}

.api-status.is-ok .api-status__dot {
  background: var(--health-ok);
  animation: pulse-dot 2.4s ease-out infinite;
}

.api-status.is-down .api-status__dot {
  background: var(--health-down);
}

.api-status.is-ok {
  color: var(--ink-body);
}

.api-status.is-down {
  color: #fca5a5;
  border-color: rgba(248, 81, 73, 0.4);
}

/* LIVE/PAUSED/STALE chip (ticket 20260831235605) — Live's toolbar and War room's header. Same
   ring-pulse mechanic as .api-status above, recoloured gold per the ticket brief rather than
   reusing --health-ok's green (that hue stays reserved for genuine collector/API reachability,
   not this view-local poll-liveness signal). The pulse is an ordinary CSS animation, so it is
   already neutralized by the site-wide `@media (prefers-reduced-motion: reduce)` rule below —
   no separate JS reduced-motion handling needed, same as .row-new's flash. */
.live-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-dim);
  border: 1px solid var(--border);
  border-radius: 9999px;
  padding: 0.25rem 0.7rem;
  white-space: nowrap;
}

.live-chip__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--ink-muted);
  box-shadow: 0 0 0 0 rgba(255, 215, 0, 0);
}

.live-chip--live {
  color: var(--gold);
  border-color: var(--border-gold);
}

.live-chip--live .live-chip__dot {
  background: var(--gold);
  animation: live-chip-pulse 2.4s ease-out infinite;
}

.live-chip--paused .live-chip__dot {
  background: var(--ink-muted);
}

.live-chip--stale {
  color: var(--rstatus-4xx);
  border-color: rgba(227, 179, 65, 0.38);
}

.live-chip--stale .live-chip__dot {
  background: var(--rstatus-4xx);
}

@keyframes live-chip-pulse {
  0% {
    box-shadow: 0 0 0 0 rgba(255, 215, 0, 0.45);
  }
  70% {
    box-shadow: 0 0 0 7px rgba(255, 215, 0, 0);
  }
  100% {
    box-shadow: 0 0 0 0 rgba(255, 215, 0, 0);
  }
}

.auth-status {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  color: var(--ink-dim);
  white-space: nowrap;
}

.auth-status__user {
  color: var(--gold);
}

.auth-status__link {
  color: var(--ink-body);
  text-decoration: none;
  border-bottom: 1px solid transparent;
}

.auth-status__link:hover {
  color: var(--gold);
  border-bottom-color: var(--border-gold);
}

button.auth-status__logout {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  cursor: pointer;
}

.login-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
}

.login-panel {
  max-width: 28rem;
  margin: 1.5rem auto;
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
}

.login-title {
  margin: 0;
  font-size: var(--fs-lg);
  color: var(--ink);
}

.login-lede {
  margin: 0;
  color: var(--ink-body);
  font-size: var(--fs-sm);
  line-height: 1.45;
}

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

.login-label {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

.login-input {
  font-family: var(--font-ui);
  font-size: var(--fs-sm);
  text-transform: none;
  letter-spacing: normal;
  color: var(--ink);
  background: var(--panel-deep);
  border: 1px solid var(--border-strong);
  border-radius: 0.6rem;
  padding: 0.55rem 0.75rem;
}

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

/* Password field + show/hide toggle — flex-wrap so the toggle drops under the input rather than
   forcing horizontal overflow on a narrow phone viewport instead of clipping or scrolling. */
.login-password-field {
  display: flex;
  align-items: stretch;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.login-password-field .login-input {
  flex: 1 1 12rem;
  min-width: 0;
}

.login-toggle-visibility {
  flex: 0 0 auto;
}

.login-caps-warning {
  margin: 0;
  color: var(--gold);
  font-size: var(--fs-2xs);
}

.login-caps-warning[hidden] {
  display: none;
}

.login-mfa-account {
  margin: 0;
  color: var(--ink-muted);
  font-size: var(--fs-xs);
}

.login-mfa-switch-row {
  display: flex;
}

.login-error {
  color: #fca5a5;
  background: rgba(248, 81, 73, 0.12);
  border: 1px solid rgba(248, 81, 73, 0.35);
  border-radius: 0.6rem;
  padding: 0.55rem 0.75rem;
  font-size: var(--fs-xs);
}

.login-error[hidden] {
  display: none;
}

.login-submit {
  align-self: flex-start;
}

.login-status {
  margin: 0;
  min-height: 1.2em;
  color: var(--ink-dim);
  font-size: var(--fs-xs);
}

.login-token-note {
  margin: 0;
  color: var(--ink-muted);
  font-size: var(--fs-2xs);
  line-height: 1.4;
}

/* TOTP MFA enrollment (ticket 028) — the QR is inline SVG geometry, not a bitmap, so it stays
   crisp at any size; the surrounding card is wider than the default .login-panel cap. */
.mfa-card-body,
.mfa-enroll {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
}

.mfa-qr {
  background: #ffffff;
  padding: 0.6rem;
  border-radius: 4px;
  align-self: flex-start;
}

.mfa-backup-codes {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
  gap: 0.4rem;
  margin: 0;
  padding: 0.75rem 1rem;
  list-style: none;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--ink);
  background: var(--panel-deep);
  border: 1px solid var(--border-gold);
  border-radius: 4px;
}

@keyframes pulse-dot {
  0% {
    box-shadow: 0 0 0 0 rgba(63, 185, 80, 0.45);
  }
  70% {
    box-shadow: 0 0 0 7px rgba(63, 185, 80, 0);
  }
  100% {
    box-shadow: 0 0 0 0 rgba(63, 185, 80, 0);
  }
}

/* ---- Capture context strip (ticket 20260831230239) --------------------------------------- */
/* Always-available evidence-context surface: capture source/instance identity, last captured
   exchange, and freshness (js/capture_context.js). Sits between the navbar and the hero, hidden
   entirely under body.is-kiosk (War room renders its own compact .warroom-evidence chip there
   instead — see the "Kiosk" rule below) and while signed out (app.js toggles [hidden] alongside
   the rest of the operator chrome). Rows reuse the generic .pill.status-2xx/3xx/4xx/unknown
   "traffic light" palette — see dom.js's STATUS_LABELS comment for why that palette, not the
   captured-response rstatus-* one, is the right fit for a non-HTTP-status state. */
.capture-context {
  position: relative;
  z-index: 1;
  max-width: var(--maxw);
  margin: 0.7rem auto 0;
  padding: 0 1.5rem;
}

.capture-context[hidden] {
  display: none;
}

.capture-context__rows {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem 1.6rem;
  list-style: none;
  margin: 0;
  padding: 0.6rem 1rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: var(--fs-xs);
}

.capture-context__row {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  min-width: 0;
}

.capture-context__label {
  color: var(--ink-muted);
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  white-space: nowrap;
}

.capture-context__value {
  color: var(--ink-body);
  white-space: nowrap;
}

.capture-context__sub {
  color: var(--ink-dim);
  font-size: var(--fs-2xs);
}

/* ---- Executive workflow breadcrumb (ticket 026) ------------------------------------------ */
/* Orientation strip for the five promoted primary routes (js/exec_journey.js): posture -> evidence
   -> outcome -> board artifact. Sits under the capture-context strip, same width/gutter and card
   language; hidden entirely while signed out or on any route outside the evaluation path (app.js
   toggles it alongside the rest of the operator chrome), in War room's kiosk mode, and in print
   (see those rules below). */
.exec-journey {
  position: relative;
  z-index: 1;
  max-width: var(--maxw);
  margin: 0.7rem auto 0;
  padding: 0 1.5rem;
}

.exec-journey[hidden] {
  display: none;
}

.exec-journey__label {
  display: block;
  margin: 0 0 0.35rem;
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-muted);
}

.exec-journey__stages {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem 0.9rem;
  list-style: none;
  margin: 0;
  padding: 0.55rem 1rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: var(--fs-xs);
}

.exec-journey__stage {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
}

.exec-journey__stage:not(:last-child)::after {
  content: "\2192";
  margin-left: 0.9rem;
  color: var(--ink-muted);
}

.exec-journey__stage-label {
  color: var(--ink-muted);
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  white-space: nowrap;
}

.exec-journey__stage.is-active .exec-journey__stage-label {
  color: var(--gold);
}

.exec-journey__stage-routes {
  white-space: nowrap;
}

.exec-journey__dot {
  margin: 0 0.3rem;
  color: var(--ink-muted);
}

.exec-journey__link {
  color: var(--ink-dim);
  text-decoration: none;
  border-bottom: 1px solid transparent;
}

.exec-journey__link:hover {
  color: var(--ink-body);
  border-bottom-color: var(--border);
}

.exec-journey__link.is-active {
  color: var(--gold);
  border-bottom-color: var(--border-gold);
}

/* ---- Hero ------------------------------------------------------------------------------- */

.hero {
  position: relative;
  z-index: 1;
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 2.6rem 1.5rem 1.4rem;
}

.hero__mark {
  display: block;
  width: clamp(150px, 26vw, 300px);
  height: auto;
  margin: 0 0 0.6rem;
  user-select: none;
  pointer-events: none;
  filter: drop-shadow(0 0 14px rgba(255, 215, 0, 0.25));
  animation: hero-mark-glow 5s ease-in-out infinite;
}

.hero__mark-text {
  fill: var(--gold);
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 44px;
}

.hero__mark-wave {
  stroke: var(--gold-2);
  opacity: 0.55;
}

.hero__mark-dot {
  fill: var(--gold);
}

@keyframes hero-mark-glow {
  0%,
  100% {
    opacity: 0.85;
  }
  50% {
    opacity: 1;
  }
}

.hero__eyebrow {
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  letter-spacing: 0.22em;
  color: var(--gold-2);
  margin: 0 0 0.5rem;
}

.hero__title {
  font-size: clamp(1.7rem, 3.4vw, 2.5rem);
  line-height: 1.05;
}

.hero__subtitle {
  margin: 0.6rem 0 0;
  color: var(--ink-dim);
  max-width: 46rem;
  font-size: var(--fs-base);
}

/* Compact chrome (ticket 009): every inner data view (js/app.js's showView() sets this body class
   for every route except #/login and #/overview) collapses the full-height hero to a slim brand
   strip — roughly eyebrow height, wordmark shrunk and inlined with the title — so #view-root
   starts within the first ~120px instead of a third of the viewport down. Kiosk mode still fully
   hides `.hero` regardless of this class (`body.is-kiosk .hero` below wins on `display: none`). */
body.chrome-compact .hero {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: 0.85rem 1.5rem;
  border-bottom: 1px solid var(--border);
}

body.chrome-compact .hero__mark {
  width: 84px;
  margin: 0;
  animation: none;
  flex: none;
}

body.chrome-compact .hero__body {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.15rem 0.6rem;
}

body.chrome-compact .hero__eyebrow {
  margin: 0;
}

body.chrome-compact .hero__title {
  font-size: var(--fs-md);
  line-height: 1.2;
}

body.chrome-compact .hero__subtitle {
  display: none;
}

@media (max-width: 560px) {
  body.chrome-compact .hero {
    padding: 0.7rem 1.1rem;
    gap: 0.6rem;
  }
  body.chrome-compact .hero__mark {
    width: 60px;
  }
}

/* ---- View shell ------------------------------------------------------------------------- */

.view-root {
  position: relative;
  z-index: 1;
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 0.8rem 1.5rem 4rem;
  outline: none;
}

/* Route-content entrance (ticket 033: "routes replace the root instantly"). #view-root itself never
   toggles display (only its children are replaced), so unlike .detail-panel/.nav-panel above this
   can't rely on a display:none reset to replay — app.js's showView() re-adds this class after a
   forced reflow on every navigation. Opacity-only (no transform) so it never disturbs the sticky
   table headers a view may render inside #view-root. */
.view-root.view-enter {
  animation: view-enter var(--dur-slow) var(--ease-standard);
}

@keyframes view-enter {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* Card/chart reveal sequencing (ticket 039 — ticket 033's own Scope bullet, left undone there): a
   small, restrained staggered entrance for a grid of cards/charts that appears once at view mount
   (Overview's stat-grid/chart-grids, War room's ticker+panel row) — never a grid that gets rebuilt
   on a poll (kpiHost/journeySteps/panelBody already have their own value-change pulse/fade, and
   restaggering them every few seconds would be exactly the "run continuously" ticket 033's own
   Scope ruled out). Opacity-only, same reasoning as #view-root.view-enter above: a transform here
   would create a new containing block for any position:sticky descendant a grid's card might
   render (defensive — none do today, but the constraint is the same one, not a one-off). Plain
   nth-child delays rather than a JS-computed custom property — every grid this targets is small and
   fixed-size, so CSS alone is enough; the reduced-motion rule above zeroes animation-delay too, so
   a reduced-motion viewer never sits looking at an invisible tail item waiting out its delay. */
.stagger-reveal > * {
  animation: stagger-in var(--dur-slow) var(--ease-standard) both;
}

.stagger-reveal > *:nth-child(1) {
  animation-delay: 0s;
}

.stagger-reveal > *:nth-child(2) {
  animation-delay: var(--dur-fast);
}

.stagger-reveal > *:nth-child(3) {
  animation-delay: calc(var(--dur-fast) * 2);
}

.stagger-reveal > *:nth-child(4) {
  animation-delay: calc(var(--dur-fast) * 3);
}

.stagger-reveal > *:nth-child(5) {
  animation-delay: calc(var(--dur-fast) * 4);
}

.stagger-reveal > *:nth-child(n + 6) {
  animation-delay: calc(var(--dur-fast) * 5);
}

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

.section {
  margin-top: 2rem;
}

.section__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 0.9rem;
}

.section__title {
  font-size: var(--fs-md);
}

.section__hint {
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  color: var(--ink-muted);
}

/* Groups a .section__hint with a trailing chip (Live's LIVE/PAUSED/STALE chip) inside
   .section__head without disturbing that flex row's existing two-column layout elsewhere. */
.section__head-meta {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}

/* Day-picker control (ticket 027) — older/newer buttons flanking a <select>, mounted inside
   .section__head-meta on every day-scoped view. */
.day-picker {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.day-picker__nav {
  padding: 0.28rem 0.55rem;
  line-height: 1;
}

.day-picker__nav:disabled {
  opacity: 0.35;
  cursor: default;
  pointer-events: none;
}

.day-picker__select {
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  color: var(--ink);
  background: var(--panel-deep);
  border: 1px solid var(--border-strong);
  border-radius: 0.5rem;
  padding: 0.32rem 0.5rem;
}

.day-picker__select:focus {
  border-color: var(--border-gold);
  outline: none;
}

.section__note {
  margin: 0 0 0.9rem;
  color: var(--ink-body);
  font-size: var(--fs-xs);
  line-height: 1.45;
}

.card {
  background: linear-gradient(135deg, var(--card-from), var(--card-to));
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.1rem 1.2rem;
}

.glass {
  background: var(--glass);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border: 1px solid var(--border-strong);
}

/* ---- Stat tiles ------------------------------------------------------------------------- */

.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 0.9rem;
}

.stat-tile {
  background: linear-gradient(135deg, var(--card-from), var(--card-to));
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1rem 1.1rem;
  position: relative;
  overflow: hidden;
}

.stat-tile::after {
  /* a faint gold rule at the top edge, the sibling system's neon-hint device */
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: linear-gradient(90deg, transparent, var(--gold-soft), transparent);
}

.stat-tile__label {
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

.stat-tile__value {
  font-size: var(--fs-xl);
  font-weight: 600;
  letter-spacing: -0.03em;
  color: var(--ink);
  margin-top: 0.25rem;
  font-variant-numeric: tabular-nums;
}

.stat-tile__value.is-accent {
  color: var(--gold);
}

.stat-tile__sub {
  font-size: var(--fs-2xs);
  color: var(--ink-dim);
  margin-top: 0.2rem;
}

/* ---- Charts ----------------------------------------------------------------------------- */

/* minmax(0, ...) (not bare 1.4fr/1fr) — same convention .warroom-grid already uses — caps each
   track at its fr-share regardless of a descendant's intrinsic width. Without it, .chart svg's
   own min-width below (nested two levels down, inside a section() wrapper this file's chart-grid
   items actually are) would inflate the *track* itself rather than just needing to scroll inside
   its own card, pushing .chart-grid — and the page — wider than the viewport (ticket 032, a
   doublecheck-adjacent regression this fix's own first pass introduced and this pass corrects). */
.chart-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
  gap: 0.9rem;
}

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

/* .chart-grid's minmax(0, ...) tracks above already stop .chart svg's min-width from inflating
   the grid track itself; this is what actually scrolls that excess width within the card once the
   track is narrower than it — the same convention .table-scroll uses for wide tables. */
.chart {
  overflow-x: auto;
}

.chart svg {
  display: block;
  width: 100%;
  /* Matches the SVG's own 640-unit viewBox width (hourlyChart() in charts.js). Text sized inside
     an SVG — by attribute or by CSS — is scaled by the same viewBox-to-rendered-box transform as
     everything else in the document, so .chart-axis-label's 13px floor below only actually
     renders at 13px or more once the SVG itself is at least this wide; a narrower card scrolls
     (via the overflow-x above) rather than silently shrinking the axis labels under the floor
     (ticket 032 — the original 10px labels, and this fix's first pass at 13px, both shrank well
     under it once .chart-grid collapsed to one narrow column below the 860px breakpoint). */
  min-width: 40rem;
  height: auto;
  overflow: visible;
}

.chart-axis-label {
  fill: var(--ink-muted);
  font-size: 13px; /* --fs-2xs's px value, pinned literally rather than via var()/rem — see the
                       min-width comment above for why this SVG text needs a real floor guarantee
                       rather than a value that would itself be subject to the same scaling. */
  font-family: var(--font-mono);
}

.bar-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.55rem;
  margin-top: 0.3rem;
}

.bar-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 0.6rem 0.8rem;
}

.bar-item__label {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--ink-body);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.bar-item__count {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--ink-dim);
  font-variant-numeric: tabular-nums;
}

/* Bar widths are drawn as SVG <rect> geometry (not inline CSS `style` attributes carrying data),
   so they render under the strict `style-src 'self'` CSP with no inline-style allowance. */
.bar-item__bar {
  grid-column: 1 / -1;
  display: block;
  width: 100%;
  height: 8px;
  border-radius: 9999px;
  overflow: hidden;
}

/* ---- Status-mix meter ------------------------------------------------------------------- */

.status-meter-svg {
  display: block;
  width: 100%;
  height: 14px;
  border-radius: 9999px;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.04);
}

.status-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.8rem;
}

.status-breakdown__caption {
  margin-top: 0.5rem;
}

/* ---- Pills ------------------------------------------------------------------------------ */

.pill {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  border-radius: 9999px;
  padding: 0.25rem 0.75rem;
  font-size: var(--fs-2xs);
  font-family: var(--font-mono);
  border: 1px solid var(--border);
  color: var(--ink-dim);
  white-space: nowrap;
}

.pill__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
}

.status-2xx {
  color: var(--st-2xx);
  border-color: rgba(63, 185, 80, 0.35);
}
.status-3xx {
  color: var(--st-3xx);
  border-color: rgba(88, 166, 255, 0.35);
}
.status-4xx {
  color: var(--st-4xx);
  border-color: rgba(227, 179, 65, 0.38);
}
.status-5xx {
  color: var(--st-5xx);
  border-color: rgba(248, 81, 73, 0.4);
}
.status-unknown {
  color: var(--st-unknown);
  border-color: var(--border);
}

/* Captured HTTP response-status-class pills only (statusPill()/statusBreakdown() in dom.js /
   charts.js) — see the --rstatus-* comment above for why this is a separate palette from
   .status-2xx etc. above. */
.rstatus-2xx {
  color: var(--rstatus-2xx);
  border-color: rgba(124, 147, 168, 0.35);
}
.rstatus-3xx {
  color: var(--rstatus-3xx);
  border-color: rgba(88, 166, 255, 0.35);
}
.rstatus-4xx {
  color: var(--rstatus-4xx);
  border-color: rgba(227, 179, 65, 0.38);
}
.rstatus-5xx {
  color: var(--rstatus-5xx);
  border-color: rgba(248, 81, 73, 0.4);
}
.rstatus-unknown {
  color: var(--rstatus-unknown);
  border-color: var(--border);
}

/* Per-verb method styling (ticket 20260831235605) — Live's method column and War room's ticker,
   via dom.js's methodToken(). A request method is scannability, not a captured outcome, so it
   gets its own small tier rather than borrowing .rstatus-*'s palette above. */
.method-tok {
  font-family: var(--font-mono);
  font-weight: 600;
  letter-spacing: 0.02em;
}
.method-tok--read {
  color: var(--ink-dim);
}
.method-tok--write {
  color: var(--gold-2);
}
/* The captured method string itself — critical evidence (an unusual/malformed verb is exactly
   the kind of anomaly worth noticing, so it must never render dimmer than .method-tok--read's
   routine GET/HEAD) — never the lowest-contrast token (ticket 032, doublecheck finding). */
.method-tok--other {
  color: var(--ink-dim);
}

/* ---- Data tables ------------------------------------------------------------------------ */

.table-scroll {
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: linear-gradient(135deg, var(--card-from), var(--card-to));
}

table.data {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  min-width: 720px;
}

table.data thead th {
  text-align: left;
  padding: 0.6rem 0.8rem;
  color: var(--ink-muted);
  font-weight: 500;
  font-size: var(--fs-2xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border-bottom: 1px solid var(--border-strong);
  position: sticky;
  top: 0;
  background: var(--panel-deep);
  white-space: nowrap;
}

table.data tbody tr {
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  transition: background 0.12s ease;
}

table.data tbody tr:hover,
table.data tbody tr:focus-visible {
  background: var(--glass);
  outline: none;
}

table.data tbody tr:focus-visible {
  box-shadow: inset 3px 0 0 var(--gold);
}

table.data td {
  padding: 0.5rem 0.8rem;
  color: var(--ink-body);
  vertical-align: top;
  white-space: nowrap;
}

.col-path {
  max-width: 34ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The desktop-table sibling of .feed-card__ua — same captured, attacker-controlled User-Agent,
   so the same critical-evidence promotion applies (ticket 032, doublecheck finding). */
.col-ua {
  max-width: 28ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink-dim, var(--ink-body));
}

.row-new {
  animation: row-flash 1.6s ease-out;
}

@keyframes row-flash {
  0% {
    background: rgba(255, 215, 0, 0.24);
  }
  100% {
    background: transparent;
  }
}

/* ---- Live feed controls ----------------------------------------------------------------- */
/* ticket 20260831230339: three always-present rows (primary controls / active-filter chips /
   advanced-filter disclosure) plus a separate exports row, in place of the old single wrapped row
   — a deliberate desktop layout adjustment (still compact, just three short rows instead of one
   long-wrapping one) made so the mobile progressive-disclosure story below has a real seam to hang
   off, rather than fighting one flat row's source order at a breakpoint. */

.feed-controls-wrap {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  margin-bottom: 0.9rem;
}

.feed-primary-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
}

/* The author `display: flex` here beats the UA `[hidden] { display: none }` regardless of
   specificity (author origin always wins over UA origin — the same trick `.detail-overlay[hidden]`
   below documents), so every advanced input stays visible at every width despite the JS-managed
   `hidden` attribute defaulting true. Only the `max-width: 760px` block further down adds a
   `[hidden]` override, so the collapsed-by-default state only ever actually collapses anything on
   a narrow viewport. */
.feed-filters-adv {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
}

.feed-filters-adv input,
.feed-filters-adv select {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--ink);
  background: var(--panel-deep);
  border: 1px solid var(--border-strong);
  border-radius: 0.6rem;
  padding: 0.4rem 0.6rem;
}

.feed-filters-adv input::placeholder {
  color: var(--ink-muted);
}

.feed-filters-adv input:focus,
.feed-filters-adv select:focus {
  border-color: var(--border-gold);
  outline: none;
}

.feed-exports {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
}

.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-family: var(--font-ui);
  font-size: var(--fs-xs);
  font-weight: 500;
  color: var(--ink);
  background: var(--glass);
  border: 1px solid var(--border-strong);
  border-radius: 0.6rem;
  padding: 0.42rem 0.85rem;
  cursor: pointer;
  transition:
    transform 0.12s ease,
    box-shadow 0.12s ease,
    background 0.12s ease;
}

/* Same UA-vs-author-display gap as .nav-link[hidden] below — a role-hidden export button
   (ticket 026 — a viewer cannot export) would otherwise stay visible. */
.btn[hidden] {
  display: none;
}

.btn:hover {
  background: rgba(255, 255, 255, 0.08);
}

.btn--gold {
  color: #1a1500;
  background: linear-gradient(135deg, var(--gold), var(--gold-2));
  border-color: transparent;
  font-weight: 600;
}

.btn--gold:hover {
  box-shadow: var(--shadow-gold);
  transform: translateY(-1px);
}

.btn__icon {
  width: 15px;
  height: 15px;
}

/* Mobile-only affordances (ticket 20260831230339) — hidden by default, so a desktop viewport never
   sees them; the `max-width: 760px` block further down turns them back on. This rule must follow
   `.btn` above so `.feed-filters-toggle`'s `display: none` wins the same-specificity tie against
   `.btn`'s own `display: inline-flex` on the identical element. */
.feed-filters-toggle,
.feed-active-filters {
  display: none;
}

.feed-active-filter {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  /* Matches .overview-threat-chip's own padding (the closest existing precedent — a small
     removable/interactive pill, not a full-width row) rather than the tighter .pill default,
     per the ticket's "touch targets ... at least the existing console standard" requirement — a
     /doublecheck codex finding against the original, shorter padding here. */
  padding: 0.4rem 0.85rem;
  border-radius: 9999px;
  border: 1px solid var(--border-strong);
  background: var(--glass);
  color: var(--ink-dim);
  cursor: pointer;
}

.feed-active-filter:hover {
  border-color: var(--border-gold);
  color: var(--ink);
}

/* A small chip follows .overview-threat-chip's own convention — keep the global gold outline —
   rather than the full-width-row convention (.session-row/.feed-card etc.) of substituting a
   border-color change for it; a /doublecheck codex finding that the original border-color-only
   treatment here was weaker than this console's own standard for this specific kind of control. */
.feed-active-filter:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
  color: var(--ink);
}

.feed-active-filter__remove {
  color: var(--ink-muted);
}

.feed-count {
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  color: var(--ink-dim);
}

.feed-count strong {
  color: var(--gold);
  font-variant-numeric: tabular-nums;
}

/* ---- Live feed: mobile event cards ------------------------------------------------------- */
/* ticket 20260831230339 — the purpose-built card/list layout that replaces the eight-column table
   below the `max-width: 760px` breakpoint (see that block, further down, for the actual toggle).
   Hidden by default so a desktop viewport — which keeps the semantic `<table>` — never renders it. */

.feed-cards {
  display: none;
  flex-direction: column;
  gap: 0.6rem;
}

.feed-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 0.75rem 2.2rem 0.75rem 0.9rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: linear-gradient(135deg, var(--card-from), var(--card-to));
  cursor: pointer;
}

.feed-card:hover,
.feed-card:focus-visible {
  border-color: var(--border-gold);
  outline: none;
}

.feed-card.is-selected {
  box-shadow: inset 3px 0 0 var(--gold);
}

.feed-card--hackback {
  border-color: rgba(227, 179, 65, 0.5);
  box-shadow: 0 0 0 1px rgba(227, 179, 65, 0.18);
}

/* row-flash (see `.row-new` above) animates the `background` *shorthand*, which would null out
   this card's own gradient for the flash's duration and then snap it back — a visible glitch a
   `<tr>` never had (it has no background image to lose). box-shadow flashes instead, leaving the
   gradient untouched throughout. */
.feed-card.row-new {
  animation: feed-card-flash 1.6s ease-out;
}

@keyframes feed-card-flash {
  0% {
    box-shadow: inset 0 0 0 999px rgba(255, 215, 0, 0.24);
  }
  100% {
    box-shadow: inset 0 0 0 999px rgba(255, 215, 0, 0);
  }
}

.feed-card__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
}

/* A captured exchange timestamp — critical evidence, not chrome, so it never uses the console's
   lowest-contrast token (ticket 032). */
.feed-card__time {
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  color: var(--ink-dim);
}

.feed-card__req {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  min-width: 0;
}

.feed-card__req .method-tok {
  flex: none;
}

.feed-card__path {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.feed-card__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.15rem 0.7rem;
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  color: var(--ink-muted);
}

.feed-card__ip {
  color: var(--ink-dim);
}

/* The captured, attacker-controlled User-Agent — critical evidence, not chrome (ticket 032,
   doublecheck finding). */
.feed-card__ua {
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  color: var(--ink-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.feed-card__tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
}

/* aria-hidden in live.js — a purely decorative disclosure arrow, not informational text, so it
   stays off the --fs-* scale (see the :root type-scale comment's exceptions). */
.feed-card__chevron {
  position: absolute;
  right: 0.8rem;
  top: 50%;
  transform: translateY(-50%);
  color: var(--ink-muted);
  font-size: 1.1rem;
}

/* ---- Services --------------------------------------------------------------------------- */

.service-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 0.9rem;
}

.service-card {
  background: linear-gradient(135deg, var(--card-from), var(--card-to));
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.1rem 1.2rem;
}

.service-card__id {
  font-family: var(--font-mono);
  color: var(--gold);
  font-size: var(--fs-base);
}

.service-card__desc {
  color: var(--ink-dim);
  font-size: var(--fs-sm);
  margin: 0.3rem 0 0.8rem;
}

.service-card__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.service-card__share {
  margin-top: 0.9rem;
}

/* ---- Bait coverage ------------------------------------------------------------------------ */

.coverage-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 0.9rem;
  margin-top: 1rem;
}

.coverage-card {
  background: linear-gradient(135deg, var(--card-from), var(--card-to));
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.1rem 1.2rem;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}

.coverage-card:focus-visible,
.coverage-card.is-selected {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}

.coverage-card--hit {
  border-color: color-mix(in srgb, var(--gold) 55%, var(--border));
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--gold) 25%, transparent);
}

.coverage-card--untouched {
  opacity: 0.72;
}

.coverage-card__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.45rem;
}

.coverage-card__id {
  font-family: var(--font-mono);
  color: var(--gold);
  font-size: var(--fs-sm);
}

.coverage-card__label {
  font-size: var(--fs-sm);
  color: var(--ink);
}

.coverage-card__pattern {
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  word-break: break-all;
}

.coverage-card__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.65rem;
  font-size: var(--fs-xs);
  color: var(--ink-dim);
}

.coverage-card__talkers {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.45rem;
  font-size: var(--fs-2xs);
}

.coverage-card__talker {
  font-family: var(--font-mono);
  color: var(--gold);
  text-decoration: none;
  border-bottom: 1px dotted color-mix(in srgb, var(--gold) 50%, transparent);
}

.coverage-card__talker:hover {
  color: var(--ink);
}

.coverage-card__live {
  align-self: flex-start;
  margin-top: 0.25rem;
  text-decoration: none;
}

/* ---- ATT&CK matrix ------------------------------------------------------------------------ */

.attack-matrix {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
  gap: 0.85rem;
  margin-top: 1rem;
  align-items: start;
}

.attack-tactic {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  min-width: 0;
}

.attack-tactic__head {
  padding: 0.55rem 0.65rem;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: color-mix(in srgb, var(--card-from) 80%, transparent);
}

.attack-tactic__id {
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  color: var(--gold);
}

.attack-tactic__name {
  font-size: var(--fs-sm);
  color: var(--ink);
  font-weight: 600;
}

.attack-tactic__hint {
  margin-top: 0.15rem;
}

.attack-tactic__cells {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.attack-cell {
  display: block;
  text-decoration: none;
  color: inherit;
  background: linear-gradient(135deg, var(--card-from), var(--card-to));
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.75rem 0.85rem;
}

.attack-cell:focus-visible,
.attack-cell.is-selected {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}

.attack-cell--hit {
  border-color: color-mix(in srgb, var(--gold) 55%, var(--border));
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--gold) 25%, transparent);
}

.attack-cell--untouched {
  opacity: 0.62;
}

.attack-cell__id {
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  color: var(--gold);
}

.attack-cell__name {
  font-size: var(--fs-xs);
  color: var(--ink);
  margin-top: 0.15rem;
}

.attack-cell__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.55rem;
  margin-top: 0.45rem;
  font-size: var(--fs-2xs);
  color: var(--ink-dim);
}

.attack-cell__when {
  margin-top: 0.25rem;
  font-size: var(--fs-2xs);
}

/* ---- Deployment readiness panel (ticket 022, js/readiness.js) --------------------------- */
/* Coverage/ATT&CK's compact readiness-vs-engagement summary, sitting above the hit/unprobed
   grid — deliberately styled like .capture-context__rows above (same pill palette, same card
   chrome) so the "is this actually deployed and healthy" story reads consistently everywhere it
   appears, not as a bespoke widget. */
.readiness-panel {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin: 0.9rem 0 0;
  padding: 0.75rem 1rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.readiness-panel__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  font-size: var(--fs-xs);
}

/* The one row that renders only for a real, evidence-based gap (deployment proven live, zero
   services currently listening) — a subtle caution rail so it reads as distinct from the neutral
   deployment-state row above it, without shouting "alert". */
.readiness-panel__row--gap {
  padding-left: 0.6rem;
  border-left: 2px solid rgba(227, 179, 65, 0.5);
}

.readiness-panel__link {
  color: var(--gold-2);
  text-decoration: none;
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  white-space: nowrap;
}

.readiness-panel__link:hover {
  color: var(--gold);
}

/* ---- Deception effectiveness ------------------------------------------------------------ */

.effectiveness-kpi__why {
  line-height: 1.35;
  max-width: 28ch;
}

/* ---- Credential captures ---------------------------------------------------------------- */

.cred-warning {
  margin: 0.6rem 0 0.8rem;
  padding: 0.75rem 0.95rem;
  border: 1px solid rgba(232, 93, 93, 0.45);
  border-radius: var(--radius-md, 0.7rem);
  background: rgba(232, 93, 93, 0.1);
  color: #ffc9c9;
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  line-height: 1.45;
}

.cred-list {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  margin-top: 0.55rem;
}

.cred-row {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  padding: 0.7rem 0.85rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md, 0.7rem);
  background: linear-gradient(135deg, var(--card-from), var(--card-to));
  cursor: default;
}

.cred-row.is-selected,
.cred-row:focus {
  border-color: var(--border-gold);
  outline: none;
  box-shadow: 0 0 0 1px rgba(212, 175, 55, 0.25);
}

.cred-row__meta,
.cred-row__value {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.45rem 0.7rem;
  font-size: var(--fs-2xs);
  color: var(--ink-dim);
}

.cred-row__path,
.cred-value {
  font-family: var(--font-mono);
  color: var(--ink);
}

.cred-value {
  padding: 0.15rem 0.4rem;
  border-radius: 0.35rem;
  background: rgba(0, 0, 0, 0.35);
  max-width: 100%;
  overflow-wrap: anywhere;
}

.cred-label {
  color: var(--ink-muted);
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
}

.cred-type--basic { color: var(--gold); }
.cred-type--bearer { color: #9ecbff; }
.cred-type--form { color: #c5a3ff; }
.cred-type--api-key { color: #ffb86c; }

.btn--small {
  font-size: var(--fs-2xs);
  padding: 0.28rem 0.55rem;
  border-radius: 0.45rem;
}

/* ---- Admin user management (ticket 027) ------------------------------------------------- */

.users-create {
  margin-bottom: 1.2rem;
}

.users-create-form {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.75rem;
  margin-top: 0.6rem;
}

.users-create-form .login-label {
  min-width: 10rem;
}

.users-create-form input,
.users-create-form select {
  font-family: var(--font-ui);
  font-size: var(--fs-sm);
  color: var(--ink);
  background: var(--panel-deep);
  border: 1px solid var(--border-strong);
  border-radius: 0.6rem;
  padding: 0.5rem 0.7rem;
}

table.users-table tbody tr {
  cursor: default;
}

table.users-table select {
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  color: var(--ink);
  background: var(--panel-deep);
  border: 1px solid var(--border-strong);
  border-radius: 0.5rem;
  padding: 0.3rem 0.45rem;
}

.users-actions {
  display: flex;
  gap: 0.4rem;
  white-space: nowrap;
}

.users-pager {
  display: flex;
  gap: 0.5rem;
  margin-top: 0.7rem;
}

.users-generated {
  margin: 0 0 1.2rem;
  padding: 0.75rem 0.95rem;
  border: 1px solid rgba(63, 185, 80, 0.4);
  border-radius: var(--radius-md, 0.7rem);
  background: rgba(63, 185, 80, 0.1);
  color: var(--ink);
}

.users-generated__label {
  margin: 0 0 0.4rem;
  font-size: var(--fs-xs);
}

.users-generated__row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}

.users-generated__value {
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  padding: 0.3rem 0.55rem;
  background: var(--panel-deep);
  border: 1px solid var(--border-strong);
  border-radius: 0.5rem;
}

.users-generated__hint {
  margin: 0.5rem 0 0;
  font-size: var(--fs-2xs);
  color: var(--ink-muted);
}

/* ---- Admin audit trail (ticket 029) ------------------------------------------------------ */

.audit-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
}

.audit-filters input {
  font-family: var(--font-ui);
  font-size: var(--fs-sm);
  color: var(--ink);
  background: var(--panel-deep);
  border: 1px solid var(--border-strong);
  border-radius: 0.6rem;
  padding: 0.5rem 0.7rem;
  min-width: 12rem;
}

.audit-pager {
  display: flex;
  gap: 0.5rem;
  margin-top: 0.7rem;
}

.audit-detail {
  max-width: 22rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---- Live scenario control (ticket 001b) ------------------------------------------------ */

.scenario-lede {
  margin: 0 0 0.75rem;
  color: var(--ink-body);
  font-size: var(--fs-xs);
  line-height: 1.45;
}

.scenario-watcher {
  margin-top: 0.9rem;
}

.scenario-watcher__title {
  margin: 0 0 0.5rem;
  font-size: var(--fs-sm);
  color: var(--gold-2);
}

.scenario-ref-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
}

.scenario-ref-row select {
  font-family: var(--font-ui);
  font-size: var(--fs-sm);
  color: var(--ink);
  background: var(--panel-deep);
  border: 1px solid var(--border-strong);
  border-radius: 0.6rem;
  padding: 0.5rem 0.7rem;
  min-width: 18rem;
  max-width: 100%;
}

.scenario-inline-host:empty {
  display: none;
}

.scenario-editor {
  display: block;
  width: 100%;
  min-height: 22rem;
  margin-top: 0.75rem;
  resize: vertical;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  line-height: 1.5;
  color: var(--ink);
  background: var(--panel-deep);
  border: 1px solid var(--border-strong);
  border-radius: 0.6rem;
  padding: 0.75rem 0.9rem;
}

.scenario-editor:focus {
  outline: none;
  border-color: var(--border-gold);
  box-shadow: 0 0 0 2px var(--gold-soft);
}

.scenario-editor__caption {
  margin: 0.4rem 0 0;
  font-size: var(--fs-2xs);
  color: var(--ink-muted);
}

.scenario-editor__caption.is-dirty {
  color: var(--st-4xx);
}

.scenario-editor-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin-top: 0.75rem;
}

.scenario-alert {
  margin-top: 0.75rem;
  padding: 0.6rem 0.85rem;
  border-radius: 0.6rem;
  font-size: var(--fs-xs);
  line-height: 1.45;
}

.scenario-alert--ok {
  color: var(--health-ok);
  background: rgba(63, 185, 80, 0.1);
  border: 1px solid rgba(63, 185, 80, 0.35);
}

.scenario-alert--warn {
  color: var(--st-4xx);
  background: rgba(227, 179, 65, 0.1);
  border: 1px solid rgba(227, 179, 65, 0.35);
}

.scenario-alert--error {
  color: #fca5a5;
  background: rgba(248, 81, 73, 0.12);
  border: 1px solid rgba(248, 81, 73, 0.35);
}

.scenario-confirm {
  margin-top: 1.2rem;
  max-width: 34rem;
}

.scenario-confirm__title {
  margin: 0 0 0.6rem;
  font-size: var(--fs-md);
  color: var(--ink);
}

.scenario-confirm__hint {
  margin: 0.5rem 0 0;
  font-size: var(--fs-2xs);
  color: var(--ink-muted);
}

.scenario-confirm__actions {
  display: flex;
  gap: 0.6rem;
  margin-top: 1rem;
}

/* ---- Detail overlay --------------------------------------------------------------------- */

.detail-overlay {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  justify-content: flex-end;
  background: rgba(0, 0, 0, 0.62);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  /* Toggling the `hidden` attribute flips this element's `display` between `none` and `flex` (the
     rule right below), which resets a CSS animation and replays it from the start every time — the
     same mechanism `.detail-panel`'s own slide-in already relies on, so the backdrop now fades in
     alongside the panel with no extra JS (ticket 033: "menu/detail/sheet open-close motion"). */
  animation: overlay-in var(--dur-base) var(--ease-standard);
}

/* The author `display:flex` above beats the UA `[hidden] { display:none }`, so the overlay would
   sit visible over the whole page on load and after close. This scoped rule restores hiding. */
.detail-overlay[hidden] {
  display: none;
}

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

.detail-panel {
  width: min(760px, 100%);
  height: 100%;
  overflow-y: auto;
  background: linear-gradient(160deg, #141414, var(--panel-deep));
  border-left: 1px solid var(--border-gold);
  box-shadow: -30px 0 60px -30px rgba(0, 0, 0, 0.9);
  padding: 1.4rem 1.5rem 3rem;
  animation: slide-in var(--dur-base) ease-out;
}

@keyframes slide-in {
  from {
    transform: translateX(24px);
    opacity: 0.4;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

/* Sticky close/title region (ticket 20260831230339) — the drawer is a full-height scroll area
   (`.detail-panel`'s own `overflow-y: auto`) holding several forensic <details> blocks that can run
   long, so without this the title and close affordance scroll away entirely, same idiom as
   `table.data thead th`'s own `position: sticky; top: 0;` above. The negative margin cancels the
   ancestor's own top/side padding for this element only, and the matching padding puts it straight
   back — so the unscrolled position is pixel-identical to before, while the scrolled (stuck)
   position sits flush with the panel's edges instead of leaving a gap. Applies at every width, not
   just mobile — a full-width drawer is already what happens under `.detail-panel`'s own
   `width: min(760px, 100%)` on any viewport narrower than 760px. */
.detail-panel__head {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  /* Bottom stays margin-only (unchanged from before this ticket, so it still collapses with
     .detail-block's own margin-top exactly as it always did) — only top/sides get the negative-
     margin-then-repadding trick, since only those two needed cancelling to sit flush once stuck. */
  margin: -1.4rem -1.5rem 1rem;
  padding: 1.4rem 1.5rem 0;
  background: var(--panel-deep);
  border-bottom: 1px solid var(--border-strong);
}

/* /doublecheck (codex) finding: an attacker-controlled title/path with one long unbroken token (no
   spaces) could otherwise force this flex item past the panel's width and push .icon-btn (the close
   button) out past the viewport — min-width: 0 lets it shrink below its content's natural width so
   the children's own overflow handling (below) can actually take effect instead of the flex item's
   default `min-width: auto` winning first. */
.detail-panel__head > div:first-child {
  min-width: 0;
}

.detail-panel__title {
  font-size: var(--fs-md);
}

/* The captured, attacker-controlled request path — critical evidence, not chrome (ticket 032). */
.detail-panel__sub {
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  color: var(--ink-dim);
  margin-top: 0.25rem;
  /* The captured path is attacker-controlled and may be one long unbroken token — wrap it (never
     truncate; the path stays fully readable/copyable) rather than letting it force the flex row
     wider than the panel and push the close button out past the viewport. */
  overflow-wrap: anywhere;
}

.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 0.6rem;
  border: 1px solid var(--border-strong);
  background: var(--glass);
  color: var(--ink-dim);
  cursor: pointer;
}

.icon-btn:hover {
  color: var(--ink);
  border-color: var(--border-gold);
}

.icon-btn svg {
  width: 18px;
  height: 18px;
}

.detail-block {
  margin-top: 1.3rem;
}

.detail-block__title {
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gold-2);
  margin-bottom: 0.5rem;
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

/* Recognized-exploit-signature banner (ticket 015) — one bordered row per matched signature, the
   left/box-shadow tint reusing the same .status-2xx/3xx/4xx/5xx/unknown severity tone the pill
   inside it carries (see detail.js's severityTone()), same idiom as .session-row--hackback below. */
.detail-recognized {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.detail-recognized__row {
  padding: 0.7rem 0.9rem;
  border-radius: 0.6rem;
  border: 1px solid var(--border);
  background: var(--panel);
}

.detail-recognized__row.status-5xx {
  border-color: rgba(248, 81, 73, 0.4);
  box-shadow: 0 0 0 1px rgba(248, 81, 73, 0.15);
}
.detail-recognized__row.status-4xx {
  border-color: rgba(227, 179, 65, 0.38);
  box-shadow: 0 0 0 1px rgba(227, 179, 65, 0.12);
}
.detail-recognized__row.status-3xx {
  border-color: rgba(88, 166, 255, 0.35);
  box-shadow: 0 0 0 1px rgba(88, 166, 255, 0.12);
}

.detail-recognized__head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.6rem;
}

.detail-recognized__label {
  font-size: var(--fs-2xs);
  color: var(--ink-muted);
}

.detail-recognized__desc {
  margin: 0.45rem 0 0;
}

/* Attack story (ticket 018) — the plain-language summary + confidence tier + evidence + next-step
   actions that leads the panel, above the unchanged Summary block and the now-collapsed forensic
   disclosures below it (see .detail-block--forensic further down). */
.attack-story__head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin-bottom: 0.6rem;
}

.attack-story__head .detail-block__title {
  margin-bottom: 0;
}

.attack-story__lead {
  font-size: var(--fs-sm);
  color: var(--ink);
  margin: 0 0 0.4rem;
}

.attack-story__why {
  font-style: italic;
}

.attack-story__evidence {
  list-style: none;
  margin: 0.7rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.attack-story__evidence-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem;
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: 0.5rem;
  background: var(--panel);
}

/* Ticket 018's explicit "observed fact vs. heuristic interpretation" label — a fixed two-word
   vocabulary, always paired with text (never colour alone), mirroring the shared status-badge
   ".pill" "traffic light" convention's own colour-plus-label rule elsewhere in this file. */
.attack-story__evidence-kind {
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border-radius: 0.35rem;
  padding: 0.1rem 0.4rem;
  flex-shrink: 0;
}

.attack-story__evidence-kind--observed {
  color: var(--ink);
  background: var(--glass);
  border: 1px solid var(--border-strong);
}

.attack-story__evidence-kind--pattern {
  color: var(--gold);
  background: var(--gold-soft);
  border: 1px solid var(--border-gold);
}

.attack-story__evidence-label {
  color: var(--ink-body);
}

.attack-story__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.9rem;
}

/* Raw-evidence disclosures (ticket 018) — same content/layout as .detail-block, now behind a
   native <details>/<summary> collapsed by default so the Attack Story above is what a reader sees
   first; keyboard/focus/Escape handling is unaffected since <details> needs none of its own. */
.detail-block--forensic > summary {
  cursor: pointer;
  user-select: none;
}

.detail-block--forensic > summary::-webkit-details-marker {
  color: var(--gold-2);
}

.detail-block--forensic[open] > summary {
  margin-bottom: 0.5rem;
}

table.kv {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
}

table.kv td {
  padding: 0.32rem 0.5rem;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
  word-break: break-word;
}

table.kv td.kv-key {
  color: var(--ink-muted);
  width: 34%;
  white-space: nowrap;
}

table.kv td.kv-val {
  color: var(--ink-body);
}

pre.wire {
  margin: 0;
  padding: 0.85rem 1rem;
  background: var(--panel-deep);
  border: 1px solid var(--border);
  border-radius: 0.7rem;
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  line-height: 1.5;
  color: var(--ink-body);
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 340px;
  overflow: auto;
}

pre.wire.is-hex {
  white-space: pre;
  word-break: normal;
}

.wire-toolbar {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.5rem;
}

.toggle-group {
  display: inline-flex;
  border: 1px solid var(--border-strong);
  border-radius: 0.6rem;
  overflow: hidden;
}

.toggle-group button {
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  color: var(--ink-dim);
  background: transparent;
  border: none;
  padding: 0.3rem 0.7rem;
  cursor: pointer;
}

.toggle-group button.is-active {
  background: var(--gold-soft);
  color: var(--gold);
}

.body-note {
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  color: var(--ink-muted);
  margin: 0.4rem 0 0;
}

/* ---- Empty / loading / error states ----------------------------------------------------- */

.loading-state,
.empty-state,
.error-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: 0.8rem;
  padding: 3.5rem 1.5rem;
  color: var(--ink-dim);
}

.empty-state {
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-lg);
  background: linear-gradient(135deg, var(--card-from), var(--card-to));
}

.empty-state__badge {
  width: 54px;
  height: 54px;
  border-radius: 16px;
  display: grid;
  place-items: center;
  color: var(--gold);
  background: var(--gold-soft);
  border: 1px solid var(--border-gold);
}

.empty-state__badge svg {
  width: 26px;
  height: 26px;
}

.empty-state h2 {
  font-size: var(--fs-md);
}

.empty-state p,
.error-state p {
  max-width: 34rem;
  margin: 0;
}

/* Quiet-day inventory disclosure (ticket 021) — Coverage/ATT&CK fold the unchanged slot grid /
   technique matrix behind a native <details> when hitCount === 0, so the first viewport reads as
   the .empty-state lead-in above ("armed, unprobed") rather than a wall of untouched cards.
   Collapsed by default; expanding reveals the identical grid/matrix these views always rendered.
   Same idiom as detail.js's .detail-block--forensic, reused here inside .empty-state's own
   centered layout. */
.quiet-day-inventory {
  align-self: stretch;
  width: 100%;
  margin-top: 1.2rem;
  text-align: left;
}

.quiet-day-inventory > summary {
  cursor: pointer;
  user-select: none;
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  color: var(--gold-2);
}

.quiet-day-inventory[open] > summary {
  margin-bottom: 1rem;
}

.error-state {
  border: 1px solid rgba(248, 81, 73, 0.35);
  border-radius: var(--radius-lg);
  background: rgba(248, 81, 73, 0.06);
  color: #fca5a5;
}

.error-state code {
  font-family: var(--font-mono);
  color: var(--ink-body);
  background: var(--panel-deep);
  padding: 0.15rem 0.4rem;
  border-radius: 0.4rem;
}

.spinner {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 3px solid rgba(255, 215, 0, 0.18);
  border-top-color: var(--gold);
  animation: spin 0.8s linear infinite;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

/* Sized skeleton placeholders (ticket 033) — dom.js's skeletonState() swaps these in for a major
   view's first paint instead of a bare spinner, so the shape of the eventual content is visible
   immediately and nothing jumps once real data lands. The shimmer sweep is an `infinite` CSS
   animation, same idiom as `.spinner` above — the reduced-motion rule caps its iteration count to 1
   at a near-zero duration, which renders as the settled final frame rather than a moving sweep. */
.skeleton-state {
  padding: 1.5rem 0;
}

.skeleton-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 0.85rem;
}

.skeleton-block {
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: linear-gradient(
    100deg,
    var(--card-from) 30%,
    rgba(255, 215, 0, 0.08) 50%,
    var(--card-from) 70%
  );
  background-size: 200% 100%;
  animation: skeleton-sweep 1.6s ease-in-out infinite;
}

.skeleton-block--card {
  grid-column: 1 / -1;
  height: 8rem;
}

.skeleton-block--kpi {
  height: 7rem;
}

.skeleton-block--row {
  grid-column: 1 / -1;
  height: 2.2rem;
  border-radius: 0.6rem;
}

@keyframes skeleton-sweep {
  0% {
    background-position: 200% 0;
  }
  100% {
    background-position: -200% 0;
  }
}

/* War room's bare skeleton placeholders (ticket 039 — dom.js's skeletonBlocks()): kpiHost and
   journeySteps provide their own grid/flex layout, so their direct .skeleton-block children need
   just enough sizing to sit in that layout instead of skeletonState()'s own wrapping
   .skeleton-state/.skeleton-grid divs. kpiHost is `display: grid; grid-template-columns: repeat(4,
   minmax(0, 1fr))` — a bare child already auto-places into one of the 4 columns, so only height
   needs setting to roughly match .warroom-kpi's own min-height. journeySteps is `display: flex`,
   where a bare child has no intrinsic width — flex-size it the same way .warroom-journey__node
   itself is sized. Both blocks pass kind "kpi" (dom.js), whose own .skeleton-block--kpi rule sets
   `height: 7rem` — a plain `min-height` here would be inert once that height already exceeds it,
   so journeySteps' own rule below overrides with `height` instead: two classes beat
   .skeleton-block--kpi's one, so the cascade actually applies it. */
.warroom-kpis > .skeleton-block {
  min-height: 7.5rem;
}

.warroom-journey__steps > .skeleton-block {
  flex: 1 1 0;
  min-width: 0;
  height: 5.5rem;
}

.site-footer {
  position: relative;
  z-index: 1;
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 1.5rem;
  border-top: 1px solid var(--border);
  color: var(--ink-muted);
  font-size: var(--fs-2xs);
  font-family: var(--font-mono);
}

.toast {
  position: fixed;
  bottom: 1.4rem;
  left: 50%;
  transform: translateX(-50%) translateY(12px);
  z-index: 80;
  background: linear-gradient(135deg, var(--gold), var(--gold-2));
  color: #1a1500;
  font-weight: 600;
  font-size: var(--fs-xs);
  padding: 0.55rem 1.1rem;
  border-radius: 9999px;
  box-shadow: var(--shadow-gold);
  opacity: 0;
  pointer-events: none;
  transition:
    opacity var(--dur-base) var(--ease-standard),
    transform var(--dur-base) var(--ease-standard);
}

.toast.is-shown {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

/* Distinct error/warning toast (ticket 039) — same gold pill by default (success/neutral), red
   when dom.js's toast() is called with { kind: "error" } so a failure reads as one at a glance
   instead of blending into every other confirmation. #dc2626/#b91c1c rather than the
   --rstatus-5xx/.error-state #f85149 red — a doublecheck-review fix: white text over #f85149 is
   only ~3.35:1, below WCAG AA for this 14px text; both stops here hold >=4.5:1 against white
   (#dc2626 ~4.83:1, #b91c1c ~6.47:1). */
.toast.is-error {
  background: linear-gradient(135deg, #dc2626, #b91c1c);
  color: #fff;
  box-shadow: 0 10px 30px rgba(220, 38, 38, 0.35);
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  border: 0;
}

/* The inline-SVG icon sprite: kept out of flow (0×0) without an inline `style` attribute, which the
   CSP would block. */
.svg-sprite {
  position: absolute;
  width: 0;
  height: 0;
}

/* ---- Demo banner -------------------------------------------------------------------------- */

/* Persistent and unmissable, per the console-demo ticket: always rendered above everything else
   whenever GET /api/health reports demo:true, on every view, never dismissible. */
.demo-banner {
  position: relative;
  z-index: 40;
  display: block;
  width: 100%;
  text-align: center;
  padding: 0.5rem 1rem;
  background: linear-gradient(90deg, var(--gold), var(--gold-2));
  color: #1a1500;
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: var(--fs-2xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* ---- Guided demo tour (ticket 034) --------------------------------------------------------- */

/* Overview's demo-only entry card (js/views/overview.js's tourEntryCard()) — a plain .card, laid
   out as text-plus-button on wide viewports and stacked on narrow ones (390px). */
.demo-tour-cta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  border-color: var(--border-gold);
}

.demo-tour-cta__title {
  margin: 0 0 0.2rem;
}

.demo-tour-cta__body {
  flex: 1 1 320px;
}

/* A fixed, non-modal bottom bar (js/demo-tour.js) — deliberately not a full-screen overlay, so the
   view underneath (whatever route the current step navigated to) stays fully visible and
   interactive; it never traps focus and Tab reaches every other control on the page as normal.
   z-index sits above .detail-overlay (50, the "evidence" step opens it) but below .toast (80, whose
   brief overlap with the bar's own top edge is an accepted, transient cosmetic tradeoff). */
.demo-tour-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 70;
  max-height: 46vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  background: linear-gradient(180deg, var(--panel), var(--panel-deep));
  border-top: 1px solid var(--border-gold);
  box-shadow: 0 -18px 40px -20px rgba(0, 0, 0, 0.85);
  padding: 0.9rem 1.2rem 1.1rem;
  animation: demo-tour-in var(--dur-base) var(--ease-standard);
}

@keyframes demo-tour-in {
  from {
    transform: translateY(16px);
    opacity: 0;
  }
  to {
    transform: translateY(0);
    opacity: 1;
  }
}

.demo-tour-bar__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
}

.demo-tour-bar__progress {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  color: var(--ink-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.demo-tour-bar__title {
  margin: 0;
  font-size: var(--fs-md);
}

.demo-tour-bar__what {
  margin: 0;
  color: var(--ink-body);
}

.demo-tour-bar__talk {
  font-style: italic;
}

.demo-tour-bar__actions {
  display: flex;
  gap: 0.5rem;
  margin-top: 0.2rem;
}

@media (max-width: 560px) {
  .demo-tour-bar {
    max-height: 60vh;
    padding: 0.8rem 0.9rem 1rem;
  }

  .demo-tour-bar__actions {
    flex-wrap: wrap;
  }

  .demo-tour-bar__actions .btn {
    flex: 1 1 auto;
  }
}

/* ---- Filter presets ----------------------------------------------------------------------- */

.preset-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 0.7rem;
}

.preset-btn {
  font-size: var(--fs-2xs);
  padding: 0.32rem 0.7rem;
}

table.data tbody tr.is-selected {
  background: var(--gold-soft);
  box-shadow: inset 3px 0 0 var(--gold);
}

/* ---- Sessions ----------------------------------------------------------------------------- */

.session-list {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.session-row {
  background: linear-gradient(135deg, var(--card-from), var(--card-to));
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.8rem 1rem;
  cursor: pointer;
}

.session-row:hover,
.session-row:focus-visible {
  border-color: var(--border-gold);
  outline: none;
}

.session-row.is-selected {
  box-shadow: inset 3px 0 0 var(--gold);
}

.session-row--hackback {
  border-color: rgba(227, 179, 65, 0.5);
  box-shadow: 0 0 0 1px rgba(227, 179, 65, 0.18);
}

.agent-row {
  text-decoration: none;
  color: inherit;
  display: block;
}

.agent-row--high {
  border-color: rgba(227, 179, 65, 0.55);
  box-shadow: 0 0 0 1px rgba(227, 179, 65, 0.2);
}

.agent-score {
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  font-weight: 700;
  color: var(--gold);
  letter-spacing: 0.02em;
}

.agent-signal {
  font-size: var(--fs-2xs);
  padding: 0.15rem 0.55rem;
  max-width: 22rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.session-row__head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}

.session-row__ip {
  font-family: var(--font-mono);
  font-weight: 600;
  color: var(--ink);
}

.session-row__char {
  font-size: var(--fs-xs);
  color: var(--ink-dim);
}

.session-tag {
  font-size: var(--fs-2xs);
  padding: 0.15rem 0.55rem;
  color: var(--ink-muted);
}

/* first_ts_utc → last_ts_utc plus duration/count — critical timestamp evidence, not chrome
   (ticket 032, doublecheck finding). */
.session-row__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.9rem;
  margin-top: 0.45rem;
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  color: var(--ink-dim);
}

.session-row__dossier {
  margin: 0.4rem 0 0;
  font-family: var(--font-ui);
  font-size: var(--fs-xs);
  color: var(--ink-dim);
}

.session-row__expanded {
  margin: -0.2rem 0 0.4rem;
  padding: 0.9rem 1rem;
  border: 1px solid var(--border);
  border-top: none;
  border-radius: 0 0 var(--radius) var(--radius);
  background: var(--panel-deep);
}

.hackback-phases {
  margin-bottom: 0.9rem;
}

.hackback-phases__list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.5rem;
}

.timeline {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
}

.timeline-row {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.4rem 0.6rem;
  border-radius: 0.5rem;
  cursor: pointer;
}

.timeline-row:hover,
.timeline-row:focus-visible {
  background: var(--glass);
  outline: none;
}

/* A session-timeline timestamp — critical evidence, not chrome (ticket 032). */
.timeline-row__time {
  color: var(--ink-dim);
  min-width: 6.5ch;
}

/* ---- Agent detection evidence (ticket 020-20260831230419) --------------------------------- */
/* The confidence pill in a collapsed row and the expanded evidence card share the same
   .status-2xx/3xx/4xx/5xx "traffic light" tone convention as detail.js's attack-story badge (see
   agents.js's confidenceTone()) — high/moderate/low here, not attack-story's four-tier scale. */

.agent-confidence {
  font-size: var(--fs-2xs);
}

/* A prominent, specific "not attribution" callout — reused for the whole-view banner above the
   list and, in a slightly smaller size (see `.session-row__expanded-body .agent-disclaimer`
   below), inside every expanded candidate's evidence panel. Same amber/caution palette as
   .scenario-alert--warn, deliberately not the red error tone: this is a caveat, not a failure. */
.agent-disclaimer {
  margin: 0.5rem 0 0.9rem;
  padding: 0.7rem 0.95rem;
  border-radius: var(--radius-md, 0.7rem);
  border: 1px solid rgba(227, 179, 65, 0.35);
  background: rgba(227, 179, 65, 0.08);
  color: var(--ink-dim);
  font-size: var(--fs-xs);
  line-height: 1.5;
}

.session-row__expanded-body.agent-evidence-panel {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.agent-evidence-panel .agent-disclaimer {
  font-size: var(--fs-2xs);
  margin: 0.2rem 0 0.6rem;
}

/* The evidence panel's lazily-loaded request-cadence timeline — same .timeline/.timeline-row
   primitives Sessions' own expanded view uses (generic, not scoped to that file), just with a
   title + spacing matching .hackback-phases above it. */
.agent-timeline {
  margin-bottom: 0.9rem;
}

.agent-timeline .timeline {
  margin-top: 0.5rem;
}

.agent-evidence__head-row {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  flex-wrap: wrap;
}

.agent-evidence {
  list-style: none;
  margin: 0.3rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}

.agent-evidence__row {
  padding: 0.65rem 0.85rem;
  border: 1px solid var(--border);
  border-radius: 0.6rem;
  background: var(--panel);
}

.agent-evidence__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.6rem;
  flex-wrap: wrap;
}

.agent-evidence__label {
  font-weight: 600;
  color: var(--ink);
}

.agent-evidence__weight {
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  color: var(--gold);
  white-space: nowrap;
}

/* The evidence pointer (timestamp/path/exchange id) backing an agent-detection signal — critical
   evidence, not chrome (ticket 032). */
.agent-evidence__detail {
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  color: var(--ink-dim);
  margin: 0.35rem 0 0;
}

.agent-evidence__reference {
  font-family: var(--font-mono);
}

.agent-evidence__limitation {
  font-style: italic;
}

/* The no-candidate/low-data empty state's signal catalog — left-aligned rows inside the otherwise
   centered .empty-state, so a multi-sentence explanation stays readable. */
.agent-signal-catalog {
  list-style: none;
  margin: 0.8rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  max-width: 34rem;
  text-align: left;
}

.agent-signal-catalog__row {
  padding: 0.6rem 0.85rem;
  border: 1px solid var(--border);
  border-radius: 0.6rem;
  background: var(--panel);
}

/* ---- Responsive ------------------------------------------------------------------------- */

/* Desktop nav: Overview / Live feed / ATT&CK / Effectiveness / Briefing — the promoted evaluation
   path (ticket 026) — reachable as one-click pills; every other destination sits behind the "More"
   trigger. Below this width there is no room for five pills plus the trigger plus auth/API status
   without wrapping or clipping, so the whole destination list — primary included — collapses into
   that one menu instead (see .nav-panel [data-group="primary"] above, and the max-width: 1100px
   rule below that folds the primary pills back into the panel).

   The breakpoint is deliberately `80rem` — the exact value of `--maxw`, `.navbar__inner`'s own
   `max-width` — not the `1101px` closed ticket 001a originally chose for its four-pill layout (a
   doublecheck-review fix: 001a's `1101px` was an otherwise-arbitrary "does the four-pill row fit"
   number, and this ticket's fifth pill shrank the margin at 001a's own number enough that a
   realistic signed-in username — verified with "demo-analyst" — wrapped `.api-status` onto a
   second row anywhere from ~1101px up to ~1258px, a range neither the ticket's own 1440/1280/1024
   spot checks nor an earlier version of this fix's browser verification happened to land in,
   since 1280/1440 both sit *above* `--maxw` where `.navbar__inner`'s width is already capped and
   the row reliably fits). Matching the breakpoint to `--maxw` removes that gap by construction
   rather than by re-tuning padding numbers for today's content: once `.nav-primary` can show at
   all, the viewport is already `>= --maxw`, so `.navbar__inner`'s rendered content width is always
   exactly `--maxw` minus its own padding — never less — for every wider viewport too. (Media
   queries cannot read a custom property, so the two values are independently declared and must be
   kept equal by hand if `--maxw` ever changes.) Verified in a real browser at 1440/1280/1024 CSS
   px, signed in with a realistic-length username (no wrap/overflow at any), plus the narrowest
   width this sandbox's Chrome window allows (500 CSS px, floored well above the ticket's 390
   target but exercising the same flex-wrap-first, no-clipping mechanism). */
@media (min-width: 80rem) {
  .nav-primary {
    display: flex;
  }
  .nav-panel [data-group="primary"] {
    display: none;
  }
  /* ticket 026's fifth primary pill (up from four) still leaves less headroom than 001a's four-pill
     layout had — trading the decorative "CONSOLE" tag and a little pill padding for that room (on
     top of raising the breakpoint above) keeps the row calm and single-line (001a's inherited
     standard) with a comfortable margin, not just a break-even one, without touching the auth/API
     status content itself. */
  .brand__tag {
    display: none;
  }
  .nav-link,
  .nav-trigger {
    padding-left: 0.6rem;
    padding-right: 0.6rem;
  }
}

@media (max-width: 860px) {
  .chart-grid,
  .chart-grid--even {
    grid-template-columns: minmax(0, 1fr);
  }
  .brand__tag {
    display: none;
  }
}

@media (max-width: 560px) {
  .hero {
    padding-top: 1.8rem;
  }
}

/* ---- Live feed: mobile breakpoint ---------------------------------------------------------- */
/* ticket 20260831230339 — below 760px (a real phone; 768 CSS px, tablet portrait, keeps the desktop
   table exactly as before): the semantic table hides in favor of the card list built alongside it,
   and the six advanced-filter inputs collapse behind a toggle so the evidence list sits closer to
   the top of the screen. Chosen below 768 (not e.g. at it) deliberately, so a check at exactly
   768px still exercises "keep the semantic table at larger widths". */
@media (max-width: 760px) {
  .live-table-scroll {
    display: none;
  }

  .feed-cards {
    display: flex;
  }

  .feed-filters-toggle {
    display: inline-flex;
    margin-left: auto;
  }

  .feed-active-filters:not([hidden]) {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
  }

  .feed-filters-adv {
    flex-direction: column;
    align-items: stretch;
  }

  /* Higher specificity than the unconditional `.feed-filters-adv { display: flex }` default above
     — this is the one place the JS-managed `hidden` attribute (collapsed by default) actually takes
     effect. */
  .feed-filters-adv[hidden] {
    display: none;
  }

  .feed-exports {
    justify-content: flex-end;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    animation-delay: 0s !important;
    transition-duration: 0.001ms !important;
  }
}

/* Forced-colors / high-contrast mode (ticket 032): every status/selection cue in this file already
   pairs a colour with a border and a text label (never colour alone), which forced-colors mode
   generally preserves on its own. The one place that leans on `background` + `border-color` alone
   to mark "current" — the active nav pill, since its label text is identical whether active or
   not — gets an explicit outline here so it survives even where a UA flattens both to the same
   system colour pair. */
@media (forced-colors: active) {
  .nav-link.is-active {
    outline: 2px solid CanvasText;
    outline-offset: -3px;
  }
}

/* ---- War room / kiosk ---------------------------------------------------------------------- */

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

.warroom-toolbar__left,
.warroom-toolbar__right {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}

.warroom-title {
  font-size: var(--fs-lg);
}

.warroom-clock {
  font-family: var(--font-mono);
  font-size: var(--fs-md);
  color: var(--gold);
  letter-spacing: 0.04em;
  min-width: 11ch;
}

.warroom-exit-hint {
  font-size: var(--fs-2xs);
  color: var(--ink-muted);
}

.warroom-kpis {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0.85rem;
  margin-bottom: 1rem;
}

.warroom-kpi {
  background: linear-gradient(180deg, var(--card-from), var(--card-to));
  border: 1px solid var(--border-gold);
  border-radius: var(--radius-lg);
  padding: 1.1rem 1.25rem;
  box-shadow: var(--shadow-gold);
  min-height: 7.5rem;
}

/* KPI value-change emphasis (ticket 033: "KPI updates snap") — warroom.js's kpiTile() adds this
   class only on a poll where a value actually changed from the previous paint (never on first
   paint), and only a fresh DOM node is ever born with it (kpiHost.replaceChildren() rebuilds every
   tile each poll), so the animation always plays on insertion with no reflow trick needed. A
   background flash on the whole tile, the same highlight-then-settle idiom as .row-new/
   .feed-card-flash elsewhere in this file, rather than a text-color flash — .is-accent already
   colors the value gold permanently, so a color-based pulse on an accent tile would have nothing
   to visibly change. */
.warroom-kpi.is-updated {
  animation: kpi-pulse var(--dur-emphasis) ease-out;
}

@keyframes kpi-pulse {
  0% {
    background: rgba(255, 215, 0, 0.22);
  }
  100% {
    background: linear-gradient(180deg, var(--card-from), var(--card-to));
  }
}

.warroom-kpi__label {
  font-size: var(--fs-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-muted);
  margin-bottom: 0.35rem;
}

.warroom-kpi__value {
  font-size: clamp(2.4rem, 5vw, 3.6rem);
  font-weight: 700;
  line-height: 1.05;
  color: var(--ink);
  letter-spacing: -0.03em;
}

.warroom-kpi__value.is-accent {
  color: var(--gold);
}

.warroom-kpi__sub {
  margin-top: 0.35rem;
  font-size: var(--fs-xs);
  color: var(--ink-dim);
}

/* ---- War room — Threat journey (ticket 025) ------------------------------------------------ */

.warroom-journey {
  background: linear-gradient(180deg, var(--card-from), var(--card-to));
  border: 1px solid var(--border-gold);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-gold);
  padding: 1rem 1.1rem;
  margin-bottom: 1rem;
}

.warroom-journey__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-bottom: 0.75rem;
}

.warroom-journey__title {
  font-size: var(--fs-md);
  margin: 0;
}

.warroom-journey__steps {
  display: flex;
  gap: 1.4rem;
  align-items: stretch;
}

.warroom-journey__node {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  padding: 0.75rem 0.9rem;
  border-radius: 0.6rem;
  border: 1px solid var(--border);
  background: rgba(255, 255, 255, 0.03);
  text-decoration: none;
  color: inherit;
}

.warroom-journey__node:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}

/* A chevron connector between adjacent phases — purely decorative (the fixed left-to-right DOM/
   tab order already carries the sequence for assistive tech), suppressed once the strip wraps
   (see the 1100px breakpoint below). */
.warroom-journey__node:not(:last-child)::after {
  content: "\2192";
  position: absolute;
  top: 50%;
  right: -1.1rem;
  transform: translateY(-50%);
  color: var(--ink-muted);
  font-size: 1.1rem;
}

.warroom-journey__node.is-observed {
  border-color: color-mix(in srgb, var(--gold) 55%, var(--border));
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--gold) 25%, transparent);
}

.warroom-journey__node.is-unobserved {
  opacity: 0.6;
}

.warroom-journey__label {
  font-weight: 700;
  font-size: var(--fs-sm);
  color: var(--ink);
}

.warroom-journey__meta {
  font-size: var(--fs-xs);
  color: var(--gold-2);
  font-family: var(--font-mono);
}

.warroom-journey__node.is-unobserved .warroom-journey__meta {
  color: var(--ink-muted);
  font-family: inherit;
}

.warroom-journey__desc {
  font-size: var(--fs-2xs);
  color: var(--ink-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

.warroom-journey__node.is-pulse {
  animation: warroom-journey-pulse 1.2s ease;
}

@keyframes warroom-journey-pulse {
  0% {
    background: rgba(255, 215, 0, 0.32);
    border-color: rgba(255, 215, 0, 0.65);
    transform: scale(1.02);
  }
  100% {
    background: rgba(255, 255, 255, 0.03);
    transform: scale(1);
  }
}

body.is-kiosk .warroom-journey__desc {
  display: none;
}

body.is-kiosk .warroom-journey__node {
  padding: 0.65rem 0.8rem;
}

@media (max-width: 1100px) {
  .warroom-journey__steps {
    flex-wrap: wrap;
  }
  .warroom-journey__node,
  .warroom-journey__steps > .skeleton-block {
    flex: 1 1 calc(50% - 0.7rem);
  }
  .warroom-journey__node::after {
    content: none;
  }
}

@media (max-width: 640px) {
  .warroom-journey__node,
  .warroom-journey__steps > .skeleton-block {
    flex: 1 1 100%;
  }
}

.warroom-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  gap: 1rem;
  align-items: stretch;
}

.warroom-ticker,
.warroom-panel {
  min-height: 22rem;
  display: flex;
  flex-direction: column;
  padding: 1rem 1.1rem;
}

.warroom-ticker__head,
.warroom-panel__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
}

.warroom-panel__title,
.warroom-ticker__title {
  font-size: var(--fs-md);
  margin: 0;
}

.warroom-panel__dots {
  display: flex;
  gap: 0.4rem;
}

.warroom-panel__dot {
  width: 0.65rem;
  height: 0.65rem;
  border-radius: 999px;
  border: 1px solid var(--border-strong);
  background: transparent;
  padding: 0;
  cursor: pointer;
}

.warroom-panel__dot.is-active {
  background: var(--gold);
  border-color: var(--gold);
}

.warroom-panel__body {
  flex: 1;
}

.warroom-panel__body.is-fading {
  animation: warroom-panel-in 0.45s ease;
}

@keyframes warroom-panel-in {
  from {
    opacity: 0.35;
    transform: translateY(6px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.warroom-ticker__body {
  flex: 1;
  overflow: hidden;
}

.warroom-ticker__list {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  font-family: var(--font-mono);
  font-size: clamp(0.95rem, 1.4vw, 1.15rem);
}

.warroom-ticker__row {
  display: grid;
  /* IP track is 16ch — wide enough for a full IPv4 address ("255.255.255.255" is 15 chars,
     e.g. --console-demo's RFC 5737 addresses) so a long client IP never overflows into the
     method column (ticket 030-20260902094151). */
  grid-template-columns: 7.5ch 16ch 5.5ch minmax(0, 1fr) auto 10ch;
  gap: 0.65rem;
  align-items: center;
  padding: 0.45rem 0.55rem;
  border-radius: 0.45rem;
  border: 1px solid transparent;
  background: rgba(255, 255, 255, 0.03);
}

.warroom-ticker__row.is-new {
  animation: warroom-row-flash 1.1s ease;
}

@keyframes warroom-row-flash {
  0% {
    background: rgba(255, 215, 0, 0.28);
    border-color: rgba(255, 215, 0, 0.55);
  }
  100% {
    background: rgba(255, 255, 255, 0.03);
    border-color: transparent;
  }
}

/* A ticker-row timestamp, read on a projector — critical evidence, not chrome (ticket 032). */
.warroom-ticker__time {
  color: var(--ink-dim);
}

.warroom-ticker__ip {
  color: var(--ink);
  /* Belt-and-suspenders alongside the 16ch track above: a full IPv6 address (client_ip.py
     resolves those too, e.g. via a trusted X-Forwarded-For) can still exceed even a widened
     column, so truncate rather than collide — same pattern as .warroom-ticker__path/__service. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.warroom-ticker__method {
  color: var(--gold-2);
  font-weight: 600;
}

.warroom-ticker__path {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink-body);
}

.warroom-ticker__service {
  color: var(--ink-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.warroom-ticker__empty {
  margin: 2rem 0 0;
  color: var(--ink-muted);
}

/* "Last event HH:MM:SS UTC" line under the ticker (ticket 20260831235605). */
/* A timestamp — critical evidence, not chrome (ticket 032, doublecheck finding). */
.warroom-ticker__last-event {
  margin: 0.6rem 0 0;
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  color: var(--ink-dim);
}

/* Kiosk: hide laptop chrome; keep DEMO DATA banner visible so synthetic capture stays labeled.
   The full capture-context strip is hidden too — War room's own .warroom-evidence chip (in the
   toolbar, which stays visible in kiosk) carries the same provenance/freshness facts instead. */
body.is-kiosk .navbar,
body.is-kiosk .hero,
body.is-kiosk .site-footer,
body.is-kiosk #particle-field,
body.is-kiosk .capture-context,
body.is-kiosk .exec-journey,
/* The tour's own final step narrates "Enter kiosk for the full-screen finale" — the bar hides the
   instant that happens, exactly like every other chrome element above, and reappears (no JS needed)
   the moment kiosk mode is exited since it never left the DOM. */
body.is-kiosk .demo-tour-bar {
  display: none !important;
}

.warroom-evidence {
  margin-left: 0.4rem;
}

body.is-kiosk .view-root {
  max-width: none;
  padding: 1.25rem 1.5rem 1.75rem;
}

body.is-kiosk .warroom-kpis {
  margin-top: 0.25rem;
}

body.is-kiosk .warroom-kpi__value {
  font-size: clamp(3rem, 6vw, 4.5rem);
}

/* +12rem over the pre-ticket-025 18rem budget for the Threat journey strip now sitting between
   the KPI row and this grid (kiosk hides its description line — see .warroom-journey__desc above
   — so one compact single row of nodes is all it adds, but that row plus its own card padding and
   margin measures ~183px/11.4rem in a real browser, not the ~5rem first assumed — a
   /doublecheck-adjacent fix caught by actually resizing a real window rather than trusting the
   arithmetic).
   Ticket 031: min-height alone is a floor, not a ceiling — a flex item with no explicit height
   grows past it to fit its content, so a full ~40-row ticker pushed the whole kiosk page to
   roughly 3x the viewport height under real traffic (pre-existing since ticket 012, independent
   of the journey strip above — confirmed by hiding the strip and seeing the bulk of the overflow
   persist). max-height at the same value turns that floor into a hard ceiling; the nested flex
   chain below (.warroom-ticker__body) is what makes overflow: hidden actually clip the excess
   instead of the card just growing past it anyway. A sparse capture is unaffected — its content
   was already shorter than this budget, so the ceiling never binds and nothing new gets clipped. */
body.is-kiosk .warroom-ticker,
body.is-kiosk .warroom-panel {
  min-height: calc(100vh - 30rem);
  max-height: calc(100vh - 30rem);
}

/* .warroom-ticker__body already has flex: 1; overflow: hidden (above), but a flex item's default
   min-height is auto — its content's min-content size — which ignores overflow: hidden and keeps
   growing regardless of the ancestor max-height just added. min-height: 0 lets it actually shrink
   to the space left after .warroom-ticker__head/__last-event, so overflow: hidden clips whatever
   ticker rows don't fit instead of the card growing past one screen (ticket 031).
   .warroom-panel__body needs the identical override — its content (top_client_ips/top_paths, both
   capped server-side at 10 rows by store.py's _TOP_N, or the small fixed status-class breakdown)
   was assumed short enough to never approach this ceiling, but browser verification with the
   bundled --console-demo dataset showed a full 10-row ranked-bars panel is taller than the
   ~20.4rem/326px kiosk budget at 1280x720, overflowing the card exactly like the ticker did. */
body.is-kiosk .warroom-ticker__body,
body.is-kiosk .warroom-panel__body {
  min-height: 0;
  overflow: hidden;
}

@media (max-width: 1100px) {
  .warroom-kpis {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .warroom-grid {
    grid-template-columns: 1fr;
  }
  .warroom-ticker__row {
    /* Same 16ch IP track as the base rule — see the comment there. */
    grid-template-columns: 7ch 16ch 5ch minmax(0, 1fr) auto;
  }
  .warroom-ticker__service {
    display: none;
  }
}

@media (max-width: 640px) {
  .warroom-kpis {
    grid-template-columns: 1fr;
  }
  .warroom-exit-hint {
    display: none;
  }
}

/* ---- Overview — executive decision layer (ticket 014) ------------------------------------- */

.overview-posture {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.6rem 0.8rem;
}

.overview-posture__headline {
  margin: 0;
  font-size: var(--fs-md);
  line-height: 1.5;
  color: var(--ink);
  max-width: 54rem;
}

.overview-unavailable-note {
  margin-top: 0.6rem;
}

.overview-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
}

/* Reuses .stat-tile for layout — this modifier only turns the tile into a clickable drill-down
   (the outcomes band is `<a>` elements, unlike the plain-`<div>` supporting-telemetry tiles). */
.overview-outcome {
  display: block;
  text-decoration: none;
  color: inherit;
}

.overview-outcome:hover,
.overview-outcome:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}

/* Reuses .briefing-event/.briefing-events (Executive briefing's notable-engagement list) for the
   "what changed" list — each row wraps its title/detail in this link instead of being static. */
.overview-changed-link {
  display: block;
  text-decoration: none;
  color: inherit;
}

.overview-changed-link:hover .briefing-event__title,
.overview-changed-link:focus-visible .briefing-event__title {
  color: var(--gold);
}

.overview-telemetry {
  margin-top: 2.6rem;
  padding-top: 1.6rem;
  border-top: 1px solid var(--border);
}

/* Recognized threats strip (ticket 015) — a row of severity-tinted, clickable chips, one per
   named exploit signature actually hit today. Each chip is an <a> like .overview-outcome above. */
.overview-threats {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
}

/* Overrides .pill's white-space: nowrap — the concatenated severity dot + name + CVE + hit-count
   meta can be wider than a narrow viewport, and with nowrap a flex item's min-width pins to that
   unwrapped content width, inflating the whole page's scrollable width past clientWidth (ticket
   037). max-width: 100% keeps the chip itself within its .overview-threats flex-wrap container,
   and flex-wrap: wrap lets the dot/name/CVE/meta spans drop onto additional lines instead. */
.overview-threat-chip {
  text-decoration: none;
  gap: 0.5rem;
  padding: 0.4rem 0.85rem;
  max-width: 100%;
  white-space: normal;
  flex-wrap: wrap;
}

.overview-threat-chip:hover,
.overview-threat-chip:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}

.overview-threat-chip__name {
  color: var(--ink);
  font-weight: 600;
}

.overview-threat-chip__cve {
  font-size: var(--fs-2xs);
  color: var(--ink-muted);
}

.overview-threat-chip__meta {
  font-size: var(--fs-2xs);
  color: var(--ink-muted);
}

.overview-telemetry__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 0.9rem;
}

@media (max-width: 560px) {
  .overview-posture {
    flex-direction: column;
    align-items: flex-start;
  }
}

/* ---- Executive briefing -------------------------------------------------------------------- */

.briefing-page {
  display: flex;
  flex-direction: column;
}

.briefing-report-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1.2rem;
  margin-bottom: 0.9rem;
}

.briefing-report-mark {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--gold);
  font-weight: 700;
}

.briefing-report-title {
  margin: 0.2rem 0 0.15rem;
}

.briefing-report-sub {
  margin: 0;
  font-size: var(--fs-xs);
  color: var(--ink-muted);
}

.briefing-report-meta {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: auto auto;
  gap: 0.2rem 0.9rem;
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
}

.briefing-report-meta__row {
  display: contents;
}

.briefing-report-meta__label {
  color: var(--ink-muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: var(--fs-2xs);
  align-self: baseline;
}

.briefing-report-meta__value {
  color: var(--ink);
}

.briefing-actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin: 0.9rem 0;
}

.briefing-purpose {
  margin: 0 0 0.4rem;
  max-width: 62rem;
}

.briefing-headline {
  font-size: var(--fs-md);
  line-height: 1.45;
  color: var(--ink);
  margin: 0 0 1rem;
  max-width: 54rem;
}

.briefing-prose {
  margin: 0;
  line-height: 1.55;
  color: var(--ink);
}

.briefing-prose--risk {
  font-weight: 500;
}

.briefing-rating {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.6rem;
}

.briefing-rating__badge {
  font-size: var(--fs-xs);
  padding: 0.3rem 0.9rem;
}

.briefing-confidence {
  margin: 0;
}

/* Reuses .stat-tile for layout (same pattern as overview.js's .overview-outcome) — the ATT&CK/
   bait-coverage outcome tiles are deep links, unlike the plain-`<div>` KPI tiles above them. */
.briefing-kpi-link {
  display: block;
  text-decoration: none;
  color: inherit;
}

.briefing-kpi-link:hover,
.briefing-kpi-link:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}

.briefing-events {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
}

.briefing-event {
  padding: 0;
  border-radius: 0.55rem;
  border: 1px solid var(--border-strong);
  background: var(--glass);
  overflow: hidden;
}

.briefing-event__link {
  display: block;
  padding: 0.55rem 0.7rem;
  text-decoration: none;
  color: inherit;
}

.briefing-event__link:hover,
.briefing-event__link:focus-visible {
  background: var(--gold-soft);
  outline: none;
}

.briefing-event__title {
  font-weight: 600;
  font-size: var(--fs-sm);
  margin-bottom: 0.2rem;
}

/* The notable-engagement evidence line (method/path/timestamp) in the board-ready Briefing —
   critical evidence, not chrome (ticket 032); also added to the print override list below, which
   had missed it (--ink-muted's light gray was unreadable on printed white paper). */
.briefing-event__detail {
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  color: var(--ink-dim);
}

.briefing-event--hackback {
  border-color: rgba(255, 215, 0, 0.45);
}

.briefing-event--errors {
  border-color: rgba(255, 120, 90, 0.4);
}

.briefing-followups {
  margin: 0;
  padding-left: 1.3rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.briefing-followup {
  line-height: 1.5;
}

.briefing-followup__link {
  color: var(--ink);
}

.briefing-followup__link:hover,
.briefing-followup__link:focus-visible {
  color: var(--gold);
}

/* A print-safe list of stable identifiers (session id / client IP / UTC timestamp) for the items
   in "Notable observations" above — for a paper/PDF copy where a deep link can't be clicked. */
.briefing-appendix {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
}

.briefing-appendix__row {
  display: flex;
  gap: 0.6rem;
  align-items: baseline;
}

.briefing-appendix__ref {
  color: var(--ink-muted);
  flex: 0 0 auto;
}

.briefing-appendix__detail {
  color: var(--ink);
}

.briefing-footer {
  margin-top: 2rem;
  padding-top: 1rem;
  border-top: 1px solid var(--border);
}

.briefing-footer__text {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  color: var(--ink-muted);
}

/* 390px: the report head's brand/title and its meta list used to share one baseline flex row and
   the print button used to compete with the purpose paragraph in the same row — both now stack. */
@media (max-width: 560px) {
  .briefing-report-head {
    flex-direction: column;
  }

  .briefing-report-meta {
    grid-template-columns: auto 1fr;
    width: 100%;
  }

  .briefing-print-btn {
    width: 100%;
  }
}

/* Print / Save as PDF — hide chrome, keep the report legible, identity-marked, and evidence-backed
   on white paper. `size: auto` (never a forced Letter or A4) plus mm margins is what keeps one
   stylesheet safe on both page sizes. */
@media print {
  @page {
    size: auto;
    margin: 14mm;
  }

  body {
    background: #fff !important;
    color: #111 !important;
  }

  #particle-field,
  .navbar,
  .hero,
  .capture-context,
  .exec-journey,
  .no-print,
  .toast,
  #shortcuts-overlay,
  #detail-overlay,
  .demo-tour-bar {
    display: none !important;
  }

  /* Keep the DEMO DATA banner visible on printed/PDF copies — synthetic capture must stay labeled. */
  .demo-banner {
    display: block !important;
    position: static !important;
    background: #fff !important;
    color: #111 !important;
    border: 2px solid #111 !important;
    padding: 0.4rem 0.6rem !important;
    margin: 0 0 0.75rem !important;
    font-weight: 700 !important;
  }

  .view-root {
    padding: 0 !important;
    max-width: none !important;
  }

  /* Reserve room at the bottom of every page for the fixed, repeating footer below. */
  .briefing-page {
    padding-bottom: 2.2rem;
  }

  .briefing-page .section__title,
  .briefing-report-mark,
  .briefing-report-sub,
  .briefing-report-meta__label,
  .briefing-report-meta__value,
  .briefing-headline,
  .briefing-prose,
  .briefing-confidence,
  .briefing-event__title,
  .briefing-event__detail,
  .briefing-followup__link,
  .briefing-appendix__ref,
  .briefing-appendix__detail,
  .stat-tile__label,
  .stat-tile__value,
  .stat-tile__sub,
  .section__hint {
    color: #111 !important;
  }

  .stat-tile,
  .briefing-card,
  .briefing-event,
  .card {
    background: #fff !important;
    border: 1px solid #ccc !important;
    box-shadow: none !important;
    break-inside: avoid;
    page-break-inside: avoid;
  }

  .briefing-section,
  .briefing-hero-block,
  .briefing-kpi-band,
  .briefing-rating,
  .briefing-report-head {
    break-inside: avoid;
    page-break-inside: avoid;
  }

  /* A section heading must never be stranded alone at the bottom of a page. */
  .briefing-section .section__head,
  .briefing-report-title {
    break-after: avoid;
    page-break-after: avoid;
  }

  .stat-tile__value.is-accent {
    color: #000 !important;
  }

  .briefing-event--hackback,
  .briefing-event--errors,
  .briefing-event--probe {
    border-color: #888 !important;
  }

  .briefing-event__link,
  .briefing-followup__link,
  .briefing-kpi-link {
    text-decoration: none !important;
  }

  .briefing-footer {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    margin: 0;
    padding: 0.3rem 0;
    border-top: 1px solid #ccc;
    background: #fff;
  }

  .briefing-footer__text {
    color: #555 !important;
  }
}
