/* ── Salesforce Sans webfont ─────────────────────────────────────────────
 * Body type for the V2 brand. Hosted on Salesforce's own CDN (the same
 * one developer.salesforce.com loads from). Only Regular / Italic /
 * Bold ship — Light/SemiBold 404, so the stack falls through to Inter
 * for those weights. font-display:swap lets the portal paint in Inter
 * immediately and reflow once Salesforce Sans resolves (no FOIT). */
@font-face {
  font-family: 'Salesforce Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('//a.sfdcstatic.com/shared/fonts/salesforce-sans/SalesforceSans-Regular.woff2') format('woff2');
}
@font-face {
  font-family: 'Salesforce Sans';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('//a.sfdcstatic.com/shared/fonts/salesforce-sans/SalesforceSans-Italic.woff2') format('woff2');
}
@font-face {
  font-family: 'Salesforce Sans';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('//a.sfdcstatic.com/shared/fonts/salesforce-sans/SalesforceSans-Bold.woff2') format('woff2');
}

/* portal.css — Salesforce Intelligence Centre · Pit Wall portal.
 *
 * v6 "Final Handoff" design system — anchored on Cloud Blue 68 #00B3FF
 * as the PRIMARY brand color, with Electric Blue 50/30 demoted to
 * supporting accents/depth. Per the UX handoff doc:
 *   "Cloud Blue 68 #00B3FF is the anchor. Everything else supports it."
 *
 * Color hierarchy:
 *   • Primary anchor:  Cloud Blue 68    #00B3FF  — CTAs, accents, active states
 *   • Supporting mid:  Electric Blue 50 #066AFE  — secondary actions, depth
 *   • Supporting deep: Electric Blue 30 #022AC0  — banner depth, deep accents
 *   • Portal bg:       #0E2A5C → #0A1E3F → #0A1424  (vertical 3-stop)
 *   • Dashboard bg:    #050B17 → #071425  (true dark mode for race ops)
 *     Panels:          #0B172B   Elevated: #10284A
 *   • Status:          Warning #FFC400 · Critical #FF3B30 · Success #34C759
 *
 * Narrative flow:
 *   Portal (overview)  → clean, bright, Cloud-Blue-led
 *   Tile (entry point) → dark with Cloud Blue 68 accents
 *   Dashboard (ops)    → dark, focused, data-intensive
 *
 * Token names preserved (--cobalt, --cobalt-deep, --cobalt-bright)
 * so the dozens of consumer files (viz extensions, dash-ext, the live
 * dashboard) don't all need to be rewritten — only the values shift.
 * `--cloud-blue*` aliases now point at the PRIMARY brand swatch (since
 * Cloud Blue IS the anchor again); `--electric-blue*` aliases point at
 * the supporting swatches.
 */

:root {
  /* v6 Cloud Blue 68 brand anchor. Token names stay `--cobalt*` so
   * the live dashboard, agent rail, and dozens of viz extensions
   * don't all need to be rewritten; only the swatch values shift. */
  --cobalt:        #00B3FF;   /* Cloud Blue 68 — PRIMARY brand anchor */
  --cobalt-deep:   #022AC0;   /* Electric Blue 30 — supporting depth */
  --cobalt-bright: #5DD2FF;   /* Bright Cloud Blue tint for highlights/hovers */
  --cobalt-glow:   rgba(0, 179, 255, 0.45);
  --cobalt-soft:   rgba(0, 179, 255, 0.18);

  /* Cloud Blue 68 aliases — same as primary, descriptive names. */
  --cloud-blue:        var(--cobalt);
  --cloud-blue-deep:   var(--cobalt-deep);
  --cloud-blue-bright: var(--cobalt-bright);
  --cloud-blue-glow:   var(--cobalt-glow);
  --cloud-blue-soft:   var(--cobalt-soft);

  /* Electric Blue supporting palette — used for secondary actions,
   * gradient mid-stops, and depth. NOT the primary brand color. */
  --electric-blue-50:    #066AFE;
  --electric-blue-30:    #022AC0;
  --electric-blue-glow:  rgba(6, 106, 254, 0.45);
  --electric-blue-soft:  rgba(6, 106, 254, 0.16);

  /* Gradients straight from the handoff. Use these instead of
   * hand-rolling gradients in component CSS so the system stays
   * consistent if the spec shifts again. */
  --gradient-portal-header:
    linear-gradient(90deg, #00B3FF 0%, #066AFE 48%, #022AC0 100%);
  --gradient-portal-body:
    radial-gradient(circle at 50% 0%, rgba(0, 179, 255, 0.18), transparent 42%),
    linear-gradient(180deg, #0E2A5C 0%, #0A1E3F 42%, #0A1424 100%);
  --gradient-hero-surface:
    linear-gradient(90deg, #FFFFFF 0%, #F4F8FF 42%, #DCEEFF 100%);
  --gradient-dashboard:
    linear-gradient(180deg, #071425 0%, #050B17 100%);

  /* Border + shadow tokens from the handoff. */
  --border-subtle-dark: 1px solid rgba(255, 255, 255, 0.12);
  --border-blue-glow:   1px solid rgba(0, 179, 255, 0.42);
  --border-light:       1px solid rgba(10, 20, 36, 0.10);
  --shadow-card-dark:
    0 18px 44px rgba(0, 0, 0, 0.32),
    inset 0 1px 0 rgba(255, 255, 255, 0.08);
  --shadow-card-blue:
    0 20px 52px rgba(0, 179, 255, 0.20),
    0 0 0 1px rgba(0, 179, 255, 0.22);
  --shadow-hero:
    0 24px 64px rgba(2, 42, 192, 0.22);

  /* Spacing system — 8px base grid. */
  --space-1:  4px;
  --space-2:  8px;
  --space-3:  12px;
  --space-4:  16px;
  --space-5:  20px;
  --space-6:  24px;
  --space-8:  32px;
  --space-10: 40px;
  --space-12: 48px;
  --space-16: 64px;
  --space-20: 80px;

  /* v4.1 stage lift — Salesforce brand pack reads as airy/cloud-bright,
   * not pitch-black. Stage shifts from near-black (#06091A) to a deeper
   * cloud-blue night that still gives dashboards plenty of contrast but
   * carries the Cloud Blue 68 DNA all the way through the chrome. Cards
   * lift in tandem so they still elevate against the lighter stage. */
  /* v6 stage system. Two distinct surfaces:
   *   • Portal landing — vertical 3-stop dark navy with a Cloud Blue
   *     glow at the top, anchored on Cloud Blue 68. Bright enough to
   *     support the light Strategy Hero, dark enough to make the dark
   *     Pit Wall card pop.
   *   • Dashboard viewer — true dark mode (#050B17 → #071425) so
   *     race-day operators feel "illuminated by data, not dim".
   *
   * Panels (per the handoff "UI BACKGROUNDS" spec):
   *   --bg-0  Panels                 #0B172B
   *   --bg-1  Raised panels          #0E1F3A
   *   --bg-2  Elevated cards         #10284A
   */
  --bg-stage:        #0A1424;   /* portal landing deep base */
  --bg-stage-mid:    #0A1E3F;   /* portal landing middle */
  --bg-stage-top:    #0E2A5C;   /* portal landing gradient top */
  --bg-stage-dark:   #050B17;   /* dashboard viewer (dark mode) */
  --bg-stage-dark-2: #071425;   /* dashboard viewer (gradient top) */
  --bg-stage-cobalt: #022AC0;   /* embedded-viz wrapper (legacy var name preserved); points at Electric Blue 30 so Tableau's outer padding matches the dashboard chrome */
  --bg-0:            #0B172B;   /* "Panels" per handoff */
  --bg-1:            #0E1F3A;   /* raised panel */
  --bg-2:            #10284A;   /* "Elevated Panels" per handoff */
  --bg-grad-top:     #1F2C4D;   /* legacy alias, kept for old callsites */
  --bg-grad-bot:     #16223C;

  /* Pit Wall card chrome — dark per spec, with Cloud Blue 68 border
   * glow as the active state. The card's own art (final_pit_wall_card)
   * already carries the live race photo; the chrome here is just the
   * frame around it. */
  --card-bg-top: #0B172B;
  --card-bg-bot: #050B17;
  --card-line:   rgba(0, 179, 255, 0.42);   /* matches --border-blue-glow */
  --card-line-h: rgba(0, 179, 255, 0.72);   /* hover-state stronger glow */

  --line:        rgba(168, 184, 220, 0.18);
  --line-soft:   rgba(168, 184, 220, 0.10);
  --line-strong: rgba(168, 184, 220, 0.32);

  --ink-0:       #FFFFFF;                          /* primary text */
  --ink-1:       rgba(255, 255, 255, 0.72);        /* secondary text */
  --ink-2:       rgba(255, 255, 255, 0.48);        /* muted text */
  --ink-3:       rgba(255, 255, 255, 0.32);        /* deep muted */

  /* Status palette — pinned to the design system spec. Aliased to the
   * legacy --lime / --amber / --magenta tokens so callsites that already
   * use those resolve to the spec swatches without per-file edits. */
  --status-warn:    #FFC400;   /* Yellow */
  --status-crit:    #FF3B30;   /* Red    */
  --status-ok:      #34C759;   /* Green  */

  --cyan:        #5AC4E0;
  --violet:      #A88FE0;
  --lime:        var(--status-ok);
  --amber:       var(--status-warn);
  --magenta:     var(--status-crit);

  --cyan-glow:    rgba(90, 196, 224, 0.45);
  --violet-glow:  rgba(168, 143, 224, 0.45);
  --lime-glow:    rgba(52, 199, 89, 0.45);
  --amber-glow:   rgba(255, 196, 0, 0.45);

  --mono:    'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
  --sans:    'Salesforce Sans', 'Inter', ui-sans-serif, -apple-system, system-ui, sans-serif;
  /* --display: head/wordmark face. Jost is the closest free, well-hinted
   * stand-in for ITC Avant Garde Gothic (Salesforce Intelligence Centre's
   * preferred display family). Once the licensed file lands, swap it in
   * here and the topbar / hero / crumbs all pick it up. */
  --display: 'Jost', 'Avant Garde', 'Century Gothic', var(--sans);

  /* Radius tokens — straight from the handoff spec. The legacy
   * --radius and --radius-lg tokens stay (still used across dozens
   * of consumer files) but pick up the handoff values; --radius-xl
   * is new and used for the headline cards (Pit Wall + Strategy
   * Hero) per the handoff "Cards: --radius-lg or --radius-xl" rule. */
  --radius-sm:   8px;
  --radius:      12px;   /* default — dashboard panels, status cards */
  --radius-md:   12px;   /* alias */
  --radius-lg:   16px;   /* tiles */
  --radius-xl:   20px;   /* headline cards (Pit Wall, Strategy Hero) */
  --radius-2xl:  24px;
  --radius-pill: 999px;

  /* Banner height — sized so the official VCARB×Salesforce co-branded
   * lockup renders large enough that the partnership tagline ("CRM
   * PARTNER & FAN ENGAGEMENT AGENT") inside the artwork stays legible
   * at desktop scales. Lockup itself is 1600×756 (~2.12:1), so a 64px
   * tall logo lands at ~136 px wide — comfortably readable. */
  /* V2 follow-up #3: bumped from 88 → 104 so the much-larger
   * "Salesforce Intelligence Centre" wordmark has room to breathe
   * inside the new blue→white gradient banner without crowding the
   * crumb / mode chip / user pill on the right. */
  --topbar-h:    104px;
}

* { box-sizing: border-box; }

html, body {
  margin: 0; padding: 0;
  /* dvh (dynamic viewport height) instead of vh: iOS Safari's URL bar
   * + tab bar collapse on scroll, and `vh` reports the LARGER static
   * viewport which makes the portal overflow the visible area on iPad.
   * `dvh` tracks the live viewport so the body stretches and shrinks
   * with the chrome. Kiosk fork only — production at demo/pit-wall/
   * still uses 100vh because production runs on desktop kiosks where
   * the chrome doesn't move. */
  width: 100%; min-height: 100dvh;
  /* v6 portal body gradient — straight from the handoff spec:
   *   radial Cloud Blue 68 glow at the top
   *   linear vertical 3-stop dark navy beneath
   * This pairs the light Strategy Hero (right) and dark Pit Wall
   * card (left) cleanly without competing with either. */
  background: var(--gradient-portal-body);
  color: var(--ink-0);
  font-family: var(--sans);
  font-feature-settings: "ss01", "tnum";
  -webkit-font-smoothing: antialiased;
}

body { display: flex; flex-direction: column; }

a { color: inherit; text-decoration: none; }
button { font-family: inherit; }

/* ============================================================
 * TOP BAR — same chrome as the dashboards' top-bar viz extension
 * ============================================================ */
.topbar {
  position: sticky; top: 0; z-index: 50;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  gap: 24px;
  align-items: center;
  height: var(--topbar-h);
  padding: 0 22px;
  /* V2 follow-up #4 (onsite team): horizontal gradient flips to dark
   * navy → light Cloud Blue 68 (left → right). White wordmark with
   * multi-layer navy text-shadow keeps it readable on the lighter
   * blue end of the gradient without looking like a glowing badge. */
  background:
    radial-gradient(700px 200px at 100% 100%, rgba(255, 255, 255, 0.12), transparent 60%),
    linear-gradient(90deg,
      #001E58 0%,
      #022AC0 32%,
      #066AFE 65%,
      #00B3FF 100%);
  border-bottom: 1px solid rgba(255, 255, 255, 0.22);
  box-shadow:
    0 2px 8px -4px rgba(0, 30, 88, 0.45),
    0 12px 28px -18px rgba(11, 16, 32, 0.85);
  color: #fff;
}

.tb-left  { display: flex; align-items: center; gap: 18px; min-width: 0; }
.tb-center{ display: flex; align-items: center; gap: 10px; justify-content: center; }
.tb-right { display: flex; align-items: center; gap: 14px; justify-content: flex-end; min-width: 0; }

/* Co-branded VCARB×Salesforce lockup — the official partnership mark
 * that contains both team and Salesforce identity in a single asset
 * ("Visa RB / Cash App Formula One Team / Salesforce / CRM PARTNER &
 * FAN ENGAGEMENT AGENT"). Because the lockup is self-contained, we no
 * longer split brand chrome into "team logo on the left + powered-by
 * Salesforce pill on the right" the way the v3 cobalt rebrand did.
 * One mark, more breathing room for crumbs and live status. */
.logo-box {
  display: flex; align-items: center; gap: 18px;
  /* Subtle hairline divider works again now the bar is fully blue. */
  border-right: 1px solid rgba(255, 255, 255, 0.22);
  padding: 6px 22px 6px 0;
  text-decoration: none; color: inherit;
}
.logo-box img.vcarb-logo {
  height: 56px; width: auto; display: block;
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.50));
}
/* V2 follow-up #4: wordmark is the focal point — same big size as
 * before, but back to white now the bar is fully blue. The text-shadow
 * stack does double duty:
 *   1. Crisp 2px navy drop = sharp edges on the brighter Cloud Blue 68
 *      end of the gradient (right).
 *   2. Soft 18px navy halo = visual weight against the light blue
 *      where pure white would otherwise wash out.
 *   3. Extended 36px halo = ambient lift so the wordmark sits on
 *      its own field instead of melting into the gradient.
 * Tuned subtle — wordmark still reads as flat painted text, not a
 * glowing badge. */
.logo-box .logo-txt {
  display: flex; align-items: center;
  line-height: 1; min-width: 0;
}
.logo-box .logo-txt .wordmark {
  font-family: var(--display);
  font-size: clamp(34px, 4.4vw, 56px);
  font-weight: 700;
  color: #fff;
  letter-spacing: -0.012em;
  white-space: nowrap;
  text-shadow:
    0 2px 4px rgba(0, 16, 56, 0.65),
    0 0 18px rgba(0, 50, 140, 0.55),
    0 0 36px rgba(0, 80, 180, 0.30);
}

/* Legacy partner-mark pill — the Salesforce identity has moved INTO the
 * official co-branded lockup on the left side of the topbar. The pill
 * markup is still emitted by portal.js for backwards-compat with cached
 * older builds; we just hide it so the right side stays clean. */
.partner-mark { display: none !important; }

/* V2 follow-up #4: bar is fully blue again (dark navy → Cloud Blue 68
 * gradient) so crumb + chips go back to white-on-blue. Body text gets
 * a soft navy text-shadow for the same readability story as the
 * wordmark on the lighter end of the gradient. */
.crumb {
  display: flex; flex-direction: column; gap: 3px; min-width: 0;
}
.crumb .title {
  font-family: var(--display);
  font-size: 22px; font-weight: 500; letter-spacing: 0.005em;
  color: #fff;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  text-shadow: 0 1px 2px rgba(0, 16, 56, 0.45);
}
.crumb .sub {
  font-family: var(--mono);
  font-size: 10px; letter-spacing: 0.22em;
  text-transform: uppercase; color: rgba(255, 255, 255, 0.85);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.mode-chip {
  display: inline-flex; align-items: center; gap: 10px;
  font-family: var(--mono); font-size: 12px; letter-spacing: 0.18em;
  font-weight: 700;
  background: rgba(168, 232, 98, 0.18);
  color: #E5FFC9;
  border: 1px solid rgba(168, 232, 98, 0.65);
  padding: 9px 16px; border-radius: 999px;
}
.mode-chip i {
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--lime); box-shadow: 0 0 12px var(--lime);
}

/* Session chip — sits beside the mode chip and announces the active
 * replay so the dashboard surface admits its source (audience knows
 * they're watching last year's race during a sim, not live MQTT).
 * Hidden when no sim is running (poll sets `hidden`). The speed pill
 * inside it is bolder so an operator can ID the playback rate from
 * across a venue room. */
.session-chip {
  display: inline-flex; align-items: center; gap: 10px;
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.16em;
  font-weight: 600;
  text-transform: uppercase;
  background: rgba(94, 224, 224, 0.10);
  color: #C7F6F6;
  border: 1px solid rgba(94, 224, 224, 0.45);
  padding: 8px 12px;
  border-radius: 999px;
  margin-left: 10px;
  white-space: nowrap;
}
.session-chip-speed {
  display: inline-flex; align-items: center;
  font-size: 11px; letter-spacing: 0.06em;
  font-weight: 700;
  background: rgba(94, 224, 224, 0.20);
  color: #E0FFFF;
  padding: 2px 8px;
  border-radius: 999px;
}

/* Driven-by pill — flashes in when an intent fires on /sse/control,
 * fades out after BUS_TAG_TTL_MS. Magenta accent so it reads as a
 * different signal class from the green/cyan mode/session chips. */
.bus-tag {
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.16em;
  font-weight: 600;
  text-transform: uppercase;
  background: rgba(168, 143, 224, 0.10);
  color: #E5DCFF;
  border: 1px solid rgba(168, 143, 224, 0.45);
  padding: 7px 12px;
  border-radius: 999px;
  margin-left: 10px;
  white-space: nowrap;
  animation: busTagIn 0.25s ease-out;
}
.bus-tag-label {
  color: rgba(229, 220, 255, 0.65);
  font-size: 10px; letter-spacing: 0.20em;
}
.bus-tag-name {
  color: #FFF;
  letter-spacing: 0.04em;
  text-transform: none;
  font-size: 12px;
  font-weight: 600;
}
@keyframes busTagIn {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Data-source banner. portal.js mirrors the /health data_source.mode
 * onto body[data-data-source]. Three audience-visible states:
 *   REPLAY → magenta 3px top strip (last year's race)
 *   DOWN   → red 3px top strip + watermark (broker disconnected)
 *   IDLE   → muted blue strip + "BETWEEN SESSIONS" watermark
 *           (between FP/Quali/Race — no fresh telemetry expected) */
body[data-data-source="REPLAY"]::before,
body[data-data-source="DOWN"]::before,
body[data-data-source="IDLE"]::before {
  content: "";
  position: fixed; top: 0; left: 0; right: 0;
  height: 3px;
  z-index: 9999;
  pointer-events: none;
}
body[data-data-source="REPLAY"]::before {
  background: linear-gradient(90deg, transparent 0%, #E66270 20%, #E66270 80%, transparent 100%);
  box-shadow: 0 0 12px rgba(230, 98, 112, 0.6);
}
body[data-data-source="DOWN"]::before {
  background: #C03A47;
  box-shadow: 0 0 14px rgba(192, 58, 71, 0.8);
}
body[data-data-source="IDLE"]::before {
  background: linear-gradient(90deg, transparent 0%, #5C7AB8 20%, #5C7AB8 80%, transparent 100%);
  box-shadow: 0 0 10px rgba(92, 122, 184, 0.55);
}

/* Mid-page watermark: only paints during IDLE / DOWN — REPLAY runs
 * the question-loop on real (replayed) telemetry so panel content
 * remains valid; we don't want to wash it out. The watermark sits
 * behind the agent rail's right edge so it's audience-visible but
 * doesn't compete with the dashboard panels for attention. */
body[data-data-source="IDLE"]::after,
body[data-data-source="DOWN"]::after {
  position: fixed;
  bottom: 24px; left: 50%;
  transform: translateX(-50%);
  z-index: 9998;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 12px; letter-spacing: 0.32em; text-transform: uppercase;
  font-weight: 600;
  padding: 8px 18px;
  border-radius: 999px;
  pointer-events: none;
  animation: dataSourceWatermarkIn 0.5s ease-out;
}
body[data-data-source="IDLE"]::after {
  content: "Between sessions · live telemetry idle";
  background: rgba(92, 122, 184, 0.18);
  color: #C8D4F0;
  border: 1px solid rgba(92, 122, 184, 0.55);
}
body[data-data-source="DOWN"]::after {
  content: "Upstream broker disconnected";
  background: rgba(192, 58, 71, 0.18);
  color: #FFD0D6;
  border: 1px solid rgba(192, 58, 71, 0.65);
}
@keyframes dataSourceWatermarkIn {
  from { opacity: 0; transform: translate(-50%, 6px); }
  to   { opacity: 1; transform: translate(-50%, 0); }
}

.user-chip {
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--mono);
  font-size: 11px; letter-spacing: 0.18em;
  text-transform: uppercase; color: rgba(255, 255, 255, 0.92);
  border: 1px solid rgba(255, 255, 255, 0.30);
  padding: 8px 14px; border-radius: 999px;
  background: rgba(255, 255, 255, 0.08);
}
.user-chip strong {
  color: #fff; font-weight: 700;
  letter-spacing: 0.08em; text-transform: none;
}

/* Topbar Pair Phone chip — persistent always-visible mini-QR (Miami-fix
 * v137). Renders the QR + room code together so any operator (mid-day
 * shift change, breaks) can self-pair just by scanning. The QR is
 * auto-refreshed every 50s by maintainPairQr() so a screenshot becomes
 * useless within a minute. Click the tile → opens the bigger overlay
 * for venue rooms that need a larger code. */
.pair-chip {
  appearance: none;
  display: inline-flex; align-items: center; gap: 12px;
  font-family: var(--mono);
  color: #fff;
  background: linear-gradient(180deg, rgba(168, 232, 98, 0.18), rgba(0, 30, 88, 0.45));
  border: 1px solid rgba(168, 232, 98, 0.55);
  padding: 6px 14px 6px 6px; border-radius: 12px;
  cursor: pointer;
  transition: background .2s ease, border-color .2s ease, transform .12s ease, box-shadow .2s ease;
  box-shadow: 0 0 18px rgba(168, 232, 98, 0.18);
  text-shadow: 0 1px 2px rgba(0, 16, 56, 0.45);
}
.pair-chip:hover {
  background: linear-gradient(180deg, rgba(168, 232, 98, 0.26), rgba(0, 30, 88, 0.55));
  border-color: rgba(168, 232, 98, 0.85);
  transform: translateY(-1px);
  box-shadow: 0 0 28px rgba(168, 232, 98, 0.30);
}
/* Topbar mini-QR (Canada race-week re-enable, 2026-05-11): for Miami
 * we hid the QR because the venue's camera focus + lighting made scans
 * unreliable. Going into Canada the demo flips back to phone-app-driven
 * questions, so the QR is the primary fast path again — operators want
 * to lift a phone, scan, and be paired without typing the rotating code.
 * The img is auto-refreshed every 50s by refreshPairQrOnce() so a
 * screenshot of the chip is useless within a minute (token TTL).
 *
 * White 4px frame so the QR's quiet-zone is preserved against the
 * lime-tinted chip background. Slightly rounded so it matches the
 * chip's pill aesthetic. */
.pair-chip-qr {
  width: 56px; height: 56px;
  background: #fff;
  border-radius: 8px;
  padding: 4px;
  flex: 0 0 auto;
  box-shadow: 0 1px 6px rgba(0, 0, 0, 0.35);
}
.pair-chip-meta {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: 1px;
  line-height: 1.05;
}
.pair-chip-label {
  font-size: 9px; font-weight: 700;
  letter-spacing: 0.22em; text-transform: uppercase;
  color: #C5E89F;
}
.pair-chip-room {
  font-weight: 800; font-size: 14px;
  letter-spacing: 0.18em;
  color: #fff;
  text-shadow: 0 0 10px rgba(168, 232, 98, 0.65);
}
/* Pair code (Miami-fix v138 PM #2) — the rotating 5-char short_id
 * shown alongside the room so an operator with a busted/blocked
 * camera can still pair by typing into /control on the phone. The
 * #topbar-pair-code span is updated in lockstep with the QR every
 * 50 s by refreshPairQrOnce() — same TTL as the QR, so a code seen
 * here is the same code the relay's /redeem-pair-code accepts. */
.pair-chip-code {
  font-weight: 800; font-size: 13px;
  letter-spacing: 0.20em;
  color: #C5E89F;
  text-shadow: 0 0 12px rgba(168, 232, 98, 0.55);
}
.pair-chip-code #topbar-pair-code {
  color: #fff;
  margin-left: 2px;
}
.pair-chip-hint {
  font-size: 8.5px; font-weight: 600;
  letter-spacing: 0.20em; text-transform: uppercase;
  color: rgba(255,255,255,0.62);
}
@media (max-width: 800px) {
  .pair-chip { padding: 4px 10px 4px 4px; gap: 8px; }
  .pair-chip-qr { width: 36px; height: 36px; }
  .pair-chip-room { font-size: 13px; }
  .pair-chip-label, .pair-chip-hint { display: none; }
}

/* Pair Phone overlay — full-viewport modal with giant code + QR.
 * Same structure as the dh-ai-agent overlay so the visual language
 * is identical across surfaces. */
.pair-overlay {
  position: fixed; inset: 0;
  background: rgba(0, 8, 24, 0.82);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  z-index: 9999;
  display: flex; align-items: center; justify-content: center;
  padding: 32px;
  animation: pair-fade-in .2s ease-out;
}
@keyframes pair-fade-in { from { opacity: 0; } to { opacity: 1; } }

.pair-overlay .pair-card {
  position: relative;
  background: linear-gradient(180deg, #001E58, #050B17);
  border: 1px solid rgba(93, 210, 255, 0.45);
  border-radius: 20px;
  box-shadow: 0 40px 100px -20px rgba(0, 8, 32, 0.95), 0 0 80px -10px rgba(6, 106, 254, 0.40);
  padding: 32px 36px 28px;
  width: min(840px, 100%);
  /* dvh tracks Safari's collapsing chrome on iPad — see body comment */
  max-height: calc(100dvh - 64px);
  overflow: auto;
  color: #F5F7FB;
  font-family: var(--display);
}
.pair-overlay .pair-card h2 {
  margin: 0 0 6px;
  font-family: var(--display);
  font-weight: 500; font-size: 26px;
  color: #fff; letter-spacing: -0.005em;
}
.pair-overlay .pair-lede {
  margin: 0 0 16px;
  color: #97A2BE;
  font-size: 15px; line-height: 1.5;
  font-family: var(--sans);
}
.pair-overlay .pair-room-code {
  font-family: var(--mono);
  font-size: clamp(56px, 7vw, 88px);
  font-weight: 700;
  letter-spacing: 0.22em;
  color: #A8E862;
  text-shadow: 0 0 36px rgba(168, 232, 98, 0.55);
  margin: 14px 0 26px;
  text-align: center;
}
.pair-overlay .pair-grid {
  /* Two columns (Canada race-week re-enable, 2026-05-11): the modal
   * shows the big QR on the left and the typed-code steps on the right
   * so a phone operator has both pair paths visible at once. */
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 28px;
  align-items: start;
}
.pair-overlay .pair-qr-wrap {
  /* Re-enabled for Canada race-week (2026-05-11) alongside the topbar
   * mini-QR. White frame preserves the QR quiet-zone against the dark
   * modal backdrop so the rotating token scans reliably across cameras. */
  background: #fff;
  padding: 14px;
  border-radius: 18px;
  box-shadow: 0 0 28px rgba(168, 232, 98, 0.20);
  align-self: start;
}
.pair-overlay .pair-qr {
  width: 100%; height: auto; max-width: 420px; display: block;
}
.pair-overlay .pair-qr-cap {
  font-family: var(--mono); font-size: 10px;
  color: #001E58; letter-spacing: 0.18em; text-transform: uppercase;
  text-align: center;
}
.pair-overlay .pair-steps {
  display: flex; flex-direction: column; gap: 16px;
  color: #C9D0E2; font-size: 15px; line-height: 1.55;
  font-family: var(--sans);
}
.pair-overlay .pair-steps ol {
  margin: 0; padding-left: 24px;
  display: flex; flex-direction: column; gap: 8px;
}
.pair-overlay .pair-steps ol li::marker { color: #5DD2FF; font-weight: 700; }
.pair-overlay .pair-steps strong { color: #A8E862; font-weight: 700; }
.pair-overlay .pair-or {
  font-family: var(--mono); font-size: 10px;
  letter-spacing: 0.22em; text-transform: uppercase;
  color: rgba(255,255,255,0.55);
  border-top: 1px dashed rgba(255,255,255,0.18);
  padding-top: 14px; text-align: center;
}
.pair-overlay .pair-url-row {
  display: flex; gap: 10px; align-items: center;
}
.pair-overlay .pair-url {
  flex: 1 1 auto; min-width: 0;
  font-family: var(--mono); font-size: 12px;
  background: rgba(0, 0, 0, 0.40);
  border: 1px solid rgba(93, 210, 255, 0.32);
  color: #5DD2FF;
  padding: 9px 12px; border-radius: 8px;
  word-break: break-all;
  line-height: 1.35;
}
.pair-overlay .pair-copy {
  appearance: none; border: 1px solid rgba(93, 210, 255, 0.55);
  background: rgba(6, 106, 254, 0.18); color: #fff;
  padding: 10px 16px; border-radius: 8px;
  font: inherit; font-family: var(--mono);
  font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase;
  cursor: pointer;
  transition: background .2s ease;
}
.pair-overlay .pair-copy:hover { background: rgba(6, 106, 254, 0.32); }
.pair-overlay .pair-room-hint {
  font-family: var(--mono); font-size: 12px;
  color: rgba(255,255,255,0.65);
  letter-spacing: 0.10em;
}
.pair-overlay .pair-room-hint strong {
  color: #A8E862; letter-spacing: 0.20em; font-size: 14px;
}
.pair-overlay .pair-close {
  position: absolute; top: 14px; right: 18px;
  appearance: none; background: transparent; border: 0;
  color: rgba(255,255,255,0.55);
  font-size: 32px; line-height: 1; cursor: pointer;
  padding: 4px 12px;
  transition: color .2s ease;
}
.pair-overlay .pair-close:hover { color: #fff; }

/* ============================================================
 * LANDING PAGE
 * ============================================================ */
.shell {
  flex: 1 1 auto;
  display: flex; flex-direction: column;
  width: 100%;
  max-width: 1680px;
  margin: 0 auto;
  padding: 32px clamp(16px, 3vw, 48px) 64px;
  gap: 32px;
}

.hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: end;
  gap: 24px;
  padding: 8px 0 0;
}
.hero h1 {
  margin: 0;
  font-family: var(--display);
  font-size: clamp(28px, 3.4vw, 44px);
  font-weight: 500;
  line-height: 1.05;
  letter-spacing: -0.005em;
  color: var(--ink-0);
}
.hero h1 .accent {
  background: linear-gradient(120deg, var(--cobalt-bright), #b6c8ff 60%, var(--violet));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.hero .lede {
  margin-top: 12px;
  font-size: clamp(13px, 1.2vw, 16px);
  color: var(--ink-2);
  max-width: 64ch; line-height: 1.55;
}
.hero .meta-strip {
  display: flex; gap: 12px; flex-wrap: wrap;
  align-items: center;
  font-family: var(--mono); font-size: 10px;
  letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--ink-3);
}
.hero .meta-strip .dot { color: var(--cobalt-bright); }
.hero .meta-strip .partner-tag {
  display: inline-flex; align-items: center; gap: 6px;
  color: var(--ink-2);
}
.hero .meta-strip .partner-tag img {
  height: 12px; width: auto;
  filter: grayscale(0.1);
}
.hero .meta-strip .partner-tag span { color: var(--ink-3); }

/* card grid */
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 360px), 1fr));
  gap: 20px;
}

/* Hero layout — Miami demo ships with the Pit Wall card alone, paired
 * with a big VCARB livery hero photo on the right so the page doesn't
 * look empty after the other three cards retire. The card stays a fixed
 * width on the left and the hero panel takes the remaining width on
 * the row; below ~880px the two stack so the page still reads on a
 * narrower display. */
.cards.cards-hero-layout {
  grid-template-columns: minmax(320px, 420px) minmax(0, 1fr);
  align-items: stretch;
  /* Cap the row height so the landing reads as one composed surface
   * (Pit Wall card on the left, Strategy Hero on the right) instead
   * of two stacked tall panels. The clamp lets it grow to ~640px on
   * 4K monitors and shrink to ~440px on smaller laptops, always
   * leaving room for the lede + status row in the same viewport.
   * dvh tracks Safari's collapsing chrome on iPad — see body comment. */
  grid-auto-rows: clamp(440px, calc(100dvh - var(--topbar-h) - 320px), 640px);
}
.cards.cards-hero-layout > .card {
  min-height: 0;
}
/* Strategy Hero panel — section 8 of the handoff.
 *   • Light gradient surface (--gradient-hero-surface)
 *   • Border light (--border-light)
 *   • Radius xl (20px) — matches Pit Wall card per user request
 *     ("rounded corners like the other graphic")
 *   • shadow-hero (Electric Blue 30 ambient under-glow)
 *
 * The strategy-hero.png image is the fully-designed final asset
 * (1024×576, 16:9). It carries the headline, VCARB lockup, car,
 * subtitle, and telemetry overlay all baked in — this container
 * just frames it.
 */
.hero-art {
  position: relative;
  border-radius: var(--radius-xl);
  overflow: hidden;
  border: var(--border-light);
  background: var(--gradient-hero-surface);
  box-shadow: var(--shadow-hero);
  display: block;
  height: 100%;
  transition: transform 180ms ease, box-shadow 180ms ease, border-color 180ms ease;
}
.hero-art:hover {
  transform: translateY(-2px);
  border-color: rgba(0, 179, 255, 0.42);
  box-shadow: var(--shadow-hero), 0 0 0 1px rgba(0, 179, 255, 0.22);
}
.hero-art-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center center;
  display: block;
  transition: transform .45s ease;
}
.hero-art:hover .hero-art-img { transform: scale(1.015); }
.hero-art--strategy { /* keep selector around for back-compat — handoff styling lives on .hero-art */ }
.hero-art-img--strategy {
  /* contain (not cover) so the VCARB×Cash App lockup at the top-left
   * of the source image is never cropped. The 1024×576 strategy-hero
   * asset is already framed exactly the way the handoff expects
   * (light bg, car right, safe-zone left); contain just guarantees
   * the whole framed design is visible inside the panel. The panel's
   * own --gradient-hero-surface bg (white → light blue, left to right)
   * matches the image's natural left + right edges so any side
   * letterbox bars are invisible. */
  object-fit: contain;
  object-position: center center;
  filter: saturate(1.04);
}
@media (max-width: 880px) {
  .cards.cards-hero-layout {
    grid-template-columns: minmax(0, 1fr);
  }
  .hero-art {
    aspect-ratio: 16 / 9;
    min-height: 0;
  }
}
/* Cards — section 7 of the handoff for the Pit Wall card, with the
 * generic .card chrome below for back-compat (other slugs still use
 * the legacy art-on-top + dynamic-body layout). Per the handoff:
 *   • Dark gradient bg
 *   • Border-blue-glow (Cloud Blue 68 @ 0.42 alpha)
 *   • Radius xl (20px) — rounded corners user explicitly requested
 *   • shadow-card-dark on default state
 *   • shadow-card-blue on hover (Cloud Blue glow lifts the card)
 */
.card {
  position: relative;
  display: flex; flex-direction: column;
  background: linear-gradient(180deg, rgba(11, 23, 43, 0.92), rgba(5, 11, 23, 0.98));
  border: var(--border-blue-glow);
  border-radius: var(--radius-xl);
  padding: 0;
  overflow: hidden;
  transition: transform 180ms ease, border-color 180ms ease, box-shadow 180ms ease;
  text-decoration: none; color: inherit;
  box-shadow: var(--shadow-card-dark);
}
.card:hover {
  transform: translateY(-2px);
  border-color: rgba(0, 179, 255, 0.72);
  box-shadow: var(--shadow-card-blue);
}

/* Image-only card variant — used for the Pit Wall slug whose card art
 * (final_pit_wall_card / VCARB_1.png) is a fully-designed mock with
 * the headline, eyebrow, description, chips, LIVE pill, and OPEN
 * DASHBOARD CTA all baked in. The image fills the card with
 * object-fit: cover so the rounded card corners crop the square-corner
 * source asset cleanly (per user request: "do the rounded corners like
 * the other graphic"). The card's bg matches the image's natural dark
 * navy padding so any over-crop bleeds invisibly. */
.card.card--image-only {
  padding: 0;
  border: var(--border-blue-glow);
  /* Match the Pit Wall card image's natural dark navy padding so any
   * letterbox bars from object-fit: contain are invisible against the
   * panel chrome. The image (VCARB_1.png, 486×836) is more portrait
   * than the card column, so contain leaves narrow side bars rather
   * than cropping the VCARB×Cash App lockup at the top. */
  background: #06122B;
  min-height: 100%;
}
.card.card--image-only .card-image-full {
  display: block;
  width: 100%;
  height: 100%;
  /* contain (not cover) so the VCARB lockup at the top of the source
   * image stays visible — cover was eating the bull silhouette before.
   * The card's border-radius + overflow:hidden on the parent .card
   * still gives the visual rounded corners the user asked for; the
   * image just sits inside that frame with matching navy bg behind. */
  object-fit: contain;
  object-position: center center;
  transition: transform .35s ease, filter .35s ease;
}
.card.card--image-only:hover .card-image-full {
  transform: scale(1.015);
  filter: saturate(1.05) brightness(1.04);
}
.card.card--image-only .card-spark {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 90px;
  pointer-events: none;
  opacity: 0.6;
}
.card .card-art {
  position: relative;
  aspect-ratio: 16 / 9;
  /* Dark navy fallback so the card-art zone reads as VCARB dark blue
   * even before the workbook thumbnail / hero PNG loads. */
  background: linear-gradient(160deg, #06091A 0%, #0F1B3A 55%, #16244F 100%);
  border-bottom: 1px solid var(--card-line);
  overflow: hidden;
}
/* Primary card art — the VCARB hero photography dropped into
 * portal/img/cards/<slug>.png. Sits above the workbook thumbnail and the
 * SVG schematic so the brand photo is what the eye lands on. The
 * onerror in portal.js reveals the workbook thumb if the PNG is missing. */
.card .card-art .hero {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: center;
  display: block;
  z-index: 2;
  background: var(--card-bg-bot);
  transition: transform .35s ease, filter .35s ease;
}
.card:hover .card-art .hero {
  transform: scale(1.025);
  filter: saturate(1.05) brightness(1.04);
}
.card .card-art .hero.failed { display: none; }

/* Tableau workbook preview thumbnail — fallback when no VCARB hero PNG
 * exists for the slug. portal.js toggles display:block when .hero fails. */
.card .card-art .thumb {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  z-index: 2;
  filter: saturate(1.05) contrast(1.04);
  background: rgba(11, 16, 32, 0.4);
  transition: transform .3s ease, filter .3s ease;
}
.card:hover .card-art .thumb {
  transform: scale(1.025);
  filter: saturate(1.1) contrast(1.06);
}
.card .card-art .thumb.failed { display: none; }
/* Subtle gradient overlay on top of the thumb so the badge + accent ring
 * stay legible even with bright workbook screenshots. */
.card .card-art .art-overlay {
  position: absolute; inset: 0; z-index: 3;
  pointer-events: none;
  background:
    linear-gradient(180deg, rgba(11, 16, 32, 0.05) 30%, rgba(11, 16, 32, 0.55) 100%),
    radial-gradient(120% 80% at 50% 0%, rgba(255,255,255,0.06), transparent 60%);
}
.card .card-art .schematic { position: absolute; inset: 14px; z-index: 1; }
.card .card-art .schematic .row {
  position: absolute; left: 0; right: 0; height: 14px;
  background: rgba(255, 255, 255, 0.05);
  border-radius: 4px;
}

/* Live-pace ribbon. Sits across the bottom third of the art area, on
 * top of the workbook thumbnail/schematic so the card reads as
 * actively streaming telemetry even at a glance. */
.card .card-art .card-spark {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 38%;
  width: 100%;
  display: block;
  z-index: 4;
  pointer-events: none;
  opacity: 0.92;
  mix-blend-mode: screen;
}
.card[data-live="1"] .card-art .card-spark {
  opacity: 1;
  filter: drop-shadow(0 0 6px rgba(157, 247, 156, 0.4));
}
.card .badge {
  position: absolute; top: 14px; right: 14px; z-index: 5;
  font-family: var(--mono); font-size: 9px; font-weight: 700;
  letter-spacing: 0.22em; text-transform: uppercase;
  padding: 5px 10px; border-radius: 999px;
  background: rgba(11, 16, 32, 0.78);
  backdrop-filter: blur(6px);
  border: 1px solid var(--line-soft);
  color: var(--ink-1);
}
.card.accent-cyan .badge   { color: var(--cyan);   border-color: rgba(90, 196, 224, 0.45); }
.card.accent-violet .badge { color: var(--violet); border-color: rgba(168, 143, 224, 0.45); }
.card.accent-lime .badge   { color: var(--lime);   border-color: rgba(168, 232, 98, 0.45); }
.card.accent-amber .badge  { color: var(--amber);  border-color: rgba(232, 184, 110, 0.45); }

/* Live pulse — added to .card[data-live="1"] when portal.js detects MQTT
 * is streaming and recent telemetry is flowing. The badge picks up a
 * green dot and a subtle glow so judges can see at a glance which
 * dashboards are reading from the live feed right now. */
.card[data-live="1"] .badge {
  border-color: rgba(157, 247, 156, 0.55);
  color: var(--lime);
  box-shadow: 0 0 18px rgba(157, 247, 156, 0.20);
}
.card[data-live="1"] .badge::before {
  content: '';
  display: inline-block;
  width: 6px; height: 6px;
  margin-right: 7px; vertical-align: middle;
  border-radius: 50%;
  background: var(--lime);
  box-shadow: 0 0 10px var(--lime);
  animation: cardLivePulse 1.4s ease-in-out infinite;
}
@keyframes cardLivePulse {
  0%, 100% { opacity: 1;   transform: scale(1); }
  50%      { opacity: 0.55; transform: scale(0.78); }
}

.card .card-body {
  padding: 22px 24px 24px;
  display: flex; flex-direction: column; gap: 14px;
  flex: 1 1 auto;
}
/* Card title — Avant Garde / Jost display family at a chunky 32px so
 * the wordmark reads as a brand statement (PIT WALL), not a heading.
 * The accent span is the LAST word of the title (set by cardHTML in
 * portal.js) and picks up the Electric Blue brand color so it pops
 * against the mostly-white head word, exactly like the design mock. */
.card .card-body .card-title {
  margin: 0;
  font-family: var(--display);
  font-size: 32px; font-weight: 700;
  color: var(--ink-0);
  letter-spacing: -0.005em;
  line-height: 1.0;
  display: flex; flex-wrap: wrap; gap: 0.3ch;
}
.card .card-body .card-title .title-accent {
  color: var(--cobalt);
  /* Subtle Electric Blue glow under the accent word so it reads as
   * "lit by data", per the design system's dark-mode principle. */
  text-shadow: 0 0 20px rgba(6, 106, 254, 0.55);
}
.card .card-body .sub {
  font-family: var(--mono);
  font-size: 10.5px; letter-spacing: 0.20em;
  text-transform: uppercase; color: var(--ink-2);
  margin-top: 2px;
}
.card .card-body .blurb {
  color: var(--ink-2); font-size: 14px; line-height: 1.55;
}
.card .chips {
  display: flex; flex-wrap: wrap; gap: 8px;
  margin-top: 4px;
}
.card .chips span {
  font-family: var(--mono);
  font-size: 10px; letter-spacing: 0.20em;
  text-transform: uppercase; font-weight: 600;
  padding: 7px 12px; border-radius: 999px;
  border: 1px solid var(--cobalt-soft);
  color: var(--cobalt-bright);
  background: rgba(6, 106, 254, 0.06);
  transition: background .2s ease, border-color .2s ease, color .2s ease;
}
.card:hover .chips span {
  border-color: var(--cobalt);
  background: rgba(6, 106, 254, 0.14);
  color: var(--ink-0);
}
.card .open-cta {
  display: flex; align-items: center; justify-content: space-between;
  margin-top: 6px;
  padding-top: 16px;
  border-top: 1px solid rgba(0, 179, 254, 0.18);
  font-family: var(--mono); font-size: 13px; font-weight: 700;
  letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--cobalt-bright);
  transition: color .2s ease, transform .2s ease;
}
.card .open-cta span:last-child {
  font-size: 18px;
  transition: transform .25s ease;
}
.card:hover .open-cta { color: var(--ink-0); }
.card:hover .open-cta span:last-child { transform: translateX(4px); }
/* Per-accent overrides — Pit Wall uses cobalt by default; other slugs
 * keep their accent color (cyan/violet/lime/amber) so the back-compat
 * grid still looks right when the other cards return. */
.card.accent-cyan   .open-cta { color: var(--cyan); }
.card.accent-violet .open-cta { color: var(--violet); }
.card.accent-lime   .open-cta { color: var(--lime); }
.card.accent-amber  .open-cta { color: var(--amber); }

/* badge--live: top-right LIVE pill on the card art, per the design mock.
 * Built on the existing `.badge` so the legacy slug-specific badge
 * styling still applies; this override just guarantees a green dot +
 * pulse + GREEN border when the card is in the LIVE state. The badge
 * sits on the card-art image so it carries a strong dark backdrop to
 * stay legible against bright photography. */
.card .badge--live {
  display: inline-flex; align-items: center; gap: 7px;
  background: rgba(0, 0, 0, 0.55);
  border: 1px solid rgba(52, 199, 89, 0.65);
  color: var(--status-ok);
  backdrop-filter: blur(4px);
}
.card .badge--live i {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--status-ok);
  box-shadow: 0 0 10px var(--status-ok);
  animation: cardLivePulse 1.4s ease-in-out infinite;
}

/* schematic accent strokes per card — purely decorative, hint at the
   shape of each dashboard so the cards feel custom without needing
   real screenshots. */
.card.accent-cyan   .card-art .schematic .row.accent { background: linear-gradient(90deg, transparent, var(--cyan-glow)); }
.card.accent-violet .card-art .schematic .row.accent { background: linear-gradient(90deg, transparent, var(--violet-glow)); }
.card.accent-lime   .card-art .schematic .row.accent { background: linear-gradient(90deg, transparent, var(--lime-glow)); }
.card.accent-amber  .card-art .schematic .row.accent { background: linear-gradient(90deg, transparent, var(--amber-glow)); }

/* ============================================================
 * STATUS BLOCK on landing
 * ============================================================ */
.status-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr));
  gap: 16px;
}
.status-card {
  background: var(--bg-0);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  padding: 14px 16px;
}
.status-card .lbl {
  font-family: var(--mono); font-size: 9px; letter-spacing: 0.22em;
  text-transform: uppercase; color: var(--ink-3);
}
.status-card .val {
  font-family: var(--mono); font-size: 18px; font-weight: 600;
  color: var(--ink-0); margin-top: 4px;
  font-variant-numeric: tabular-nums; letter-spacing: 0.04em;
}
.status-card.ok .val   { color: var(--lime); }
.status-card.warn .val { color: var(--amber); }
.status-card.bad .val  { color: var(--magenta); }

/* ============================================================
 * VIEWER PAGE
 * ============================================================ */
/* Dashboard viewer dark mode — the body--viewer class on the viewer
 * page swaps the bright portal stage for the true-dark dashboard
 * surface (#050B17 per the design system spec). The Electric Blue
 * topbar stays brand-bright on top, but everything below it sits on
 * the dark surface so race-day data ops feel "illuminated by data,
 * not just dim". */
body.body--viewer {
  background: var(--bg-stage-dark);
}

/* When the viewer-toolbar is hidden (UX feedback round 2 removed it
 * entirely), collapse the toolbar row out of the grid template so the
 * frame + rail get the full vertical space. Without this, the grid's
 * auto row still claims a sliver of height from the agent rail's
 * intrinsic content (the rail spans both rows), which clips the
 * bottom of the dashboard panels. */
body.body--viewer .viewer-shell.with-rail {
  grid-template-rows: 1fr;
  grid-template-areas: "frame rail";
}

.viewer-shell {
  display: flex; flex-direction: column;
  flex: 1 1 auto;
  width: 100%;
  /* dvh fixes the iPad clip-below-viewport bug. The viewer-shell hosts
   * BOTH the Tableau dashboard iframe and the TORO agent rail; when
   * Safari's URL bar + tab bar weren't subtracted from `vh`, both
   * iframes overflowed below the visible area. With `dvh` the shell
   * tracks the live viewport so the agent rail's bottom edge stays
   * on-screen as Safari's chrome collapses on scroll. */
  height: calc(100dvh - var(--topbar-h));
  background: var(--bg-stage-dark);
}

/* Viewer page chrome lockdown (UX feedback round 2):
 *   • Hide the .crumb subtitle line (TABLEAU CLOUD · ... text)
 *   • Hide the .mode-chip in the center (PORTAL pill)
 *   • Hide the SIGNED IN user chip on the right (but KEEP the pair
 *     chip — the venue laptop is locked-down kiosk-mode, no keyboard
 *     or mouse, so the pair chip is the operator's only path back in.
 *     Without it any new operator on a shift change is stranded with
 *     no way to pair their phone).
 *   • The viewer-toolbar is hidden inline via portal.js (style.display
 *     = 'none') so we don't need a CSS selector for it.
 * Landing page (no body--viewer class) keeps all the chrome it had. */
body.body--viewer .crumb .sub  { display: none; }
body.body--viewer .tb-center   { display: none; }
body.body--viewer .user-chip   { display: none; }
/* Topbar in viewer mode keeps a left + right column so the pair chip
 * still has its native flex-end home. The center column is hidden via
 * the rule above. */
body.body--viewer .topbar {
  grid-template-columns: 1fr auto;
}
/* In viewer/kiosk mode the topbar pair chip is one of two visible
 * scan targets (the other is the bigger always-on tile pinned to the
 * agent rail). Bump it up so the QR is genuinely scannable from
 * arm's length on a venue display — the v137 default of 46px was
 * too dense to scan a long pairing-token URL. v138 PM also encodes a
 * SHORT URL into the QR so 90px is now a low-density code. */
body.body--viewer .pair-chip {
  padding: 8px 16px 8px 8px;
  gap: 14px;
}
body.body--viewer .pair-chip-qr {
  width: 90px; height: 90px;
  padding: 4px;
}
body.body--viewer .pair-chip-room {
  font-size: 17px;
}
body.body--viewer .pair-chip-code {
  font-size: 16px;
}
body.body--viewer .pair-chip-label {
  font-size: 10px;
}
body.body--viewer .pair-chip-hint {
  font-size: 9.5px;
}
.viewer-toolbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px;
  padding: 10px clamp(12px, 2vw, 22px);
  background: linear-gradient(180deg, rgba(11, 16, 32, 0.85), rgba(11, 16, 32, 0.55));
  border-bottom: 1px solid var(--line-soft);
  font-family: var(--mono); font-size: 11px;
  letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--ink-2);
}
.viewer-toolbar .left,
.viewer-toolbar .right { display: flex; align-items: center; gap: 12px; }
.viewer-toolbar a.back {
  display: inline-flex; align-items: center; gap: 6px;
  color: var(--ink-2);
  border: 1px solid var(--line-soft);
  padding: 7px 12px; border-radius: 999px;
  transition: color .2s ease, border-color .2s ease;
}
.viewer-toolbar a.back:hover { color: var(--cobalt-bright); border-color: var(--cobalt); }
.viewer-toolbar .pill {
  padding: 5px 9px; border: 1px solid var(--line-soft);
  border-radius: 999px; color: var(--ink-3);
  background: rgba(34, 44, 80, 0.4);
}
.viewer-toolbar .actions button {
  appearance: none; border: 1px solid var(--line-soft);
  background: rgba(34, 44, 80, 0.45);
  color: var(--ink-2);
  padding: 7px 12px; border-radius: 999px;
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.18em;
  text-transform: uppercase; cursor: pointer;
  transition: color .2s ease, border-color .2s ease;
}
.viewer-toolbar .actions button:hover { color: var(--cobalt-bright); border-color: var(--cobalt); }
.viewer-toolbar .actions button.primary {
  color: #fff;
  border-color: var(--cobalt);
  background: linear-gradient(180deg, var(--cobalt), var(--cobalt-deep));
  box-shadow: 0 0 12px var(--cobalt-glow);
}
.viewer-toolbar .actions button.primary:hover {
  color: #fff;
  background: linear-gradient(180deg, var(--cobalt-bright), var(--cobalt));
  border-color: var(--cobalt-bright);
}

/* Device picker — desktop / tablet / phone segmented control */
.viewer-toolbar .device-pick {
  display: inline-flex;
  border: 1px solid var(--line-soft);
  border-radius: 999px;
  background: rgba(34, 44, 80, 0.45);
  padding: 2px;
}
.viewer-toolbar .device-pick button {
  appearance: none;
  border: 0; background: transparent;
  color: var(--ink-3);
  padding: 6px 12px;
  font-family: var(--mono); font-size: 10px; font-weight: 600;
  letter-spacing: 0.18em; text-transform: uppercase;
  border-radius: 999px;
  cursor: pointer;
  transition: color .2s ease, background .2s ease;
}
.viewer-toolbar .device-pick button:hover { color: var(--ink-1); }
.viewer-toolbar .device-pick button.on {
  color: #fff;
  background: var(--cobalt-soft);
  box-shadow: inset 0 0 0 1px var(--cobalt);
}

@media (max-width: 640px) {
  .viewer-toolbar .device-pick { display: none; }
}

.viewer-frame {
  flex: 1 1 auto;
  position: relative;
  display: flex; align-items: stretch; justify-content: stretch;
  /* Embedded Tableau iframe wrapper. We keep the legacy --bg-stage-cobalt
   * token here (now Electric Blue 30 #022AC0) so the iframe's own outer
   * letterbox padding (which the relay's published .twb still paints in
   * a similar deep blue) bleeds into the wrapper without a visible step. */
  background: var(--bg-stage-cobalt);
  overflow: hidden;
}
.viewer-frame tableau-viz {
  width: 100%; height: 100%;
  --tableau-loading-spinner-color: var(--cobalt-bright);
}
.viewer-overlay {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 16px;
  color: var(--ink-2);
  text-align: center;
  padding: 32px;
  background: var(--bg-stage-cobalt);
  pointer-events: none;
  transition: opacity .35s ease;
}
.viewer-overlay.hidden { opacity: 0; pointer-events: none; }
.viewer-overlay .ring {
  width: 56px; height: 56px; border-radius: 50%;
  border: 3px solid rgba(106, 138, 255, 0.18);
  border-top-color: var(--cobalt-bright);
  animation: spin 1s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.viewer-overlay .lbl {
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.22em;
  text-transform: uppercase; color: var(--ink-2);
}
.viewer-overlay .detail {
  font-size: 13px; color: var(--ink-3); max-width: 56ch; line-height: 1.6;
}
.viewer-overlay.error .ring { display: none; }
.viewer-overlay.error .lbl  { color: var(--magenta); }

/* ============================================================
 * AGENT RAIL — pinned to the right of every dashboard so the
 * conversation persists as the user hops between Pit Wall, Race
 * Recap, Driver Deep Dive, and Driver History. The viz column
 * shrinks; the agent stays anchored.
 *
 * Width matches the v3 dashboard rail (380px desktop, 340px on
 * tighter screens) so the agent rail is a single mental object
 * across the whole product surface.
 * ============================================================ */
.viewer-shell.with-rail {
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--rail-w, 380px);
  grid-template-rows: auto 1fr;
  grid-template-areas:
    "toolbar rail"
    "frame   rail";
  gap: 0;
}
.viewer-shell.with-rail .viewer-toolbar { grid-area: toolbar; }
.viewer-shell.with-rail .viewer-frame   { grid-area: frame; }

.agent-rail {
  grid-area: rail;
  display: flex; flex-direction: column;
  min-width: 0; min-height: 0;
  /* v5 — agent rail sits on the dark dashboard surface, edged in
   * Electric Blue so it reads as a distinctive column not just
   * dashboard chrome. The top of the rail picks up an Electric Blue
   * tint that fades into the dark dashboard floor; the left edge is
   * a 3px Electric Blue gradient line + outer Cloud Blue glow so the
   * agent feels "lit" against the surrounding telemetry panels. */
  background:
    linear-gradient(180deg,
      rgba(6, 106, 254, 0.22) 0%,
      rgba(6, 106, 254, 0.08) 14%,
      var(--bg-0) 28%,
      var(--bg-stage-dark) 100%);
  border-left: 3px solid var(--cobalt);
  box-shadow:
    inset 2px 0 0 0 rgba(0, 179, 254, 0.35),
    -8px 0 32px -16px var(--cobalt-glow);
  position: relative;
}
/* Ambient Cloud Blue accent ribbon at the top of the rail — picks up
 * the topbar brand color and pulls it down into the agent column. */
.agent-rail::before {
  content: "";
  position: absolute;
  top: 0; left: 3px; right: 0;
  height: 3px;
  background: linear-gradient(90deg,
    var(--cobalt) 0%,
    var(--cobalt-bright) 40%,
    var(--cobalt) 100%);
  box-shadow: 0 0 18px var(--cobalt-glow);
  pointer-events: none;
  z-index: 1;
}
.agent-rail iframe {
  flex: 1 1 auto;
  width: 100%; height: 100%;
  border: 0; display: block;
  background: transparent;
}

/* Collapse the rail on phones — the agent moves below the viz frame
 * (stacked) so neither column gets squeezed to nothing. */
@media (max-width: 900px) {
  .viewer-shell.with-rail {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto 1fr 360px;
    grid-template-areas:
      "toolbar"
      "frame"
      "rail";
  }
  /* Toolbar is hidden via inline style in viewer mode — collapse the
   * toolbar row here too so the stacked frame + rail get the full
   * vertical space. */
  body.body--viewer .viewer-shell.with-rail {
    grid-template-rows: 1fr 360px;
    grid-template-areas:
      "frame"
      "rail";
  }
  .agent-rail { border-left: 0; border-top: 1px solid var(--cobalt); }
}

/* ============================================================
 * RESPONSIVE — iPad portrait / landscape, big screens, mobile
 * ============================================================ */

/* Big screens (1920+): widen container, lift cards a touch, and give the
 * agent rail more room since 4K race-day monitors give us the headroom
 * to make the agent feel like a first-class column instead of a side
 * rail. */
@media (min-width: 1920px) {
  .shell { max-width: 1840px; }
  .card .card-body { padding: 22px 26px 24px; }
  .card .card-body h2 { font-size: 22px; }
  .hero h1 { font-size: 48px; }
  :root { --rail-w: 440px; }
}

/* iPad landscape (1024-1366) — 2-3 cards per row, slightly tighter spacing.
 * Also narrow the agent rail so the viz column has room to breathe. */
@media (min-width: 1024px) and (max-width: 1366px) {
  .shell { padding-left: 24px; padding-right: 24px; }
  .cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  :root { --rail-w: 340px; }
}

/* iPad portrait / small laptop (768-1023). */
@media (max-width: 1023px) {
  /* Bring the topbar height back down on tablet so it doesn't eat
   * half the viewport — the wordmark scales down with the breakpoint. */
  :root { --topbar-h: 80px; }
  .topbar { grid-template-columns: 1fr auto; padding: 0 16px; }
  .tb-center { display: none; }
  .cards { grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr)); }
  .hero { grid-template-columns: 1fr; }
  .viewer-toolbar { font-size: 10px; }
  .partner-mark .pm-txt .pm-lbl { display: none; }
  /* Scale the wordmark down so the page-title crumb still fits beside
   * it on tablet widths. */
  .logo-box .logo-txt .wordmark { font-size: 28px; letter-spacing: -0.005em; }
  .logo-box img.vcarb-logo { height: 44px; }
  /* Tighten the gradient stops a touch since the bar is narrower —
   * keeps the same dark-navy → Cloud Blue 68 sweep as desktop. */
  .topbar {
    background:
      radial-gradient(500px 160px at 100% 100%, rgba(255, 255, 255, 0.10), transparent 60%),
      linear-gradient(90deg,
        #001E58 0%, #022AC0 30%,
        #066AFE 65%, #00B3FF 100%);
  }
}

/* Phone (≤640): single column, stacked toolbar, hide non-essential chips.
 * The VCARB primary brand stays visible — the wordmark collapses to just
 * the bull mark so the partner pill still has room. */
@media (max-width: 640px) {
  :root { --topbar-h: 56px; }
  .topbar { gap: 12px; padding: 0 12px; }
  .logo-box { padding: 4px 8px; gap: 8px; }
  /* Phone collapse: drop the wordmark text and shrink the lockup to a
   * single-row icon — desktop wordmark is too wide for 56 px topbar. */
  .logo-box .logo-txt { display: none; }
  .logo-box img.vcarb-logo { height: 32px; }
  .partner-mark .pm-txt { display: none; }
  .crumb .title { font-size: 15px; }
  .crumb .sub { display: none; }
  .user-chip { display: none; }
  .shell { padding: 20px 16px 48px; gap: 24px; }
  .cards { grid-template-columns: 1fr; gap: 14px; }
  .card .card-art { aspect-ratio: 16 / 7; }
  .viewer-toolbar { flex-wrap: wrap; padding: 8px 12px; }
  .viewer-toolbar .pill { display: none; }
  .viewer-toolbar a.back { padding: 6px 10px; }
}

/* Touch device niceties: bigger hit targets, no hover transforms. */
@media (hover: none) {
  .card:hover { transform: none; }
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}

/* ============================================================
 * SIM FAB — reviewer-facing "Start replay" overlay
 *
 * Anchored bottom-right of the embedded Tableau viz. Visible on
 * the pit-wall slug only (mountSimFab() in portal.js scopes it).
 * Color states:
 *   • idle           — Cloud Blue chip, primary CTA
 *   • running/paused — Lime chip with pulsing dot
 *   • live-lockout   — Magenta chip, no actions, banner explains
 * ============================================================ */
.sim-fab {
  position: absolute;
  right: 16px;
  bottom: 16px;
  z-index: 80;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 10px;
  font-family: 'Salesforce Sans', Inter, system-ui, sans-serif;
  pointer-events: none;
}
.viewer-frame { position: relative; }
.sim-fab > * { pointer-events: auto; }

.sim-fab__chip {
  --chip-bg: linear-gradient(135deg, var(--cobalt-deep) 0%, var(--cobalt) 100%);
  --chip-shadow: 0 12px 28px rgba(0, 0, 0, 0.45), 0 0 0 1px rgba(255, 255, 255, 0.08) inset;
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding: 10px 18px 10px 14px;
  border: 0;
  border-radius: 999px;
  background: var(--chip-bg);
  color: var(--ink-0);
  font: 600 13px/1.1 'Salesforce Sans', Inter, system-ui, sans-serif;
  letter-spacing: 0.01em;
  cursor: pointer;
  box-shadow: var(--chip-shadow);
  transition: transform 120ms ease, box-shadow 120ms ease, background 120ms ease;
}
.sim-fab__chip:hover {
  transform: translateY(-1px);
  box-shadow: 0 16px 32px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.14) inset;
}
.sim-fab__chip:focus-visible {
  outline: 2px solid var(--cobalt-bright);
  outline-offset: 3px;
}
.sim-fab__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px; height: 26px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.16);
  font-size: 13px;
  line-height: 1;
}
.sim-fab__copy {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
}
.sim-fab__label {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.02em;
}
.sim-fab__sub {
  font-size: 11px;
  font-weight: 500;
  color: rgba(255, 255, 255, 0.75);
  letter-spacing: 0.01em;
}
.sim-fab__pulse { display: none; }

/* Running / paused — switch to lime accent + pulsing dot */
.sim-fab[data-state="running"] .sim-fab__chip,
.sim-fab[data-state="paused"]  .sim-fab__chip {
  --chip-bg: linear-gradient(135deg, #1F8A4D 0%, var(--lime, #34C759) 100%);
}
.sim-fab[data-state="running"] .sim-fab__pulse,
.sim-fab[data-state="paused"]  .sim-fab__pulse {
  display: inline-block;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.85);
  animation: simFabPulse 1.6s ease-out infinite;
}
@keyframes simFabPulse {
  0%   { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.65); }
  70%  { box-shadow: 0 0 0 10px rgba(255, 255, 255, 0); }
  100% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); }
}

/* Live lockout — red/magenta chip, no clickable affordance */
.sim-fab[data-state="live-lockout"] .sim-fab__chip {
  --chip-bg: linear-gradient(135deg, #7A0E2E 0%, var(--magenta, #C72167) 100%);
  cursor: default;
}
.sim-fab[data-state="live-lockout"] .sim-fab__chip:hover {
  transform: none;
}

/* Panel — opens upward from the chip; fits in the bottom-right corner
 * without overlapping the agent rail (which is in a separate grid column). */
.sim-fab__panel {
  width: 320px;
  max-width: calc(100vw - 32px);
  background: linear-gradient(180deg, var(--bg-1) 0%, var(--bg-0) 100%);
  border: 1px solid rgba(0, 179, 255, 0.28);
  border-radius: 14px;
  box-shadow: 0 24px 56px rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(255, 255, 255, 0.04) inset;
  padding: 14px 16px 14px;
  color: var(--ink-0);
  font-size: 13px;
  line-height: 1.45;
  order: -1; /* panel above chip when both visible */
}
.sim-fab__panel[hidden] { display: none; }

.sim-fab__panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 6px;
}
.sim-fab__panel-head strong {
  font-size: 14px;
  letter-spacing: 0.02em;
}
.sim-fab__close {
  background: transparent;
  border: 0;
  color: var(--ink-1);
  font-size: 18px;
  line-height: 1;
  width: 24px; height: 24px;
  border-radius: 6px;
  cursor: pointer;
}
.sim-fab__close:hover { color: var(--ink-0); background: rgba(255, 255, 255, 0.06); }

.sim-fab__hint {
  margin: 4px 0 12px;
  color: var(--ink-1);
  font-size: 12px;
}
.sim-fab__share {
  margin: 12px 0 0;
  color: var(--ink-2);
  font-size: 11px;
  line-height: 1.4;
  font-style: italic;
}

.sim-fab__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin: 8px 0;
}
.sim-fab__row-label {
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-2);
  font-weight: 600;
}
.sim-fab__row--stacked {
  flex-direction: column;
  align-items: stretch;
  gap: 4px;
}
.sim-fab__row--stacked .sim-fab__row-label { text-align: left; }
.sim-fab__session-select {
  width: 100%;
  background: rgba(255, 255, 255, 0.05);
  color: var(--ink-0);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 8px;
  padding: 8px 10px;
  font-size: 13px;
  font-family: 'Salesforce Sans', Inter, system-ui, sans-serif;
  cursor: pointer;
}
.sim-fab__session-select:focus-visible {
  outline: 2px solid var(--cobalt-bright);
  outline-offset: 2px;
}
.sim-fab__session-select:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}
.sim-fab__session-select optgroup {
  font-style: normal;
  font-weight: 700;
  background: var(--bg-0);
  color: var(--cobalt-bright);
}
.sim-fab__session-select option {
  background: var(--bg-1);
  color: var(--ink-0);
}

.sim-fab__speed {
  display: inline-flex;
  gap: 4px;
  background: rgba(255, 255, 255, 0.04);
  padding: 3px;
  border-radius: 8px;
  border: 1px solid rgba(255, 255, 255, 0.06);
}
.sim-fab__speed button {
  background: transparent;
  color: var(--ink-1);
  border: 0;
  padding: 5px 9px;
  border-radius: 5px;
  cursor: pointer;
  font-size: 12px;
  font-weight: 600;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  transition: background 120ms ease, color 120ms ease;
}
.sim-fab__speed button:hover { color: var(--ink-0); background: rgba(255, 255, 255, 0.06); }
.sim-fab__speed button.active {
  background: var(--cobalt);
  color: #001428;
}

.sim-fab__live-banner {
  margin: 10px 0 4px;
  padding: 8px 10px;
  border-radius: 8px;
  background: rgba(199, 33, 103, 0.12);
  border: 1px solid rgba(199, 33, 103, 0.4);
  color: #FFD0E0;
  font-size: 11.5px;
  line-height: 1.4;
}
.sim-fab__live-banner strong { color: #fff; }

.sim-fab__error {
  margin: 8px 0 0;
  padding: 8px 10px;
  border-radius: 8px;
  background: rgba(255, 102, 102, 0.10);
  border: 1px solid rgba(255, 102, 102, 0.35);
  color: #FFD2D2;
  font-size: 11.5px;
  line-height: 1.4;
}

.sim-fab__actions {
  display: flex;
  gap: 8px;
  margin-top: 12px;
  flex-wrap: wrap;
}
.sim-fab__actions > button {
  flex: 1 1 auto;
  border: 0;
  border-radius: 8px;
  padding: 10px 14px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  letter-spacing: 0.01em;
  transition: filter 120ms ease, transform 120ms ease;
}
.sim-fab__actions > button:hover { filter: brightness(1.1); }
.sim-fab__actions > button:active { transform: scale(0.97); }
.sim-fab__actions > button:disabled { opacity: 0.5; cursor: wait; }
.sim-fab__primary  { background: var(--cobalt); color: #001428; }
.sim-fab__secondary { background: rgba(255, 255, 255, 0.10); color: var(--ink-0); }
.sim-fab__danger   { background: rgba(199, 33, 103, 0.85); color: #fff; }

/* Phone breakpoint — keep the chip but compress the panel + drop the
 * "share this URL" hint, since on phone the user almost certainly is
 * the operator and already knows. */
@media (max-width: 640px) {
  .sim-fab { right: 10px; bottom: 10px; }
  .sim-fab__panel { width: calc(100vw - 24px); padding: 12px; }
  .sim-fab__share { display: none; }
}
