/* offline-blind — Wingman agent dashboard, offline state.
 *
 * Self-contained on purpose: the shared frontend/ui/ layer is a different
 * (seed-derived) token system and this page must not silently redefine it.
 * Provenance and open questions are in NOTES.md.
 *
 * Canonical composition target: 1672 x 941 CSS px, DPR 1.
 */

/* ── §1 Foundation ─────────────────────────────────────────────────────── */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html,
body {
  width: 100%;
  min-height: 100%;
  margin: 0;
}


/* ── §2 Design tokens ──────────────────────────────────────────────────── */

:root {
  --text-primary: #eee8dd;
  --text-secondary: #c6beb2;
  --text-muted: #8d877f;

  /* Metallic tiers. Gold is NOT one colour reused everywhere — the tier
   * changes with material and importance: highlight for lit edges, core for
   * headings and values, medium for icon strokes, dark for borders, hairline
   * for the barely-there rules. */
  --gold-highlight: #edcc91;
  --gold-core: #d6a85c;
  --gold-medium: #b7843c;
  --gold-dark: #765020;
  --gold-hairline: rgba(184, 126, 48, 0.25);

  --border-bright: rgba(221, 163, 75, 0.52);

  /* Live/online tiers. Same material logic as gold — bright for lit edges, core
   * for the energized ring, medium for the mid rim, dark and deep for recessed
   * surfaces. Deliberately desaturated away from #0f0: this has to read as
   * illuminated hardware, not as a gaming LED. */
  --online-green-bright: #b8ff58;
  --online-green-core: #8fe83f;
  --online-green-medium: #5ebd2a;
  --online-green-dark: #17441d;
  --online-green-deep: #071d0d;

  /* The glow tiers run WARMER than the ring tiers — chartreuse, not lime. Light
   * this colour spilling onto near-black reads as a warm lamp; the same light
   * at the ring's hue reads as a backlit LCD. */
  --online-glow-strong: rgba(196, 255, 84, 0.42);
  --online-glow-medium: rgba(168, 240, 60, 0.22);
  --online-glow-soft: rgba(140, 214, 50, 0.1);

  /* Assets referenced from more than one rule. */
  --grain: url("./assets/noise-256.png");
  --brand-mark: url("../../../assets/wingman-official-logo.svg");

  /* Fluid, not stepped. Each of these hits its canonical value exactly at
   * 1672px wide (278 / 60 / 1216) and then scales continuously — stepping them
   * at breakpoints made the wings jump ~135px mid-drag while resizing. */
  --sidebar-width: clamp(240px, 16.627vw, 278px);
  --content-left: clamp(28px, 3.59vw, 60px);
  --content-width: 1216px;
  --content-gutters: clamp(80px, 10.646vw, 178px);
  /* One definition of the content column, used by the hero, the product grid
   * and the metrics grid alike, so all three always share a centre. */
  --content-w: min(var(--content-width), calc(100% - var(--content-gutters)));

  --radius-card: 15px;
  --radius-small: 10px;
  --radius-pill: 999px;

  /* Stacking order, declared in one place so no rule has to guess. */
  --z-sidebar-content: 1;
  --z-hero-grain: 1;
  --z-sidebar: 2;
  --z-utility: 3;
  --z-sticky-bar: 20;
  --z-page-grain: 100;

  --transition-fast: 120ms ease;
  --transition-normal: 220ms ease;

  /* Offline → online choreography. ONE timeline, declared once; every layer
   * below reads its beat from here, so the sequence is retimed in this block
   * rather than by hunting delays through the file. page.js reads --t-ignition
   * and --t-shutdown off the computed style so the lock-out window can never
   * drift out of step with the animation. Milliseconds throughout. */
  --t-press: 90ms;         /* physical press bottoms out */
  --t-ignite: 180ms;       /* green light appears at the centre */
  --t-ring: 280ms;         /* perimeter sweep starts */
  --t-wing: 400ms;         /* reflected light starts crossing the feathers */
  --t-label: 480ms;        /* ONLINE resolves */
  --t-sublabel: 560ms;
  --t-glint: 620ms;        /* enabled sources acknowledge */
  --t-echo: 760ms;
  --t-ignition: 1000ms;    /* whole go-online sequence */
  --t-shutdown: 620ms;     /* go-offline is faster and quieter, by design */

  --ease-press: cubic-bezier(0.4, 0, 0.7, 1);
  --ease-ignite: cubic-bezier(0.16, 1, 0.3, 1);

  /* Where the wings' inner roots meet, as a share of the visible hero frame.
   * The orb and the light it casts both anchor here; two hand-tuned copies of
   * this number is exactly how the button drifted off the wings last time. */
  --orb-anchor-y: 48.56%;

  /* Vertical rhythm. 75 + 356 (hero) + 272 + 24 + 159 + 55 = 941, the
   * canonical viewport height; the hero's 356 is derived, not declared. */
  --product-height: 272px;
  --product-gap: 45px;
  --metric-height: 159px;
  --metric-gap: 24px;
  --page-bottom: 55px;

  --font-ui: "Geist", "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
}


/* ── §3 Global background and texture ──────────────────────────────────── */

body {
  color: var(--text-primary);
  font-family: var(--font-ui);
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  min-height: 100vh;
  min-height: 100dvh;
  background:
    /* deep edge vignette — keeps the corners from reading as flat fill */
    radial-gradient(
      130% 105% at 52% 34%,
      transparent 52%,
      rgba(0, 0, 0, 0.34) 88%,
      rgba(0, 0, 0, 0.5) 100%
    ),
    /* soft top-centre illumination, warm charcoal not orange */
    radial-gradient(
      ellipse at 57% 7%,
      rgba(77, 67, 51, 0.09) 0%,
      rgba(30, 28, 24, 0.04) 30%,
      transparent 58%
    ),
    linear-gradient(180deg, #090a0a 0%, #060707 52%, #040505 100%);
  background-attachment: fixed;
}

/* Nearly invisible. Its only job is to stop 1400px of near-black from reading
 * as a flat digital fill — at this opacity over soft-light the grain registers
 * as depth, never as grain. */
.app-shell::after {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  opacity: 0.055;
  background-image: var(--grain);
  background-repeat: repeat;
  mix-blend-mode: soft-light;
  z-index: var(--z-page-grain);
}

/* A second, stronger grain pass over the hero band: one global opacity cannot
 * be both invisible over a card and present across 1300px of empty canvas. */
.status-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.05;
  background-image: var(--grain);
  background-repeat: repeat;
  mix-blend-mode: soft-light;
  /* Deliberately above .hero-art: the grain lies over the wings AND the orb, so
   * the whole hero reads as one surface rather than as artwork on a plate. */
  z-index: var(--z-hero-grain);
}

.icon-sprite {
  position: absolute;
  width: 0;
  height: 0;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}


/* ── §4 Application shell ──────────────────────────────────────────────── */

.app-shell {
  display: grid;
  grid-template-columns: var(--sidebar-width) minmax(0, 1fr);
  min-height: 100vh;
  min-height: 100dvh;
}

.dashboard {
  position: relative;
  min-width: 0;
  min-height: 100vh;
  min-height: 100dvh;
  padding-bottom: var(--page-bottom);
  overflow: hidden;
}

/* Shared geometry for the two content grids: anchored --content-left from the
 * workspace's left edge, NOT centred — the extra breathing room belongs on the
 * right (spec §4). 1394 - 178 = 1216 at the canonical viewport. */
.product-grid,
.metrics-grid,
.hero-stage {
  width: var(--content-w);
  margin-left: var(--content-left);
}


/* ── §5 Sidebar ────────────────────────────────────────────────────────── */

.sidebar {
  position: relative;
  z-index: var(--z-sidebar);
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100dvh;
  padding: 44px 0 96px;
  background: linear-gradient(180deg, rgba(10, 11, 11, 0.99), rgba(5, 6, 7, 0.99));
  /* A dark-gold edge, a narrow brighter line just inside it, and a black
   * shadow thrown into the workspace — the separation should be legible, not
   * merely implied. */
  border-right: 1px solid rgba(150, 103, 38, 0.42);
  box-shadow:
    10px 0 34px rgba(0, 0, 0, 0.42),
    inset -1px 0 rgba(255, 214, 140, 0.05);
}

/* Faint inner frame, ~8px inside the panel. Neutral bronze rather than orange,
 * and carried down to y≈922. */
.sidebar::before {
  content: "";
  position: absolute;
  inset: 7px 10px 18px 8px;
  border: 1px solid rgba(196, 166, 120, 0.11);
  pointer-events: none;
}

/* Minimal gold illumination near the top. */
.sidebar::after {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 320px;
  pointer-events: none;
  background: radial-gradient(
    118% 74% at 50% -6%,
    rgba(196, 141, 60, 0.09),
    transparent 68%
  );
}

.sidebar > * {
  position: relative;
  z-index: var(--z-sidebar-content);
}

/* Brand ------------------------------------------------------------------ */

.sidebar-brand {
  display: flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
  border-radius: var(--radius-small);
}

/* Pure white. `contain` keeps the lockup's own proportions inside the box
 * rather than stretching it to fill. */
.sidebar-logo {
  width: 104px;
  height: 95px;
  background: #fff;
  -webkit-mask: var(--brand-mark) center / contain no-repeat;
  mask: var(--brand-mark) center / contain no-repeat;
}

/* Navigation ------------------------------------------------------------- */

.primary-navigation {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-top: 109px;  /* brand box ends at 139; nav starts at 248 */
  padding: 0 23px;
}

.nav-item {
  display: flex;
  align-items: center;
  gap: 20px;
  height: 68px;
  padding: 0 27px;
  border: 1px solid transparent;
  border-radius: 16px;
  color: #d5cab8;
  font-size: 17px;
  font-weight: 400;
  text-decoration: none;
  transition: color var(--transition-fast), background-color var(--transition-fast);
}

.nav-icon {
  flex: none;
  width: 28px;
  height: 28px;
  color: #c3a67e;   /* warm metallic, not neutral gray */
  transition: color var(--transition-fast);
}

.nav-item:hover {
  color: var(--text-primary);
  background: rgba(255, 225, 175, 0.022);
}

.nav-item:hover .nav-icon {
  color: var(--gold-core);
}

.nav-item:active {
  background: rgba(255, 225, 175, 0.04);
}

/* Barely-illuminated black glass with a thin brass outline, not a bright fill.
 * Top-lit rather than uniformly outlined: the top edge catches gold, the sides
 * fall away, the bottom nearly disappears. Selector is [aria-current] so the
 * active state has exactly one source of truth. */
.nav-item[aria-current="page"] {
  height: 70px;
  color: #e8c47e;
  border-color:
    rgba(206, 148, 60, 0.5)
    rgba(187, 128, 48, 0.3)
    rgba(187, 128, 48, 0.15)
    rgba(187, 128, 48, 0.3);
  background:
    radial-gradient(115% 78% at 50% -14%, rgba(158, 106, 36, 0.15), transparent 66%),
    radial-gradient(90% 130% at 50% 50%, transparent 38%, rgba(0, 0, 0, 0.3) 100%),
    linear-gradient(180deg, rgba(30, 26, 21, 0.78), rgba(14, 14, 13, 0.86));
  box-shadow:
    inset 0 1px rgba(255, 224, 165, 0.07),
    0 8px 22px rgba(0, 0, 0, 0.42);
}

.nav-item[aria-current="page"] .nav-icon {
  color: var(--gold-highlight);
}

/* Divider + profile ------------------------------------------------------ */

/* margin-top:auto pins this block to the bottom of the column, so the divider
 * lands at 941 - 96 - 94 - 60 - 1 = 690 and the profile at 751 without either
 * being positioned absolutely. */
.sidebar-divider {
  width: 220px;
  height: 1px;
  margin: auto 30px 60px 28px;
  background: linear-gradient(90deg, transparent, rgba(211, 162, 87, 0.14), transparent);
}

.agent-profile {
  display: flex;
  align-items: center;
  width: 220px;
  height: 94px;
  margin: 0 26px 0 32px;
  border-radius: 14px;
  color: inherit;
  text-decoration: none;
  transition: background-color var(--transition-fast);
}

.agent-profile:hover {
  background: rgba(255, 225, 175, 0.025);
}

.agent-avatar {
  flex: none;
  display: grid;
  place-items: center;
  width: 85px;
  height: 85px;
  border-radius: 50%;
  border: 1px solid rgba(226, 180, 110, 0.42);
  background:
    radial-gradient(circle at 44% 30%, #22201d 0%, #131313 58%, #0a0a0b 100%);
  box-shadow: 0 0 18px rgba(176, 118, 42, 0.16);
  color: var(--gold-core);
  font-size: 27px;
  font-weight: 300;
  letter-spacing: 0.04em;
  overflow: hidden;
}

.agent-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.agent-identity {
  display: flex;
  flex-direction: column;
  margin-left: 19px;
  min-width: 0;
}

.agent-name {
  font-size: 17px;
  font-weight: 400;
  color: #ede8df;
  white-space: nowrap;
}

.agent-role {
  margin-top: 5px;
  font-size: 16px;
  font-weight: 400;
  color: #d29631;
}

.agent-chevron {
  flex: none;
  width: 19px;
  height: 19px;
  margin-left: auto;
  color: rgba(210, 150, 49, 0.72);
}


/* ── §6 Utility header ─────────────────────────────────────────────────── */

.dashboard-utility {
  position: absolute;
  top: 35px;
  right: 72px;
  z-index: var(--z-utility);
  display: flex;
  align-items: center;
  margin: 0;
}

.utility-greeting {
  margin: 0;
  font-size: 17px;
  font-weight: 300;
  color: #ded0bb;
}

.utility-divider {
  width: 2px;
  height: 25px;
  margin: 0 32px;
  background: rgba(171, 152, 122, 0.62);
}

.utility-time {
  margin: 0;
  font-size: 18px;
  font-weight: 400;
  color: #d29a3d;
  font-variant-numeric: tabular-nums;
}


/* ── §7 Hero ───────────────────────────────────────────────────────────── */

.status-hero {
  position: relative;
  /* Padding, NOT a margin on .hero-art: a child's top margin collapses out
   * through this block and lifted the whole composition by 75px. */
  padding-top: 75px;
}

/* .hero-art is the VISIBLE frame of the wing artwork, not the artwork's own
 * box. The asset is 1475x450; the wing mask below fades to nothing at 90.5% of
 * its height, so the frame is 1475 x 407 (450 x 0.905) and the artwork
 * overhangs it invisibly.
 *
 * Kept in normal flow on purpose: .status-hero then sizes itself from the frame
 * (75 + 356 = 431px at the canonical viewport) and shrinks with it rather than
 * leaving a hole. Everything the hero contains is a percentage of this box,
 * which is what keeps the orb locked to the centre of the wings at every
 * width. The +74px is the amount the wingspan overhangs the content column. */
.hero-art {
  position: relative;
  left: 50%;
  transform: translateX(-50%);
  width: min(1290px, calc(100% + 74px));
  aspect-ratio: 1475 / 407;
}

/* Natural aspect, so the asset is never stretched; it overhangs the frame and
 * the mask takes it to zero exactly at the frame's bottom edge. */
.hero-wings {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: auto;
  pointer-events: none;
  -webkit-user-select: none;
  user-select: none;
  filter:
    saturate(0.6)
    brightness(0.92)
    drop-shadow(0 26px 34px rgba(0, 0, 0, 0.5));
  -webkit-mask-image: linear-gradient(180deg, #000 0 78%, rgba(0, 0, 0, 0.42) 86%, transparent 90.5%);
  mask-image: linear-gradient(180deg, #000 0 78%, rgba(0, 0, 0, 0.42) 86%, transparent 90.5%);
}

/* The orb ---------------------------------------------------------------- */

.status-orb {
  /* Centring is a token so every transform below can compose with it in ONE
   * declaration — `var(--orb-center) scale(.975)` — instead of each state and
   * each breakpoint restating the -50% pair and drifting apart. The compact
   * layout re-points it at a no-op rather than switching to `transform: none`,
   * which would make the state transforms invalid there. */
  --orb-center: translate(-50%, -50%);
  position: absolute;
  left: 50%;
  /* 43.95% of the artwork's height, expressed against the 90.5% visible frame:
   * 0.4395 / 0.905 = 48.56%. That is where the wings' inner roots meet, so the
   * orb sits in the notch rather than on top of it. */
  top: var(--orb-anchor-y);
  transform: var(--orb-center);
  /* A share of the artwork, not a stepped pixel size: 25.27% of the 1290px
   * frame is the canonical 326px, and the ratio holds at every other width, so
   * the button can never outgrow the wings. */
  width: clamp(200px, 25.27%, 326px);
  aspect-ratio: 1;
  padding: 0;
  border-radius: 50%;
  /* Per-side colours, not one flat value: on a 50%-radius border the four sides
   * blend across the diagonals, which is the cheapest way to get a genuinely
   * top-lit ring. The upper arcs catch the light; the bottom falls away. */
  border: 1px solid;
  border-color:
    rgba(232, 189, 116, 0.92)
    rgba(229, 184, 109, 0.66)
    rgba(229, 184, 109, 0.34)
    rgba(229, 184, 109, 0.66);
  background: radial-gradient(
    circle at 50% 36%,
    #151616 0%,
    #0b0d0e 48%,
    #050607 75%,
    #020303 100%
  );
  box-shadow:
    0 0 0 1.5px rgba(3, 4, 5, 0.92),
    0 20px 60px rgba(0, 0, 0, 0.7),
    0 0 24px rgba(184, 123, 43, 0.08),
    inset 0 1px rgba(255, 226, 174, 0.13),
    inset 0 -18px 35px rgba(0, 0, 0, 0.54);
  color: inherit;
  font: inherit;
  cursor: pointer;
  transition:
    transform 180ms ease,
    border-color 180ms ease,
    box-shadow var(--transition-normal);
}

/* Secondary thin gold ring, inside a narrow black gap. */
.status-orb::before {
  content: "";
  position: absolute;
  inset: 5px;
  border-radius: 50%;
  border: 1px solid;
  border-color:
    rgba(206, 155, 79, 0.52)
    rgba(198, 146, 71, 0.36)
    rgba(198, 146, 71, 0.17)
    rgba(198, 146, 71, 0.36);
  pointer-events: none;
}

/* Directional sheen on the broad metallic rim only — the mask keeps it off the
 * centre face. Upper-left and upper-right catch the light; the bottom does
 * not, which is what stops the rim reading as a uniform painted circle. */
.status-orb::after {
  content: "";
  position: absolute;
  inset: 6px;
  border-radius: 50%;
  pointer-events: none;
  background: conic-gradient(
    from 0deg,
    rgba(255, 226, 174, 0.055) 0deg,
    rgba(255, 226, 174, 0.1) 42deg,
    rgba(255, 226, 174, 0.015) 96deg,
    transparent 150deg,
    transparent 212deg,
    rgba(255, 226, 174, 0.02) 266deg,
    rgba(255, 226, 174, 0.11) 318deg,
    rgba(255, 226, 174, 0.055) 360deg
  );
  -webkit-mask: radial-gradient(circle, transparent 0 86%, #000 87%);
  mask: radial-gradient(circle, transparent 0 86%, #000 87%);
}

/* A second recessed channel with a fine champagne ring on its inner lip, so
 * the rim reads as machined steps rather than as two outlines with a gap.
 * Declared before .orb-face so the rim layers read outside-in. */
.orb-face::before {
  content: "";
  position: absolute;
  inset: -9px;
  border-radius: 50%;
  border: 1px solid;
  border-color:
    rgba(224, 183, 116, 0.3)
    rgba(214, 172, 105, 0.18)
    rgba(214, 172, 105, 0.08)
    rgba(214, 172, 105, 0.18);
  box-shadow: inset 0 3px 7px rgba(0, 0, 0, 0.6);
  pointer-events: none;
  transition: border-color 240ms ease;
}

/* Grid, not flex: the offline and online readouts occupy the SAME cell so they
 * can crossfade without either one displacing the other, and the face never
 * reflows when the label changes. */
.orb-face {
  position: absolute;
  inset: 27px;   /* 324 padding box - 54 = 270px centre face */
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid;
  border-color:
    rgba(228, 189, 124, 0.44)
    rgba(224, 183, 116, 0.28)
    rgba(224, 183, 116, 0.13)
    rgba(224, 183, 116, 0.28);
  /* Charcoal with real radial variation and a small top-centre reflection;
   * near-flat black reads as a plain CSS circle rather than as metal. */
  background:
    radial-gradient(60% 34% at 50% 7%, rgba(191, 173, 146, 0.07), transparent 70%),
    radial-gradient(circle at 50% 32%, #141618 0%, #0d0f11 40%, #06080a 74%, #020304 100%);
  box-shadow:
    inset 0 2px 12px rgba(0, 0, 0, 0.72),
    inset 0 -16px 30px rgba(0, 0, 0, 0.55),
    inset 0 1px rgba(255, 226, 174, 0.08);
  /* The lit counterpart in §7b deliberately mirrors this declaration's SHAPE —
   * same layer count, same gradient types, same stop positions, same
   * inset flags. Backgrounds and shadows only interpolate between matching
   * structures; mismatch them and the face hard-cuts instead of crossfading. */
  transition:
    background 240ms ease,
    border-color 240ms ease,
    box-shadow 240ms ease;
}

/* Surface grain on the face. Very low opacity — it should register as material,
 * never as visible noise. */
.orb-face::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  pointer-events: none;
  opacity: 0.06;
  background-image: var(--grain);
  mix-blend-mode: soft-light;
}

/* position:relative only so the readout paints ABOVE .orb-ignition, which is
 * absolutely positioned and would otherwise sit on top of the text. */
.status-content {
  grid-area: 1 / 1;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.status-label {
  font-size: clamp(24px, 2.28vw, 38px);   /* NOT a %: that resolves against the parent font-size */
  font-weight: 400;
  line-height: 1.1;
  letter-spacing: 0.005em;
  color: #ecd3a6;            /* warm pale champagne */
}

.status-action {
  margin-top: clamp(7px, 0.72vw, 12px);
  font-size: clamp(13px, 1.08vw, 18px);
  font-weight: 300;
  line-height: 1.25;
  color: #cfc2ae;
}

.status-orb:hover {
  transform: var(--orb-center) translateY(-2px);
  border-color: rgba(239, 194, 119, 0.95);
  box-shadow:
    0 0 0 1.5px rgba(3, 4, 5, 0.92),
    0 24px 70px rgba(0, 0, 0, 0.76),
    0 0 34px rgba(201, 141, 55, 0.12),
    inset 0 1px rgba(255, 226, 174, 0.15),
    inset 0 -18px 35px rgba(0, 0, 0, 0.54);
}

/* Phase 1: the press bottoms out immediately, before anything else moves. A
 * control this large has to answer the pointer on the first frame or the whole
 * sequence reads as lag. */
.status-orb:active {
  transform: var(--orb-center) scale(0.975);
}


/* ── §7b Online state and the ignition sequence ────────────────────────── */

/* Everything below keys off ONE attribute, `data-status` on .app-shell. Its
 * four values are offline | going-online | online | going-offline, and the
 * `:is(going-online, online)` pair below is read throughout as "lit": the
 * settled online look and the look the transition is animating TOWARD are the
 * same declaration, so the moment page.js swaps going-online for online there
 * is nothing left to change and nothing pops.
 *
 * Restraint is the whole design here. Green touches four things — the orb, the
 * light it throws on the inner feathers, enabled lead sources, and the
 * atmosphere immediately around the control. Cards, charts, prices, icons and
 * the sidebar stay black and bronze; that is what keeps this reading as a
 * machine coming online rather than as a green theme.
 *
 * ORDER MATTERS: this section must stay AFTER §7, because the lit rules and
 * the base rules have equal weight on some properties and the later one wins. */

/* --sweep is registered so it can be INTERPOLATED. An unregistered custom
 * property is a token stream, and a token stream cannot be animated — the ring
 * would jump from nothing to fully lit at the animation's midpoint instead of
 * travelling. Without @property support that jump is the graceful degradation:
 * the sweep is lost, the settled online state is unaffected. */
@property --sweep {
  syntax: "<angle>";
  inherits: false;
  initial-value: 360deg;
}

/* The readouts ----------------------------------------------------------- */

/* Whichever readout is not current is faded, blurred and slightly shrunk
 * rather than removed: it holds no space either way (both share one grid cell),
 * and animating opacity + transform + filter keeps the work off the layout. */
.status-content-online {
  opacity: 0;
  filter: blur(5px);
  transform: scale(0.94) translateY(5px);
  transition:
    opacity 220ms ease,
    filter 220ms ease,
    transform 220ms ease;
}

/* `none`, NOT blur(0). A zero-radius blur is still a filter: it puts the text
 * on its own render surface and rasterises every glyph a value off. Both resting
 * states have to be filter:none or the settled page never quite matches itself.
 * none ⇄ blur(Npx) interpolates fine — the initial value stands in. */
.status-content-offline {
  opacity: 1;
  filter: none;
  transform: none;
  transition:
    opacity 130ms ease var(--t-press),
    filter 130ms ease var(--t-press),
    transform 130ms ease var(--t-press);
}

:is([data-status="going-online"], [data-status="online"]) .status-content-offline {
  opacity: 0;
  filter: blur(4px);
  transform: scale(0.96);
}

:is([data-status="going-online"], [data-status="online"]) .status-content-online {
  opacity: 1;
  filter: none;
  transform: none;
  transition-delay: var(--t-label);
}

/* The subtitle lands a beat after the status word, never with it. */
:is([data-status="going-online"], [data-status="online"]) .status-content-online .status-action {
  transition-delay: calc(var(--t-sublabel) - var(--t-label));
}

.status-content-online .status-action {
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 180ms ease, transform 180ms ease;
}

:is([data-status="going-online"], [data-status="online"]) .status-content-online .status-action {
  opacity: 1;
  transform: none;
}

/* Brighter and more assertive than OFFLINE, but still weight 400. Warm white,
 * NOT green: the surrounding light says "live", and a green word on a green
 * face is the fastest way to lose the text. Tracking opens wide on the way in
 * and closes to its final value as it lands, so the word reads as resolving
 * into place rather than simply fading up. */
.status-content-online .status-label {
  font-size: clamp(28px, 2.72vw, 45px);
  letter-spacing: 0.09em;
  color: #f3f1e9;
  text-shadow:
    0 1px 1px rgba(0, 0, 0, 0.7),
    0 0 10px rgba(176, 255, 103, 0.1);
  transition: letter-spacing 220ms ease;
}

:is([data-status="going-online"], [data-status="online"]) .status-content-online .status-label {
  letter-spacing: 0.055em;
}

.status-content-online .status-action {
  color: #ded8cc;
}

/* Ignition --------------------------------------------------------------- */

/* Phase 3. A concentrated point of light inside the centre face that expands
 * into the surface. No sparks, no particles: the whole effect is light and the
 * material's response to it. */
.orb-ignition {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  pointer-events: none;
  opacity: 0;
  transform: scale(0.15);
  background: radial-gradient(
    circle at 50% 44%,
    rgba(172, 252, 104, 0.2) 0%,
    rgba(128, 214, 58, 0.11) 24%,
    rgba(80, 168, 38, 0.05) 50%,
    transparent 74%
  );
  box-shadow: inset 0 0 0 1px rgba(184, 255, 88, 0.3);
  transition: opacity 260ms var(--ease-ignite), transform 260ms var(--ease-ignite);
}

:is([data-status="going-online"], [data-status="online"]) .orb-ignition {
  opacity: 1;
  transform: none;
  transition-delay: var(--t-ignite);
}

/* The centre face -------------------------------------------------------- */

/* Very dark forest-green glass, never a flat green fill: the same two gradient
 * layers as the dormant face with the same stop positions, so it crossfades
 * instead of hard-cutting, and still dark enough for white text to dominate. */
:is([data-status="going-online"], [data-status="online"]) .orb-face {
  border-color:
    rgba(206, 255, 130, 0.88)
    rgba(158, 232, 74, 0.46)
    rgba(96, 158, 44, 0.18)
    rgba(158, 232, 74, 0.46);
  /* Lit from above, not from the middle: the reference's face is brightest
   * across the top third and falls into near-black at the bottom edge. Same two
   * layers and the same stop positions as the dormant face so it crossfades. */
  background:
    radial-gradient(60% 34% at 50% 7%, rgba(198, 255, 132, 0.14), transparent 70%),
    radial-gradient(circle at 48% 24%, #175828 0%, #0b3a1c 40%, #04200f 74%, #020b06 100%);
  box-shadow:
    inset 0 2px 14px rgba(0, 0, 0, 0.76),
    inset 0 -16px 32px rgba(0, 0, 0, 0.62),
    inset 0 1px rgba(214, 255, 160, 0.16);
  transition-delay: var(--t-ignite);
}

/* The rings -------------------------------------------------------------- */

/* Outside-in the reference reads: warm chartreuse glow, a hard bright rim that
 * is near-white at the top and turns amber down the sides, a wide dark channel,
 * a warm CHAMPAGNE-GOLD hairline, a narrow channel, a brilliant green inner
 * perimeter, then the deep forest-green face.
 *
 * The gold does not sit outside the green — it sits INSIDE it, and that is what
 * keeps the lit orb reading as Wingman rather than as a status LED. The top of
 * the rim is lit green because the orb's own light falls on it; the sides and
 * bottom fall back to the metal underneath, which is why the per-side border
 * values below are not four shades of one colour. */
:is([data-status="going-online"], [data-status="online"]) .status-orb {
  border-color:
    rgba(233, 255, 190, 0.95)
    rgba(206, 226, 120, 0.7)
    rgba(150, 130, 62, 0.42)
    rgba(206, 226, 120, 0.7);
  box-shadow:
    0 0 0 1.5px rgba(3, 4, 5, 0.92),
    0 20px 65px rgba(0, 0, 0, 0.72),
    0 0 38px var(--online-glow-strong),
    inset 0 1px rgba(226, 255, 180, 0.2),
    inset 0 -18px 35px rgba(0, 0, 0, 0.5);
  transition-delay: var(--t-ring), var(--t-ring), var(--t-ring);
}

/* The energized ring, immediately inside the rim so the two read as one hard
 * lit edge. inset:0 rather than the dormant 5px — it never animates, and the
 * mask below hides the ring until the sweep reaches it, so the move is unseen. */
:is([data-status="going-online"], [data-status="online"]) .status-orb::before {
  inset: 0;
  border-color:
    var(--online-green-bright)
    rgba(178, 236, 96, 0.72)
    rgba(120, 150, 58, 0.3)
    rgba(178, 236, 96, 0.72);
  /* Inward only. An outward spill lands on the glow and flattens the hard edge
   * the whole rim depends on. */
  box-shadow: inset 0 0 7px rgba(184, 255, 88, 0.3);
}

/* The champagne hairline. Brighter than dormant, NOT green: this is the ring
 * the brand survives on when everything around it turns electric. */
:is([data-status="going-online"], [data-status="online"]) .orb-face::before {
  border-color:
    rgba(246, 214, 143, 0.72)
    rgba(226, 184, 106, 0.44)
    rgba(168, 128, 62, 0.2)
    rgba(226, 184, 106, 0.44);
  box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.66);
  transition: border-color 240ms ease var(--t-ring);
}

/* Phase 4: the light travels once around the perimeter and leaves a lit edge
 * behind it. The ring is masked away entirely until the sweep starts, which is
 * also what gives the dormant rim its beat of darkness before ignition. */
[data-status="going-online"] .status-orb::before {
  -webkit-mask: conic-gradient(from 0deg, #000 0 var(--sweep), transparent var(--sweep));
  mask: conic-gradient(from 0deg, #000 0 var(--sweep), transparent var(--sweep));
  /* NOT --ease-ignite. That curve is ~97% resolved a third of the way in, which
   * is right for a light switching on and wrong for one travelling: the sweep
   * finished before the eye could follow it. Near-linear with a soft landing. */
  animation: ring-sweep 270ms var(--t-ring) cubic-bezier(0.3, 0.55, 0.4, 1) both;
}

@keyframes ring-sweep {
  from { --sweep: 0deg; }
  to { --sweep: 360deg; }
}

/* Glow and halo ---------------------------------------------------------- */

/* A circular gradient with a genuinely transparent centre, so it can never
 * wash over the rings it sits in front of and its paint order stops mattering.
 * Sized as a share of the orb, so it tracks every responsive width for free. */
.orb-halo {
  position: absolute;
  inset: -34%;
  border-radius: 50%;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(
    circle,
    transparent 0 30.5%,
    var(--online-glow-strong) 34%,
    var(--online-glow-medium) 41%,
    var(--online-glow-soft) 50%,
    transparent 64%
  );
  filter: blur(14px);
  transform: scale(0.92);
  transition: opacity 300ms ease, transform 300ms ease;
}

:is([data-status="going-online"], [data-status="online"]) .orb-halo {
  opacity: 1;
  transform: none;
  transition-delay: var(--t-ring);
}

/* Phase 7's idle breathing. One layer, opacity and transform only, and small
 * enough that it registers as the system being alive rather than as something
 * to watch. Runs only once the sequence has settled. */
[data-status="online"] .orb-halo {
  animation: online-breathe 4s ease-in-out infinite alternate;
}

@keyframes online-breathe {
  from { opacity: 0.86; transform: scale(0.986); }
  to { opacity: 1; transform: scale(1.014); }
}

/* Phase 4's closing beat: one light echo outward, and it stays close to the
 * orb. Not a pulse wave and never repeated. */
.orb-echo {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  pointer-events: none;
  opacity: 0;
  box-shadow: 0 0 12px 2px rgba(184, 255, 88, 0.55);
}

[data-status="going-online"] .orb-echo {
  animation: orb-echo 260ms var(--t-echo) ease-out both;
}

@keyframes orb-echo {
  from { opacity: 0.3; transform: scale(1); }
  to { opacity: 0; transform: scale(1.12); }
}

/* Phase 1 + phase 7 as one timeline on the orb itself. Deliberately a single
 * animation rather than :active plus a settle transition — an animation on
 * `transform` outranks the hover and active rules for as long as it runs, so
 * two competing sources would fight for a full second. */
[data-status="going-online"] .status-orb {
  animation: orb-ignite var(--t-ignition) linear both;
}

/* Keyframe offsets cannot be custom properties, so these are the timeline above
 * expressed against --t-ignition: 9% = --t-press, 70% = the settle. Change the
 * token block and these four numbers together. */
@keyframes orb-ignite {
  0% { transform: var(--orb-center) scale(1); animation-timing-function: var(--ease-press); }
  9% { transform: var(--orb-center) scale(0.975); animation-timing-function: var(--ease-ignite); }
  30% { transform: var(--orb-center) scale(1.006); }
  70% { transform: var(--orb-center) scale(1.012); animation-timing-function: ease-out; }
  100% { transform: var(--orb-center) scale(1); }
}

/* Reflected light on the wings ------------------------------------------- */

/* Masked with the wing bitmap itself. The mask is static, so it composites
 * once; only .wing-glow-light moves. Recolouring or filtering the 1475px
 * artwork per frame is the one thing in this sequence that would actually cost
 * frames, and it would flatten the feathers to a uniform green besides. */
.wing-glow {
  position: absolute;
  inset: 0;
  pointer-events: none;
  -webkit-mask-image: url("./assets/offline-wings.webp");
  mask-image: url("./assets/offline-wings.webp");
  -webkit-mask-size: 100% auto;
  mask-size: 100% auto;
  -webkit-mask-position: top left;
  mask-position: top left;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

/* Anchored to the same point as the orb, so the light demonstrably comes FROM
 * the control. Bright at the inner roots and gone well before the tips: the
 * wings have to still read as black first. */
.wing-glow-light {
  position: absolute;
  left: 50%;
  top: var(--orb-anchor-y);
  width: 46%;
  height: 40%;
  opacity: 0;
  transform: translate(-50%, -50%) scaleX(0.18);
  /* Falls off fast. The reference reads as black wings catching a little light,
   * not as green wings — clear illumination across the inner sixth of each
   * wing, a bronze-green suggestion over the next fifth, and effectively
   * nothing across the outer two thirds. */
  background: radial-gradient(
    closest-side ellipse at 50% 50%,
    rgba(184, 255, 88, 0.44) 0%,
    rgba(143, 232, 63, 0.2) 28%,
    rgba(94, 189, 42, 0.07) 52%,
    transparent 74%
  );
  mix-blend-mode: screen;
  transition: opacity 320ms ease, transform 320ms ease;
}

:is([data-status="going-online"], [data-status="online"]) .wing-glow-light {
  opacity: 0.24;
  transform: translate(-50%, -50%) scaleX(1);
}

/* Phase 5. The sweep overshoots in brightness on the way out and settles back
 * to the permanent reflection — and its final keyframe is exactly the settled
 * value above, so removing the animation at the end changes nothing. */
[data-status="going-online"] .wing-glow-light {
  animation: wing-wake 320ms var(--t-wing) ease-out both;
}

@keyframes wing-wake {
  from { opacity: 0; transform: translate(-50%, -50%) scaleX(0.18); }
  55% { opacity: 0.66; transform: translate(-50%, -50%) scaleX(0.74); }
  to { opacity: 0.24; transform: translate(-50%, -50%) scaleX(1); }
}

/* Shutdown --------------------------------------------------------------- */

/* Going offline is faster and quieter — controlled shutdown, not the ignition
 * played backwards. The light retracts toward the centre first, the rings dim,
 * and the dormant readout returns last. */
[data-status="going-offline"] .status-content-offline {
  transition-delay: 300ms;
}

[data-status="going-offline"] .orb-halo,
[data-status="going-offline"] .wing-glow-light {
  transition-duration: 260ms;
  transition-delay: 60ms;
}

[data-status="going-offline"] .orb-ignition {
  transition-delay: 140ms;
}

[data-status="going-offline"] .orb-face {
  transition-delay: 180ms;
}

/* Only while something is actually moving. Left on permanently it costs a
 * promoted layer per element for the entire session and buys nothing. */
:is([data-status="going-online"], [data-status="going-offline"]) :is(
  .status-orb,
  .orb-halo,
  .orb-ignition,
  .wing-glow-light,
  .status-content
) {
  will-change: transform, opacity;
}


/* ── §8 Product cards ──────────────────────────────────────────────────── */

.product-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--product-gap);
}

/* Top-lit gradient border: a single border-color is uniformly bright on all
 * four sides, so the ring is painted as a gradient and the interior masked out. */
.product-card::before,
.metric-card::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  pointer-events: none;
  background: linear-gradient(
    172deg,
    rgba(214, 158, 68, 0.62) 0%,
    rgba(180, 119, 41, 0.34) 26%,
    rgba(150, 100, 36, 0.16) 62%,
    rgba(126, 86, 33, 0.08) 100%
  );
  -webkit-mask:
    linear-gradient(#000 0 0) content-box,
    linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask:
    linear-gradient(#000 0 0) content-box,
    linear-gradient(#000 0 0);
  mask-composite: exclude;
}

.product-card {
  position: relative;
  border: 0;
  display: flex;
  flex-direction: column;
  min-width: 0;
  height: var(--product-height);
  padding: 38px 40px 26px;   /* +8 top; the toggle is pinned to the bottom */
  border-radius: var(--radius-card);
  background:
    radial-gradient(circle at 50% -18%, rgba(112, 78, 34, 0.11), transparent 54%),
    radial-gradient(112% 128% at 50% 42%, transparent 44%, rgba(0, 0, 0, 0.34) 100%),
    linear-gradient(180deg, rgba(15, 15, 14, 0.96), rgba(7, 8, 8, 0.98));
  box-shadow:
    0 16px 34px rgba(0, 0, 0, 0.42),
    inset 0 1px rgba(255, 220, 155, 0.05);
}

/* Fine texture inside the card; a clean fill reads flat at this size. */
.product-card::after,
.metric-card::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0.04;
  background-image: var(--grain);
  mix-blend-mode: soft-light;
}

.product-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.product-title {
  margin: 0;
  /* Fluid so "LIVE TRANSFER PLUS" keeps fitting on one line as the column
   * narrows, instead of clipping against a fixed 22px. */
  font-size: clamp(15px, 1.32vw, 22px);
  font-weight: 400;
  line-height: 1.2;
  letter-spacing: 0.05em;
  color: #e0bb7d;
  white-space: nowrap;
}

.info-button {
  position: relative;   /* the 44px hit-target pseudo below resolves here */
  flex: none;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid rgba(202, 145, 60, 0.55);
  background: transparent;
  color: var(--gold-core);
  font-family: inherit;
  font-size: 15px;
  font-weight: 400;
  line-height: 1;
  cursor: pointer;
  transition: color var(--transition-fast), border-color var(--transition-fast);
}

/* 44px minimum hit target at every width — the visible circle stays 28px. */
.info-button::after {
  content: "";
  position: absolute;
  inset: 50% auto auto 50%;
  width: 44px;
  height: 44px;
  transform: translate(-50%, -50%);
}

.info-button:hover {
  color: var(--gold-highlight);
  border-color: var(--border-bright);
}

.info-button:active {
  border-color: rgba(202, 145, 60, 0.8);
}

.product-pricing {
  display: flex;
  align-items: center;
  margin: 22px 0 0;
}

.duration {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-size: 18px;
  font-weight: 300;
  color: #ded5c7;
  font-variant-numeric: tabular-nums;
}

.duration-icon {
  width: 25px;
  height: 25px;
  color: #bd8c42;
}

/* The UA sheet gives <hr> an inset border and margin-inline:auto; both have to
 * be reset or the rule renders as a short centred dash. */
.product-rule {
  width: 100%;
  height: 1px;
  margin: 20px 0 0;
  border: 0;
  background: linear-gradient(
    90deg,
    rgba(198, 158, 96, 0.06),
    rgba(206, 168, 106, 0.24) 22%,
    rgba(206, 168, 106, 0.24) 78%,
    rgba(198, 158, 96, 0.06)
  );
}

.product-ap {
  margin: 20px 0 0;
  font-size: 18px;
  font-weight: 300;
  color: #d8a44e;   /* warmer, less saturated orange */
  font-variant-numeric: tabular-nums;
}

/* Toggle ----------------------------------------------------------------- */

/* The capsule sits 28px inside the card exterior while the card itself pads to
 * 40px, hence the negative inline margin. margin-top:auto pins it to the card's
 * bottom padding, so the card's other rows can grow without moving it. */
/* --knob-clear = capsule padding + label width + gap + knob radius. The knob's
 * `left` clamps against it because a bare percentage lands ON the OFF label as
 * soon as the card narrows (reproduced at 1280px). */
.source-toggle {
  --knob-size: 51px;
  --knob-clear: 106px;
  /* The knob's two resting points, named once. The lit track is then DERIVED
   * from them rather than measured by eye — it is defined as the path the knob
   * travels, so the knob is flush at both ends of the pill at every width and
   * the two can never drift apart. */
  --knob-off-pos: max(33.5%, var(--knob-clear));
  --knob-on-pos: min(71.5%, calc(100% - var(--knob-clear)));
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 14px;
  width: calc(100% + 26px);
  height: 61px;
  margin: auto -13px 0;
  padding: 0 24px;
  border-radius: var(--radius-pill);
  border: 1px solid rgba(205, 160, 92, 0.26);
  background:
    linear-gradient(180deg, rgba(19, 19, 18, 0.98) 0%, rgba(9, 9, 9, 0.98) 46%, rgba(4, 5, 5, 0.98) 100%);
  box-shadow:
    inset 0 3px 11px rgba(0, 0, 0, 0.8),
    inset 0 1px rgba(255, 226, 172, 0.06),
    inset 0 -2px 6px rgba(0, 0, 0, 0.5),
    0 3px 9px rgba(0, 0, 0, 0.46);
  cursor: pointer;
  transition: box-shadow 180ms ease, border-color 180ms ease;
}

.toggle-label {
  font-size: 16px;
  font-weight: 300;
  letter-spacing: 0.04em;
  color: #dfcfb8;
  transition: color 140ms ease, opacity 140ms ease;
}

/* Both sides stay readable; the selected side is only modestly stronger. */
.toggle-label-off { opacity: 1; }
.toggle-label-on  { opacity: 0.68; }

.source-toggle[aria-pressed="true"] .toggle-label-off { opacity: 0.68; }
/* Pale green-white, not fluorescent green: the track and the knob already say
 * "live", and green label text on a green track is the first thing to become
 * unreadable. */
.source-toggle[aria-pressed="true"] .toggle-label-on {
  opacity: 1;
  color: #e8f6d8;
}

/* A rail, not a second capsule — the control must read as one continuous black
 * surface with the labels sitting on it. Deliberately NOT positioned: the
 * knob's percentage has to resolve against the whole capsule, not against this
 * middle grid column. This element only reserves the row height. */
.toggle-track {
  height: var(--knob-size);
  border-radius: var(--radius-pill);
}

.toggle-knob {
  position: absolute;
  top: 50%;
  left: var(--knob-off-pos);
  width: var(--knob-size);
  height: var(--knob-size);
  border-radius: 50%;
  /* Lit from the upper left, shaded to the lower right, fine champagne rim.
   * A uniformly bright rim is what makes a dark knob read as chrome. */
  border: 1px solid;
  border-color:
    rgba(240, 226, 200, 0.62)
    rgba(240, 226, 200, 0.3)
    rgba(240, 226, 200, 0.14)
    rgba(240, 226, 200, 0.44);
  background:
    radial-gradient(circle at 34% 26%, rgba(255, 250, 240, 0.16) 0 22%, transparent 58%),
    radial-gradient(circle at 36% 28%, #9c968d 0, #6b665f 26%, #3d3a36 56%, #201f1e 80%, #0e0e0e 100%);
  box-shadow:
    0 6px 16px rgba(0, 0, 0, 0.82),
    inset 0 1px rgba(255, 255, 255, 0.2),
    inset -3px -5px 9px rgba(0, 0, 0, 0.5);
  transform: translate(-50%, -50%);
  transition:
    left 220ms cubic-bezier(0.2, 0.8, 0.2, 1),
    box-shadow 180ms ease,
    border-color 180ms ease,
    background 180ms ease;
}

/* Smooth machined bevel. Striping here reads as a ribbed roller rather than a
 * circular button. */
.toggle-knob::after {
  content: "";
  position: absolute;
  inset: 1px;
  border-radius: 50%;
  pointer-events: none;
  background: linear-gradient(
    154deg,
    rgba(255, 250, 238, 0.1) 0%,
    transparent 42%,
    rgba(0, 0, 0, 0.22) 100%
  );
}

.source-toggle[aria-pressed="true"] .toggle-knob {
  left: var(--knob-on-pos);
}

/* Hover/active were missing entirely — the capsule was the only control on the
 * page that gave no feedback before its state changed. */
.source-toggle:hover {
  border-color: rgba(220, 179, 108, 0.38);
}

.source-toggle:active .toggle-knob {
  box-shadow:
    0 3px 9px rgba(0, 0, 0, 0.82),
    inset 0 1px rgba(255, 255, 255, 0.2),
    inset -3px -5px 9px rgba(0, 0, 0, 0.5);
}

/* Enabled = an illuminated green track inside an unchanged capsule, which is
 * what the reference shows: the pill lights up, the housing does not. Flooding
 * the whole capsule green was both louder than the mockup and louder than the
 * orb, and the orb has to stay the brightest thing on the page.
 *
 * The state stays green whether or not the agent is globally online. The toggle
 * reports one thing only — "this source is armed" — and a control that
 * recolours itself because of a decision made elsewhere on the page is a
 * control the agent stops trusting. See NOTES.md. */
.source-toggle[aria-pressed="true"] .toggle-track::before {
  content: "";
  position: absolute;   /* resolves against .source-toggle, same as the knob */
  top: 50%;
  left: calc(var(--knob-off-pos) - var(--knob-size) / 2);
  right: calc(100% - var(--knob-on-pos) - var(--knob-size) / 2);
  height: var(--knob-size);
  border-radius: var(--radius-pill);
  border: 1px solid rgba(142, 238, 59, 0.72);
  transform: translateY(-50%);
  pointer-events: none;
  background:
    radial-gradient(72% 220% at var(--knob-on-pos) 50%, rgba(184, 255, 88, 0.16), transparent 74%),
    linear-gradient(180deg, rgba(21, 89, 34, 0.98) 0%, rgba(10, 58, 24, 0.98) 48%, rgba(5, 38, 16, 0.99) 100%);
  box-shadow:
    inset 0 2px 7px rgba(4, 16, 7, 0.85),
    inset 0 1px rgba(202, 255, 151, 0.16),
    0 0 6px rgba(142, 238, 59, 0.3),
    0 0 14px rgba(105, 201, 37, 0.12);
}

/* Illuminated machined metal, not a plastic switch: bright upper-left
 * reflection, lime core, dark lower edge, fine champagne-green rim. */
.source-toggle[aria-pressed="true"] .toggle-knob {
  border-color:
    rgba(232, 255, 196, 0.72)
    rgba(190, 245, 130, 0.36)
    rgba(120, 190, 60, 0.18)
    rgba(214, 250, 170, 0.5);
  background:
    radial-gradient(circle at 34% 26%, rgba(244, 255, 220, 0.22) 0 22%, transparent 58%),
    radial-gradient(circle at 36% 26%, #e3ffad 0, #adf451 18%, #73ca2d 48%, #3b8d1d 72%, #173f12 100%);
  box-shadow:
    0 6px 16px rgba(0, 0, 0, 0.82),
    0 0 10px rgba(150, 240, 62, 0.34),
    inset 0 1px rgba(255, 255, 255, 0.24),
    inset -3px -5px 9px rgba(9, 32, 10, 0.5);
}

/* Phase 7's acknowledgement: a narrow highlight crosses each ARMED track once,
 * long after the orb has ignited. Disabled sources are left alone entirely —
 * the glint means "you asked for this one", not "something happened". */
.source-toggle::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  background-image: linear-gradient(
    100deg,
    transparent 42%,
    rgba(220, 255, 175, 0.28) 50%,
    transparent 58%
  );
  background-size: 260% 100%;
  background-repeat: no-repeat;
  background-position: 0 0;
}

[data-status="going-online"] .source-toggle[aria-pressed="true"]::after {
  animation: toggle-glint 180ms var(--t-glint) ease-out both;
}

@keyframes toggle-glint {
  from { opacity: 0; background-position: 100% 0; }
  30% { opacity: 1; }
  to { opacity: 0; background-position: 0 0; }
}


/* Disabled and in-flight. The orb and the source toggles both have to survive a
 * server round-trip before their state is real, so the states they will need
 * are defined here rather than invented at call time. Neither is applied in the
 * default render. */
.status-orb:disabled,
.source-toggle:disabled,
.status-orb[aria-busy="true"],
.source-toggle[aria-busy="true"] {
  cursor: progress;
  opacity: 0.62;
}

.status-orb:disabled,
.source-toggle:disabled {
  cursor: not-allowed;
}

.status-orb:disabled:hover,
.source-toggle:disabled:hover,
.status-orb[aria-busy="true"]:hover {
  transform: translate(-50%, -50%);
  border-color: inherit;
}

.source-toggle:disabled:hover { border-color: rgba(205, 160, 92, 0.26); }


/* ── §9 Metric cards ───────────────────────────────────────────────────── */

.metrics-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--metric-gap);
  margin-top: var(--metric-gap);
}

.metric-card {
  position: relative;
  border: 0;
  display: flex;
  align-items: center;
  min-width: 0;
  height: var(--metric-height);
  padding-left: 29px;
  border-radius: 14px;
  background:
    radial-gradient(circle at 50% -26%, rgba(105, 73, 32, 0.09), transparent 56%),
    radial-gradient(112% 132% at 50% 44%, transparent 42%, rgba(0, 0, 0, 0.32) 100%),
    linear-gradient(180deg, rgba(15, 15, 14, 0.96), rgba(7, 8, 8, 0.98));
  box-shadow:
    0 15px 32px rgba(0, 0, 0, 0.4),
    inset 0 1px rgba(255, 220, 155, 0.04);
  overflow: hidden;
}

.metric-icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 104px;
  height: 104px;
  border-radius: 50%;
  border: 1px solid;
  border-color:
    rgba(199, 140, 52, 0.44)
    rgba(185, 126, 43, 0.28)
    rgba(185, 126, 43, 0.14)
    rgba(185, 126, 43, 0.28);
  background:
    radial-gradient(circle at 45% 32%, rgba(64, 55, 43, 0.4) 0%, rgba(26, 24, 21, 0.6) 42%, rgba(8, 9, 9, 0.97) 74%),
    radial-gradient(105% 118% at 50% 62%, transparent 40%, rgba(0, 0, 0, 0.45) 100%);
  box-shadow:
    inset 0 1px rgba(255, 226, 172, 0.09),
    inset 0 -8px 16px rgba(0, 0, 0, 0.42);
}

.metric-icon svg {
  width: 49px;
  height: 49px;
  color: var(--gold-core);
}

.metric-body {
  min-width: 0;
  margin-left: 21px;
}

.metric-label {
  margin: 0;
  font-size: 17px;
  font-weight: 300;
  line-height: 1.3;
  color: #cdc5b9;
  white-space: nowrap;
}

.metric-value {
  margin: 6px 0 0;
  font-size: clamp(34px, 2.9vw, 48px);
  font-weight: 300;
  line-height: 1;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}

.metric-goal {
  margin: 9px 0 0;
  font-size: 17px;
  font-weight: 300;
  line-height: 1.3;
  color: #c8c0b4;
}

/* Anchored bottom-right rather than centred — 20px up from the card's base. */
.sparkline {
  position: absolute;
  right: 35px;
  bottom: 20px;
  width: 250px;
  height: 95px;
  pointer-events: none;
}

/* The fill and bars both terminate on a straight vertical edge at "now"; the
 * closing edge of the area path otherwise reads as a box corner. Fades the
 * start too, so nothing begins abruptly. */
.spark-area,
.spark-bars {
  -webkit-mask: linear-gradient(90deg, transparent 0, #000 7%, #000 90%, rgba(0, 0, 0, 0.35) 99%, transparent 100%);
  mask: linear-gradient(90deg, transparent 0, #000 7%, #000 90%, rgba(0, 0, 0, 0.35) 99%, transparent 100%);
}

.spark-line {
  stroke: #d29a3d;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.spark-line--tip {
  stroke: var(--gold-highlight);
  stroke-width: 2;
}

.spark-dot {
  fill: #f6ddaf;
}

.spark-dot-glow {
  fill: rgba(226, 168, 76, 0.2);
}

/* Faint vertical drop under the final point: same gradient as the guide bars,
 * slightly heavier so the endpoint still reads as the endpoint. */
.spark-tick {
  stroke-width: 1.6;
}


/* ── §14 Focus ─────────────────────────────────────────────────────────── */

:where(a, button):focus-visible {
  outline: 2px solid rgba(235, 188, 111, 0.8);
  outline-offset: 3px;
}

.status-orb:focus-visible {
  outline-offset: 6px;
}


/* ── §15 Responsive ────────────────────────────────────────────────────── */

/* Shorter than the canonical viewport: pin the sidebar, let the workspace
 * scroll on its own. Never a global transform: scale().
 *
 * ORDER MATTERS and is deliberate. This block runs before the width queries so
 * they can opt back out of the fixed height (the compact layout scrolls the
 * whole page instead). What must survive into the compact layout is
 * `.sidebar { position: sticky }` — without it the rail stops painting partway
 * down a scrolled page. Keep this block above the width queries. */
@media (max-height: 940px) {
  .app-shell {
    height: 100vh;
    height: 100dvh;
  }
  .sidebar {
    position: sticky;
    top: 0;
    height: 100vh;
    height: 100dvh;
    min-height: 0;
  }
  .dashboard {
    height: 100vh;
    height: 100dvh;
    overflow-x: hidden;
    overflow-y: auto;
  }
}

/* 1440–1599: the shell is untouched (§15) but the content column shrinks with
 * the viewport, so the card does too. Trimming the capsule keeps the knob near
 * its specified band instead of drifting toward the middle. */
@media (max-width: 1599px) {
  .source-toggle {
    padding: 0 20px;
    --knob-clear: 96px;
    --knob-size: 45px;
  }
}

/* 1280–1439: trim the shell before the components. The hero needs no rule at
 * all now — .hero-art is a percentage of the content column and the orb is a
 * percentage of .hero-art, so both track the column automatically. */
@media (max-width: 1439px) {
  :root { --product-gap: 24px; }

  .dashboard-utility { right: clamp(28px, 3.4vw, 72px); }
  .nav-item { height: 62px; padding: 0 22px; gap: 16px; }
  .nav-item[aria-current="page"] { height: 63px; }
  .sidebar-logo { width: 92px; height: 84px; }
  .primary-navigation { margin-top: 92px; }
  .product-card { padding: 26px 30px 22px; }
  .source-toggle {
    width: calc(100% + 4px);
    margin-inline: -2px;
    padding: 0 18px;
    --knob-clear: 94px;
  }
  .sparkline { width: 205px; }
  .metric-icon { width: 90px; height: 90px; }
}

/* Below 1280: a compact layout, not a scaled one. */
@media (max-width: 1279px) {
  :root {
    --sidebar-width: 88px;
    --content-left: 28px;
    --content-gutters: 180px;
  }

  .app-shell { height: auto; }
  .sidebar { padding: 26px 0 40px; }
  .sidebar-brand { min-height: 44px; }
  /* Crop to the crest: at rail width the full lockup renders its wordmark ~4px
   * tall, which reads as a smudge. Oversizing the mask and anchoring it to the
   * top shows the crest alone. */
  .sidebar-logo {
    width: 54px;
    height: 40px;
    -webkit-mask: var(--brand-mark) top center / 150% auto no-repeat;
    mask: var(--brand-mark) top center / 150% auto no-repeat;
  }
  .primary-navigation { margin-top: 30px; padding: 0 14px; }
  .nav-item,
  .nav-item[aria-current="page"] { height: 60px; padding: 0; justify-content: center; gap: 0; }
  .nav-item > span { display: none; }

  .sidebar-divider { width: 56px; margin-inline: 16px; }
  .agent-profile { width: 56px; height: 56px; margin-inline: 16px; justify-content: center; }
  .agent-avatar { width: 52px; height: 52px; font-size: 18px; }
  .agent-identity,
  .agent-chevron { display: none; }

  /* Opt out of the fixed height above: below 1280 the page scrolls, not the
   * workspace. The sticky sidebar from that block is kept on purpose. */
  .dashboard {
    height: auto;
    min-height: 100vh;
    min-height: 100dvh;
  }

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

/* At this width the wing artwork has shrunk to the point where the orb would
 * hit its size floor and burst out of the frame. Drop the wings and let the
 * control stand on its own rather than render a broken composition. */
@media (max-width: 1024px) {
  .status-hero { padding: 30px 0 12px; }
  .hero-wings,
  .wing-glow { display: none; }
  .hero-art {
    left: auto;
    transform: none;
    display: grid;
    place-items: center;
    width: 100%;
    aspect-ratio: auto;
  }
  /* The orb centres itself by flow here, so the token is EMPTY and every state
   * transform below composes down to just its own function. Deliberately not
   * translate(0,0): a zero translate is still a transform, and it makes the orb
   * a stacking context, which visibly repaints the rim where the blended grain
   * layers composite against it. Empty also makes the base `transform:
   * var(--orb-center)` invalid at computed-value time, so it falls back to its
   * initial value — none — which is exactly what this layout wants. */
  .status-orb {
    --orb-center: ;
    position: relative;
    left: auto;
    top: auto;
    width: min(320px, 42vw);
  }
}

/* Mobile: same semantics and order, a different composition. */
@media (max-width: 860px) {
  :root {
    --sidebar-width: 0px;
    --content-left: 20px;
    --content-gutters: 40px;
    --content-width: 100%;
    --product-height: auto;
    --metric-height: auto;
  }

  .app-shell { display: block; }

  /* On a phone the orb is the dominant first component, so it takes a much
   * larger share of the width than the 42vw the tablet rule above gives it. */
  .status-hero { padding: 26px 0 10px; }
  .status-orb { width: min(320px, 78vw); }

  .sidebar {
    flex-direction: row;
    align-items: center;
    gap: 6px;
    height: 68px;
    min-height: 0;
    padding: 0 14px;
    border-right: 0;
    border-bottom: 1px solid rgba(180, 124, 46, 0.22);
    position: sticky;
    top: 0;
    z-index: var(--z-sticky-bar);
  }

  .sidebar::before,
  .sidebar-divider { display: none; }
  /* Wider box, not taller: the lockup is 1.33:1, so a square box renders it
   * 33px tall and the wordmark disappears. `contain` still forbids stretch. */
  .sidebar-logo { width: 62px; height: 44px; }
  /* min-width:0 + overflow lets the RAIL scroll on a very narrow phone rather
   * than pushing the page into a horizontal scroll. Shrinking the rows instead
   * would drop them under the 44px touch minimum. */
  .primary-navigation {
    flex-direction: row;
    min-width: 0;
    margin: 0 0 0 auto;
    padding: 0;
    gap: 2px;
    overflow-x: auto;
    scrollbar-width: none;
  }
  .primary-navigation::-webkit-scrollbar { display: none; }
  /* flex:none throughout — in the row layout these are the first things the
   * flexbox shrinks, which drops them under the 44px touch minimum at 360px.
   * min-height on the brand for the same reason: its mark is only 44 tall. */
  .sidebar-brand { flex: none; min-height: 44px; }
  .nav-item,
  .nav-item[aria-current="page"] {
    flex: none;
    width: 46px;
    height: 46px;
    border-radius: 12px;
  }
  .nav-icon { width: 24px; height: 24px; }
  .agent-profile { flex: none; width: 46px; height: 46px; margin: 0 0 0 6px; }
  .agent-avatar { width: 42px; height: 42px; font-size: 16px; }

  .dashboard { padding: 0 0 40px; }
  .dashboard-utility { position: static; margin: 20px 20px 0; justify-content: flex-end; }

  .product-grid,
  .metrics-grid {
    grid-template-columns: minmax(0, 1fr);
    gap: 16px;
  }

  .product-card { padding: 22px 22px 20px; }
  .product-title { white-space: normal; }
  .source-toggle { width: 100%; margin-inline: 0; margin-top: 22px; }

  /* The chart gets its own row. Left inline it competes with the text for the
   * same 350px and forces "Billable calls today" onto three lines. */
  .metric-card {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas:
      "icon  body"
      "spark spark";
    align-items: center;
    padding: 20px 20px 18px 22px;
  }
  .metric-icon { grid-area: icon; width: 72px; height: 72px; }
  .metric-icon svg { width: 34px; height: 34px; }
  .metric-body { grid-area: body; }
  .sparkline {
    grid-area: spark;
    position: static;
    width: 100%;
    height: 68px;
    margin-top: 14px;
  }
}


/* ── §14 Reduced motion ────────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    scroll-behavior: auto !important;
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    /* Zeroing duration alone is not enough: a 480ms transition-delay still
     * holds ONLINE off the screen for half a second with nothing moving, which
     * reads as a broken button rather than as reduced motion. */
    transition-delay: 0ms !important;
    animation-delay: 0ms !important;
  }

  /* Offline ⇄ online keeps a short crossfade — an instant swap of a control
   * this large is its own kind of jarring. What goes is the choreography:
   * no sweep, no wing wash, no echo, no breathing. The settled appearance is
   * identical either way. */
  .status-content {
    transition-duration: 120ms !important;
    filter: none !important;
    transform: none !important;
  }

  .orb-halo,
  .orb-ignition,
  .wing-glow-light,
  .orb-face {
    transition-duration: 120ms !important;
  }

  .orb-echo,
  .orb-halo,
  .status-orb,
  .status-orb::before,
  .source-toggle::after,
  .wing-glow-light {
    animation: none !important;
  }
}
