/* ============================================================================
   Barkpark Cloud dashboard — Barkpark Studio's visual language.
   CSS custom properties, light + dark via [data-theme]. No framework.
   ========================================================================== */

/* Self-hosted Inter (charter decision 27) — the console renders fully offline;
   no fonts.googleapis.com. One variable woff2 carries weights 100–900 (we use
   400/500/600/700), latin-subset. Italic is browser-synthesised (used only in
   tiny captions). Font file: fonts/Inter-var.woff2 (Inter, SIL OFL 1.1 — see
   fonts/Inter-OFL.txt). */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("fonts/Inter-var.woff2") format("woff2");
}

/* Self-hosted IBM Plex Mono (GUI-remake GR8) — the console's monospace face for
   IDs, refs, versions, token codes. Three static weights, latin-subset to woff2
   (~13–14KB each). Copyright 2017 IBM Corp., SIL OFL 1.1 — see
   fonts/IBMPlexMono-OFL.txt. Leads var(--mono) below the GENERATED marker. */
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/IBMPlexMono-Regular-latin.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/IBMPlexMono-Medium-latin.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("fonts/IBMPlexMono-SemiBold-latin.woff2") format("woff2");
}

/* BEGIN GENERATED: tokens (design/tokens.json — regenerate: node design/emit.mjs --write; do not hand-edit) */
:root {
  --cc-bg: #f4f5f7;
  --cc-bg-side: #ffffff;
  --cc-card: #ffffff;
  --cc-card2: #eceef2;
  --cc-modal: #ffffff;
  --cc-fg: #1a2330;
  --cc-fg2: #3d4859;
  --cc-fg3: #5f6a7b;
  --cc-fg4: #76818f;
  --cc-line-rgb: 20,30,48;
  --cc-red: #bb4040;
  --cc-red-strong: #b23636;
  --cc-blue: #2560bd;
  --cc-amber: #96660f;
  --bg: var(--cc-bg);
  --surface: var(--cc-card);
  --muted-surface: var(--cc-card2);
  --text: var(--cc-fg);
  --muted-text: var(--cc-fg2);
  --dim: var(--cc-fg3);
  --border: rgba(var(--cc-line-rgb), 0.12);
  --warn-hsl: 35 92% 38%; --danger-hsl: 0 68% 42%; --info-hsl: 217.2 91.2% 59.8%;
  --warn: hsl(var(--warn-hsl)); --danger: var(--cc-red-strong); --info: var(--cc-blue);
  --warn-soft: hsl(var(--warn-hsl) / 0.15); --danger-soft: hsl(var(--danger-hsl) / 0.15); --info-soft: hsl(var(--info-hsl) / 0.15);
  --warn-strong: #7d5500;
  --primary: hsl(151.96 71.81% 29.22%);
  --primary-fg: hsl(0 0% 100%);
  --ring: hsl(163 42% 30%);
  --primary-hsl: 151.96 71.81% 29.22%;
  --ok-hsl: 151.96 71.81% 29.22%;
  --ok: hsl(var(--ok-hsl));
  --ok-soft: hsl(var(--ok-hsl) / 0.15);
  --ok-strong: hsl(151.96 71.81% 23.22%);
  --provider-hetzner: #c4123a;
  --provider-azure: #0f6cbd;
}
[data-theme="dark"] {
  --cc-bg: #0b0e13;
  --cc-bg-side: #0d1117;
  --cc-card: #10141b;
  --cc-card2: #161b24;
  --cc-modal: #12161d;
  --cc-fg: #e9edf3;
  --cc-fg2: #c3cad5;
  --cc-fg3: #8d96a5;
  --cc-fg4: #626c7c;
  --cc-line-rgb: 255,255,255;
  --cc-red: #e57f7f;
  --cc-red-strong: #e56a6a;
  --cc-blue: #7fb4ff;
  --cc-amber: #e8b45a;
  --bg: var(--cc-bg);
  --surface: var(--cc-card);
  --muted-surface: var(--cc-card2);
  --text: var(--cc-fg);
  --muted-text: var(--cc-fg2);
  --dim: var(--cc-fg3);
  --border: rgba(var(--cc-line-rgb), 0.14);
  --warn-hsl: 38 94% 56%; --danger-hsl: 0 82% 66%; --info-hsl: 217.2 91.2% 59.8%;
  --warn: hsl(var(--warn-hsl)); --danger: var(--cc-red); --info: var(--cc-blue);
  --warn-soft: hsl(var(--warn-hsl) / 0.2); --danger-soft: hsl(var(--danger-hsl) / 0.2); --info-soft: hsl(var(--info-hsl) / 0.2);
  --warn-strong: #e8b45a;
  --primary: hsl(152.92 60% 52.94%);
  --primary-fg: hsl(163 45% 8%);
  --ring: hsl(152.92 60% 52.94%);
  --primary-hsl: 152.92 60% 52.94%;
  --ok-hsl: 152.92 60% 52.94%;
  --ok: hsl(var(--ok-hsl));
  --ok-soft: hsl(var(--ok-hsl) / 0.2);
  --ok-strong: hsl(152.92 60% 60.94%);
  --provider-hetzner: #f0728a;
  --provider-azure: #4aa3e0;
}
/* instance-lifecycle glyph tones — colour READ THROUGH the state's status
   role (Decision 7: identity is never a state voice); theme-invariant since
   the referenced role var flips per theme. A later wave rewires statusPill/
   bucketOf onto these. */
.bp-inst--provisioning { color: var(--info); }
.bp-inst--live { color: var(--ok); }
.bp-inst--degraded { color: var(--warn); }
.bp-inst--stopped { color: var(--muted-text); }
.bp-inst--archived { color: var(--muted-text); }
.bp-inst--decommissioned { color: var(--danger); }
.bp-inst--adopted { color: var(--info); }
/* ── theme identity (data-bp-theme) — orthogonal to light/dark mode; the bare declarations above are the evergreen fallback (no attribute → renders exactly as today) ── */
html[data-bp-theme="evergreen"] {
  --primary: hsl(151.96 71.81% 29.22%);
  --primary-fg: hsl(0 0% 100%);
  --ring: hsl(163 42% 30%);
  --primary-hsl: 151.96 71.81% 29.22%;
  --ok-hsl: 151.96 71.81% 29.22%;
  --ok: hsl(var(--ok-hsl));
  --ok-soft: hsl(var(--ok-hsl) / 0.15);
  --ok-strong: hsl(151.96 71.81% 23.22%);
}
html[data-bp-theme="evergreen"][data-theme="dark"] {
  --primary: hsl(152.92 60% 52.94%);
  --primary-fg: hsl(163 45% 8%);
  --ring: hsl(152.92 60% 52.94%);
  --primary-hsl: 152.92 60% 52.94%;
  --ok-hsl: 152.92 60% 52.94%;
  --ok: hsl(var(--ok-hsl));
  --ok-soft: hsl(var(--ok-hsl) / 0.2);
  --ok-strong: hsl(152.92 60% 60.94%);
}
html[data-bp-theme="charple"] {
  --primary: hsl(282.39 44.23% 40.78%);
  --primary-fg: hsl(0 0% 100%);
  --ring: hsl(282.39 44.23% 48.78%);
  --primary-hsl: 282.39 44.23% 40.78%;
  --ok-hsl: 282.39 44.23% 40.78%;
  --ok: hsl(var(--ok-hsl));
  --ok-soft: hsl(var(--ok-hsl) / 0.15);
  --ok-strong: hsl(282.39 44.23% 34.78%);
}
html[data-bp-theme="charple"][data-theme="dark"] {
  --primary: hsl(282.56 58.11% 70.98%);
  --primary-fg: hsl(282.56 45% 8%);
  --ring: hsl(282.56 58.11% 70.98%);
  --primary-hsl: 282.56 58.11% 70.98%;
  --ok-hsl: 282.56 58.11% 70.98%;
  --ok: hsl(var(--ok-hsl));
  --ok-soft: hsl(var(--ok-hsl) / 0.2);
  --ok-strong: hsl(282.56 58.11% 78.98%);
}
html[data-bp-theme="ember"] {
  --primary: hsl(19.33 71.29% 40.98%);
  --primary-fg: hsl(0 0% 100%);
  --ring: hsl(19.33 71.29% 48.98%);
  --primary-hsl: 19.33 71.29% 40.98%;
  --ok-hsl: 19.33 71.29% 40.98%;
  --ok: hsl(var(--ok-hsl));
  --ok-soft: hsl(var(--ok-hsl) / 0.15);
  --ok-strong: hsl(19.33 71.29% 34.98%);
}
html[data-bp-theme="ember"][data-theme="dark"] {
  --primary: hsl(19.01 75.53% 63.14%);
  --primary-fg: hsl(19.01 45% 8%);
  --ring: hsl(19.01 75.53% 63.14%);
  --primary-hsl: 19.01 75.53% 63.14%;
  --ok-hsl: 19.01 75.53% 63.14%;
  --ok: hsl(var(--ok-hsl));
  --ok-soft: hsl(var(--ok-hsl) / 0.2);
  --ok-strong: hsl(19.01 75.53% 71.14%);
}
html[data-bp-theme="fjord"] {
  --primary: hsl(191.6 80.95% 28.82%);
  --primary-fg: hsl(0 0% 100%);
  --ring: hsl(191.6 80.95% 36.82%);
  --primary-hsl: 191.6 80.95% 28.82%;
  --ok-hsl: 191.6 80.95% 28.82%;
  --ok: hsl(var(--ok-hsl));
  --ok-soft: hsl(var(--ok-hsl) / 0.15);
  --ok-strong: hsl(191.6 80.95% 22.82%);
}
html[data-bp-theme="fjord"][data-theme="dark"] {
  --primary: hsl(193.27 62.43% 64.51%);
  --primary-fg: hsl(193.27 45% 8%);
  --ring: hsl(193.27 62.43% 64.51%);
  --primary-hsl: 193.27 62.43% 64.51%;
  --ok-hsl: 193.27 62.43% 64.51%;
  --ok: hsl(var(--ok-hsl));
  --ok-soft: hsl(var(--ok-hsl) / 0.2);
  --ok-strong: hsl(193.27 62.43% 72.51%);
}
html[data-bp-theme="iris"] {
  --primary: hsl(248.75 63.72% 55.69%);
  --primary-fg: hsl(0 0% 100%);
  --ring: hsl(248.75 63.72% 62%);
  --primary-hsl: 248.75 63.72% 55.69%;
  --ok-hsl: 248.75 63.72% 55.69%;
  --ok: hsl(var(--ok-hsl));
  --ok-soft: hsl(var(--ok-hsl) / 0.15);
  --ok-strong: hsl(248.75 63.72% 49.69%);
}
html[data-bp-theme="iris"][data-theme="dark"] {
  --primary: hsl(248.87 100% 77.45%);
  --primary-fg: hsl(248.87 45% 8%);
  --ring: hsl(248.87 100% 77.45%);
  --primary-hsl: 248.87 100% 77.45%;
  --ok-hsl: 248.87 100% 77.45%;
  --ok: hsl(var(--ok-hsl));
  --ok-soft: hsl(var(--ok-hsl) / 0.2);
  --ok-strong: hsl(248.87 100% 85.45%);
}
/* END GENERATED: tokens */

/* coherence-fixture legend chips (coherenceFixtureToHtml) — the committed TUI
   golden text painted in the SAME lifecycle vocabulary as .bp-inst--* above, so
   the terminal surface and the console speak one palette. The role words follow
   the .notice tone-suffix grammar (role word → role token, no new colours); the
   hex cell is .token-chip-shaped and carries a leading swatch of the literal
   #rrggbb the fixture names. --hex is declared here with a safe default and
   OVERRIDDEN inline per cell by coherenceFixtureToHtml — the default is what
   makes the chip legible if that inline style is ever dropped. */
.bp-lc-info { color: var(--info); }
.bp-lc-warn { color: var(--warn); }
.bp-lc-ok { color: var(--ok); }
.bp-lc-danger { color: var(--danger); }
.bp-lc-hex {
  --hex: currentColor;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-family: var(--mono);
  font-size: 11px;
  padding: 0 6px;
  border: 1px solid var(--border);
  border-radius: var(--pill);
  color: var(--muted-text);
}
.bp-lc-hex::before {
  content: "";
  width: 8px;
  height: 8px;
  flex: 0 0 auto;
  border-radius: var(--pill);
  border: 1px solid var(--border);
  background: var(--hex);
}

:root {
  /* GR7 alias bridge (GUI-remake wave 2): the shell vocabulary
     (--bg/--surface/--muted-surface/--text/--muted-text/--dim/--border), the
     status roles (--ok / --warn / --danger / --info / --warn-strong families), --ring,
     and the brand --primary/--primary-fg are now OWNED by the
     GENERATED block above (design/tokens.json → color.cloudChrome + the
     per-identity accent, mapped role-for-role onto the designer ladder).
     --accent is RETIRED (gr-p3-site-detail): its sole consumer reads
     --cc-amber directly. One
     generated move retints this whole file. Freeze-by-generation (GR11): tune
     values in tokens.json, NEVER here. Only tokens the emitter does not yet own
     stay hand-authored below. */

  /* Solid button fills — decoupled from --primary so dark can keep a light
     sage LINK while buttons carry a deeper fill with LIGHT text (user call,
     2026-07-04: "buttons should have light text in dark"). In light they
     resolve to the brand pair; dark re-declares them. Contrast-gated. */
  --btn-bg: var(--primary);
  --btn-fg: var(--primary-fg);
  --btn-danger-bg: var(--danger);
  --btn-danger-fg: hsl(0 0% 100%);
  --ring-soft: hsl(163 42% 30% / 0.15);

  /* Legacy aliases — existing rules still reference these; they resolve to the
     generated semantic roles. New rules use --ok / --danger directly. */
  --success: var(--ok);
  --destructive: var(--danger);

  /* Build/provision console — a dark terminal in BOTH themes by design. */
  --console-bg: hsl(240 10% 8%);
  --console-fg: hsl(0 0% 88%);
  --console-dim: hsl(240 5% 55%);

  --radius-sm: 6px;
  --radius: 8px;
  --radius-lg: 12px;
  --pill: 999px;

  /* Type scale (decision 29) — sizes with PAIRED line-heights. Values pin the
     sizes the SPA already uses; the whole-file migration is the decision-24
     sweep. Shared primitives below consume these now. */
  --text-xs: 12px;   --leading-xs: 1.4;
  --text-sm: 13px;   --leading-sm: 1.45;
  --text-base: 14px; --leading-base: 1.5;
  --text-lg: 16px;   --leading-lg: 1.4;
  --text-xl: 20px;   --leading-xl: 1.3;
  --text-2xl: 26px;  --leading-2xl: 1.2;

  /* Space scale — the 4px grid (decision 29). --step stays as a legacy alias
     (= --space-2) until the decision-24 sweep retires it. --space-1/7/8 retired
     GR29 (gr-p3-hygiene-guard) — zero consumers; the used steps are 2–6. */
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --step: 8px;

  /* Motion (decision 29) — durations + easing; the reduced-motion media block
     at the end of the token area collapses durations to 0. Spinners keep a
     literal duration on purpose: a loading indicator is state, not decoration. */
  --dur-1: 0.15s;
  --dur-2: 0.18s;
  --dur-3: 0.28s;
  --ease: ease;
  --t: var(--dur-1) var(--ease);

  /* Elevation ladder (decision 29). --shadow / --shadow-sm are legacy aliases
     resolving into the ladder; the dark block re-tunes the --elev-* values and
     re-declares the aliases (scoped-theme substitution — see the E8 note there). */
  --elev-0: none;
  --elev-1: 0 1px 2px rgba(0, 0, 0, 0.05);
  --elev-2: 0 4px 16px rgba(0, 0, 0, 0.08);
  --elev-3: 0 16px 40px rgba(0, 0, 0, 0.14);
  --shadow: var(--elev-2);
  --shadow-sm: var(--elev-1);

  /* Z-index ladder (decision 29) — replaces the magic 19/20/30/50/60. */
  --z-tabnav: 19;
  --z-topbar: 20;
  --z-menu: 30;
  --z-modal: 50;
  --z-toast: 60;

  --font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --mono: "IBM Plex Mono", "SF Mono", "Fira Code", ui-monospace, Menlo, monospace;
}

[data-theme="dark"] {
  /* GR7 alias bridge (GUI-remake wave 2): shell + status + --ring +
     brand --primary/--primary-fg are OWNED by the generated [data-theme="dark"]
     block above (color.cloudChrome + per-identity accent). The re-declarations
     below stay hand-authored ONLY for the tokens the emitter does not yet own,
     and MUST re-declare any :root alias that references a dark-re-themed role
     (E8): --btn-*, --ring-soft, --success, --destructive. */

  /* Solid button fills, dark: IDENTITY-AWARE (GR37, gr-p4-hygiene) — --btn-bg/fg
     follow the identity primary pair exactly as light mode does (:root above),
     so ember/fjord/charple/iris CTAs carry THEIR accent, not a frozen evergreen
     green. The dark --primary-fg is already the contrast-safe on-primary tone
     (near-black on the bright dark accent; the --btn-fg/--btn-bg CONTRAST_PAIRS
     row holds across all 10 theme states — E5). Re-declared here rather than
     aliased (E8: scoped-dark subtrees must not freeze light values). authButton
     stays identity-invariant (pre-login has no identity — see color.authButton). */
  --btn-bg: var(--primary);
  --btn-fg: var(--primary-fg);
  --btn-danger-bg: hsl(0 65% 48%);
  --btn-danger-fg: hsl(0 0% 100%);
  --ring-soft: hsl(160 42% 62% / 0.2);

  /* Alias re-declarations — REQUIRED, not redundant. var() inside a custom
     property substitutes where the property is DECLARED, so a :root-only alias
     freezes the LIGHT value for any subtree that scopes [data-theme="dark"]
     onto a non-root element (the styleguide's side-by-side panes do exactly
     that). Gated by E8 in __css_check.mjs. */
  --success: var(--ok);
  --destructive: var(--danger);

  /* Elevation — deeper shadows on the dark surfaces; the --shadow aliases are
     re-declared below so scoped-dark subtrees pick them up (see E8 note). */
  --elev-1: 0 1px 2px rgba(0, 0, 0, 0.3);
  --elev-2: 0 4px 20px rgba(0, 0, 0, 0.4);
  --elev-3: 0 16px 48px rgba(0, 0, 0, 0.55);
  --shadow: var(--elev-2);
  --shadow-sm: var(--elev-1);

  --console-bg: hsl(240 10% 8%);
  --console-fg: hsl(0 0% 88%);
  --console-dim: hsl(240 5% 55%);
}

/* Motion collapses when the user asks for it (decision 29). Only the token
   durations go to 0 — spinner keyframes keep their literal duration because a
   progress indicator is state, not decoration. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --dur-1: 0s;
    --dur-2: 0s;
    --dur-3: 0s;
  }
}

* { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  font-family: var(--font);
  font-size: var(--text-base);
  font-weight: 400;
  line-height: var(--leading-base);
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  transition: background var(--t), color var(--t);
}

[hidden] { display: none !important; }

h1 { font-size: 20px; font-weight: 700; letter-spacing: -0.02em; margin: 0; }
h2 { font-size: 16px; font-weight: 600; margin: 0; }
a { color: var(--primary); text-decoration: none; }
a:hover { text-decoration: underline; }

.dim { color: var(--dim); }
.muted { color: var(--muted-text); }

/* ---------------------------------------------------------- wordmark */
.wordmark { display: inline-flex; align-items: center; gap: 9px; color: var(--text); }
.wordmark:hover { text-decoration: none; }
/* The mark is a real inline SVG in the markup (an evergreen — decision 27),
   drawn in currentColor so it themes for free through this one rule. */
.wordmark-mark {
  width: 18px;
  height: 18px;
  display: block;
  flex: 0 0 auto;
  color: var(--primary);
}
.wordmark-text { font-size: 15px; font-weight: 500; letter-spacing: -0.01em; }
.wordmark-text b { font-weight: 700; }

/* ---------------------------------------------------------- buttons */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 32px;
  padding: 0 14px;
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--t), border-color var(--t), color var(--t), box-shadow var(--t), opacity var(--t);
}
.btn:hover { background: var(--muted-surface); }
.btn:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--ring-soft); }
.btn:disabled { opacity: 0.55; cursor: not-allowed; }

/* Shared keyboard focus ring (WCAG 2.4.7) — the house ring from .btn above,
   for interactive chrome that would otherwise fall back to the near-invisible
   UA outline on the dark near-black bg. :focus-visible only (no mouse-click
   ring). New panels: add your class here rather than re-rolling the ring. */
.nav-link:focus-visible,
.nav-menu summary:focus-visible,
.nav-menu-item:focus-visible,
.auth-tab:focus-visible,
.inst-tab:focus-visible,
.affix-btn:focus-visible,
.btn-link:focus-visible,
.modal-x:focus-visible,
.toast-close:focus-visible,
.topbar-link:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--ring-soft);
}

.btn-primary {
  background: var(--btn-bg);
  color: var(--btn-fg);
  border-color: var(--btn-bg);
}
.btn-primary:hover { background: var(--btn-bg); filter: brightness(1.08); }

.btn-ghost { background: transparent; border-color: transparent; color: var(--muted-text); }
.btn-ghost:hover { background: var(--muted-surface); color: var(--text); }

.btn-sm { height: 28px; padding: 0 10px; font-size: var(--text-xs); }
/* Full-width CTA (auth submit, launch, ready-fold, Vercel handoff): every
   .btn-block in the product is a screen's ONE primary-weight action, so it
   carries the taller 40px front-door hit target (v4, gr-p2-front-door). */
.btn-block { width: 100%; height: 40px; font-size: var(--text-base); }

/* ---------------------------------------------------------- inputs */
.label { display: block; font-size: var(--text-sm); font-weight: 500; margin-bottom: 6px; color: var(--text); }
.field { margin-bottom: var(--space-4); }
.field-hint { font-size: var(--text-xs); margin: 6px 0 0; }

.form-input {
  width: 100%;
  height: 38px;
  padding: 0 var(--space-3);
  font-family: inherit;
  font-size: var(--text-base);
  color: var(--text);
  background: var(--surface);
  border: 1px solid rgba(var(--cc-line-rgb), 0.14);
  border-radius: var(--radius-sm);
  transition: border-color var(--t), box-shadow var(--t);
  appearance: none;
}
select.form-input {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  padding-right: 32px;
}
.form-input::placeholder { color: var(--dim); }
/* Focus rides the IDENTITY accent (not the evergreen-frozen --ring-soft): the
   ring recolours across all 5 skins × both modes for free (gr-p2-front-door). */
.form-input:focus {
  outline: none;
  border-color: var(--ring);
  box-shadow: 0 0 0 3px hsl(var(--primary-hsl) / 0.15);
}

/* ---------------------------------------------------------- card */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}

/* ------------------------------------------ update panel (isu-w5) */
.update-panel { padding: 16px 18px; margin-bottom: 20px; }
.update-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}
.update-panel-head h2 { margin: 0; }
.update-badge { flex: 0 0 auto; }
.update-panel-body { display: flex; flex-direction: column; gap: 2px; }
.update-panel-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}

/* ---------------------------------------------------------- badge */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 22px;
  padding: 0 10px;
  font-size: 12px;
  font-weight: 500;
  border-radius: var(--pill);
  background: var(--muted-surface);
  color: var(--muted-text);
  white-space: nowrap;
}
.badge .dot {
  width: 7px;
  height: 7px;
  border-radius: var(--pill);
  background: var(--dim);
  flex: 0 0 auto;
}
.badge .dot.up { background: var(--success); }
.badge .dot.down { background: var(--destructive); }
.badge .dot.unknown { background: var(--dim); }
.badge .dot.online { background: var(--success); }
.badge .dot.offline { background: var(--dim); }
.badge .dot.warn { background: var(--warn); } /* update available */

/* ---------------------------------------------------------- notices */
.notice {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  border-radius: var(--radius-sm);
  font-size: 13px;
  margin-bottom: calc(var(--step) * 2);
}
.notice-warn {
  background: hsl(var(--warn-hsl) / 0.1);
  border: 1px solid hsl(var(--warn-hsl) / 0.3);
  color: var(--text);
}
.notice-ok {
  background: hsl(var(--ok-hsl) / 0.1);
  border: 1px solid hsl(var(--ok-hsl) / 0.3);
  color: var(--text);
}
.notice-error {
  background: hsl(var(--danger-hsl) / 0.08);
  border: 1px solid hsl(var(--danger-hsl) / 0.25);
  color: var(--text);
}
.notice .btn { margin-left: auto; }

/* destructive action button (e.g. Remove instance) — rides the solid button
   fill tokens (white label both themes; dark uses a deeper red than the
   status text colour); the pairing is contrast-gated in __css_check.mjs. */
.btn-danger {
  background: var(--btn-danger-bg);
  color: var(--btn-danger-fg);
  border-color: var(--btn-danger-bg);
}
.btn-danger:hover { background: var(--btn-danger-bg); filter: brightness(1.08); }

/* instance/site detail-head inline actions (retry / remove) */
.detail-actions { display: inline-flex; gap: 8px; margin-left: 8px; }

/* a failed provision reads in the danger colour, not the muted url style
   (gr-p3: retired the legacy --destructive alias for the direct --danger role) */
.fleet-url.failed { color: var(--danger); font-family: var(--font); font-style: italic; }

/* billing: small meta lines under the plan card, and the active-tier highlight */
.plan-meta { font-size: 12px; margin: 6px 0 0; }
.tier-current { border-color: var(--ring); box-shadow: 0 0 0 1px var(--ring-soft); }

.form-error {
  font-size: 13px;
  /* Copy rides --text like .notice-error / .cm-error — the tint + border say
     "error"; red-on-red copy read muddy in dark (user call 2026-07-04). */
  color: var(--text);
  margin: 0 0 calc(var(--step) * 2);
  padding: 10px 12px;
  background: hsl(var(--danger-hsl) / 0.08);
  border: 1px solid hsl(var(--danger-hsl) / 0.25);
  border-radius: var(--radius-sm);
}

/* ============================================================ AUTH
   The front door (B-01..B-03: login/signup, reset request+form, 2FA challenge),
   composed fresh from the styleguide vocabulary on the v4 ladder (GR21 — no
   agency mock exists for auth; the component spec is styleguide.html). Chrome
   rides the identity-invariant cloudChrome family; ONLY the accent
   (--primary…) varies per skin, so the welcome glow, active tab, focus rings
   and links restyle across all 5 identities × light/dark for free. The shared
   form primitives (.field/.label/.form-input/.btn-block/.input-affix/
   .check-row/.oauth-*, in their sections above) stay UNSCOPED on purpose:
   /new's auth step and /activate inherit them by class identity — never scope
   them under .auth-card, never import Launch Theater's .new-* here. */
.auth-screen {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background:
    radial-gradient(90% 55% at 50% -5%, hsl(var(--primary-hsl) / 0.08) 0%, transparent 70%),
    var(--bg);
}
.auth-center { flex: 1; display: grid; place-items: center; padding: 24px; }
.auth-card {
  width: 100%;
  max-width: 400px;
  padding: 36px 32px 32px;
  background: var(--cc-card);
  border: 1px solid rgba(var(--cc-line-rgb), 0.1);
  box-shadow: var(--elev-2);
}
.auth-brand { display: flex; align-items: center; gap: 9px; margin-bottom: 26px; }
.auth-brand .wordmark-text { font-size: 16px; }

/* Card heading + supporting copy — AUTH-scoped by name, shared by the reset
   card and the 2FA challenge card at ALL THREE of its mount sites (#twofa-card,
   /new's #new-twofa, the /activate park→resume). GR21: theater's
   .new-title/.new-desc never render on an auth surface. */
.auth-title {
  font-size: var(--text-xl);
  line-height: var(--leading-xl);
  font-weight: 700;
  letter-spacing: -0.01em;
  margin: 0 0 6px;
}
.auth-desc {
  font-size: var(--text-sm);
  line-height: 1.55;
  color: var(--dim);
  margin: 0 0 20px;
}

/* Segmented Log in / Sign up switch — card2 track, card-raised active thumb. */
.auth-tabs {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px;
  padding: 4px;
  background: var(--cc-card2);
  border: 1px solid rgba(var(--cc-line-rgb), 0.06);
  border-radius: var(--radius);
  margin-bottom: 24px;
}
.auth-tab {
  height: 32px;
  border: none;
  background: transparent;
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--dim);
  cursor: pointer;
  transition: background var(--t), color var(--t), box-shadow var(--t);
}
.auth-tab:hover { color: var(--text); }
.auth-tab.is-active {
  background: var(--cc-card);
  color: var(--text);
  font-weight: 600;
  box-shadow: var(--elev-1);
}
.auth-foot { font-size: var(--text-sm); text-align: center; margin: 20px 0 0; }

/* ---------------------------------------------- OAuth / SSO (oauth-sso) */
/* "or" divider between password auth and the provider buttons — the mono
   eyebrow voice (machine string, meta duty: --cc-fg4 is capped ≥3:1). */
.oauth-divider {
  display: flex; align-items: center; gap: 12px;
  margin: 20px 0; color: var(--cc-fg4);
  font-family: var(--mono); font-size: 10.5px;
  text-transform: uppercase; letter-spacing: 0.14em;
}
.oauth-divider::before, .oauth-divider::after {
  content: ""; flex: 1; height: 1px; background: rgba(var(--cc-line-rgb), 0.1);
}
.oauth-buttons { display: flex; flex-direction: column; gap: 10px; }
.btn-oauth {
  background: var(--cc-card); color: var(--text);
  border: 1px solid rgba(var(--cc-line-rgb), 0.14);
}
.btn-oauth:hover { background: var(--cc-card); border-color: rgba(var(--cc-line-rgb), 0.3); }

/* ========================================================== SHELL */
.app-shell { display: flex; flex-direction: column; min-height: 100vh; }

/* ---------------------------------------------------------- topbar */
.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  height: 56px;
  padding: 0 20px;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
  position: sticky;
  top: 0;
  z-index: var(--z-topbar);
}
.topbar-left, .topbar-right { display: flex; align-items: center; gap: 12px; min-width: 0; }
/* GR116: .topbar itself never overflows (right edge 753 at a 768 viewport) —
   its CHILDREN do. They are flex items at the default min-width:auto, floored
   at min-content, so when past-due injects the billing chip the topbar's
   children escape to 775-780. The broken band reaches ~782 — ABOVE the 768
   breakpoint (769px still measured 775.22 light / 776.92 dark) — so NO
   max-width media rule can close it; the fix must be breakpoint-free. Letting
   every topbar-right child shrink (the chip clips via its own overflow:hidden
   below) measured 0/44 overflowing across 721-1440 x 2 themes x 2 past-due
   scenarios. Width-independent by construction: it cannot regress when chip
   text or locale changes. */
.topbar-right > * { min-width: 0; }
.crumb-sep { color: var(--border); font-size: 16px; flex: 0 0 auto; }

.org-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 30px;
  padding: 0 10px 0 6px;
  border-radius: var(--radius-sm);
  min-width: 0;
}
.org-avatar {
  width: 20px; height: 20px;
  border-radius: 5px;
  background: var(--primary);
  color: var(--primary-fg);
  display: grid; place-items: center;
  font-size: 11px; font-weight: 600;
  flex: 0 0 auto;
}
.org-name { font-size: 13px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.topbar-link { font-size: 13px; font-weight: 500; color: var(--muted-text); }
.topbar-link:hover { color: var(--text); text-decoration: none; }

.acct-btn { padding: 0; border: none; background: none; cursor: pointer; border-radius: var(--pill); flex: 0 0 auto; }
.acct-btn:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--ring-soft); }
.acct-avatar {
  width: 30px; height: 30px;
  border-radius: var(--pill);
  background: var(--primary);
  color: var(--primary-fg);
  display: grid; place-items: center;
  font-size: 13px; font-weight: 600;
}

/* ---------------------------------------------------------- tab nav */
.tabnav {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 0 20px;
  height: 46px;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
  position: sticky;
  top: 56px;
  z-index: var(--z-tabnav);
  /* overflow VISIBLE so the Settings disclosure can drop below the bar; the
     primary links scroll inside .tabnav-primary instead (charter decision 6). */
  overflow: visible;
}
/* The four primary links; scroll horizontally on narrow screens without
   clipping the Settings dropdown (which lives outside this scroller). */
.tabnav-primary {
  display: flex;
  align-items: center;
  gap: 4px;
  height: 46px;
  flex: 1 1 auto;
  min-width: 0;
  overflow-x: auto;
}
.tabnav .nav-link {
  position: relative;
  display: flex;
  align-items: center;
  height: 46px;
  padding: 0 12px;
  font-size: 13px;
  font-weight: 500;
  color: var(--muted-text);
  white-space: nowrap;
  transition: color var(--t);
}
.tabnav .nav-link:hover { color: var(--text); text-decoration: none; }
.tabnav .nav-link.is-active { color: var(--text); }
.tabnav .nav-link.is-active::after {
  content: "";
  position: absolute;
  left: 8px; right: 8px; bottom: 0;
  height: 2px;
  background: var(--text);
  border-radius: 2px;
}

/* ---------------------------------------------------------- content */
.content { flex: 1; width: 100%; max-width: 1040px; margin: 0 auto; padding: 32px 24px; }
.view-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 28px;
}
.view-head p { margin: 4px 0 0; font-size: 13px; }
/* A4: header action cluster (Fleet: Refresh + Launch). */
.view-head-actions { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }

/* ---------------------------------------------------------- footer */
.appfoot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 16px 24px;
  border-top: 1px solid var(--border);
  color: var(--dim);
  font-size: 12px;
}
.appfoot-brand i { font-style: italic; }
.appfoot-links { display: flex; gap: 16px; }
.appfoot-links a { color: var(--muted-text); }
.appfoot-links a:hover { color: var(--text); text-decoration: none; }

/* ---------------------------------------------------------- fleet */
.fleet-body { display: flex; flex-direction: column; gap: 10px; }
.fleet-row {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 16px 18px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  transition: border-color var(--t);
}
.fleet-row:hover { border-color: var(--dim); }
/* v4 screens/01: the lifecycle pill LEADS the row in its own column so names
   align down the list; the pill's detail truncates via the existing .status-pill
   rules (never edited here — charter GR11/GR25). */
.fleet-status { flex: 0 0 auto; min-width: 116px; display: flex; align-items: center; }
.fleet-main { min-width: 0; flex: 1; }
.fleet-name { font-size: 14px; font-weight: 600; line-height: 1.3; }
.fleet-url { font-size: 12px; color: var(--muted-text); font-family: var(--mono); margin-top: 3px; }
.fleet-url.provisioning { color: var(--dim); font-family: var(--font); font-style: italic; }
/* The mono metadata line: region · size · version · channel · autoupdate. */
.fleet-meta { font-size: 12px; color: var(--dim); font-family: var(--mono); margin-top: 3px; }
/* fleetInfraLine — .fleet-meta's denser twin (region · size only), on the v4
   instance card where the full region·size·version·channel·autoupdate line would
   crowd the stat row. Same mono voice, one step quieter and one step tighter. */
.fleet-infra { font-size: 11px; color: var(--dim); font-family: var(--mono); margin-top: 2px; }
.fleet-badges { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
.fleet-version { font-size: 12px; color: var(--dim); font-family: var(--mono); }
/* The inline update chip (v4 "vX available"). Orthogonal to the status pill —
   ready = accent (the box is behind), busy = info (a rollout is landing). */
.fleet-update-chip {
  display: inline-flex; align-items: center; gap: 6px;
  height: 22px; padding: 0 9px;
  border: 1px solid var(--border); border-radius: var(--pill);
  font-family: var(--mono); font-size: 11px; font-weight: 500;
  white-space: nowrap; color: var(--text);
}
.fleet-update-dot { width: 6px; height: 6px; border-radius: var(--pill); background: var(--dim); flex: 0 0 auto; }
.fleet-update-chip--ready { background: var(--ok-soft); border-color: hsl(var(--ok-hsl) / 0.28); color: var(--ok-strong); }
.fleet-update-chip--ready .fleet-update-dot { background: var(--ok); }
.fleet-update-chip--busy { background: var(--info-soft); border-color: hsl(var(--info-hsl) / 0.28); }
.fleet-update-chip--busy .fleet-update-dot { background: var(--info); }
[data-theme="dark"] .fleet-update-chip--ready { background: hsl(var(--ok-hsl) / 0.2); }
[data-theme="dark"] .fleet-update-chip--busy { background: hsl(var(--info-hsl) / 0.2); }

/* stw4-freshness (charter D24): the site-row deploy-freshness badge. Amber pulse
   while a content-auto rebuild is in flight; a settled status·trigger·when pill
   otherwise. Tokens only (no baseline bump): --pill/--radius, --border, --dim,
   --muted-surface/-text, --ok/--danger/--info/--warn + -soft/-strong, --dur/-ease. */
.fresh-badge {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 9px; border-radius: var(--pill);
  font-size: 11px; font-weight: 500; line-height: 1.2; white-space: nowrap;
  background: var(--muted-surface); color: var(--muted-text);
  border: 1px solid var(--border);
}
.fresh-badge .fresh-dot { width: 7px; height: 7px; border-radius: var(--pill); background: var(--dim); flex: 0 0 auto; }
/* The badge's two texts are NOT peers: .fresh-label is the verdict word ("Live",
   "Failed", "Not deployed") and .fresh-meta is the quiet trailer already dimmed
   to .75. The label therefore takes the weight — and never wraps, so a long
   trigger·when trailer can never break the verdict across two lines. */
.fresh-badge .fresh-label { font-weight: 600; white-space: nowrap; }
.fresh-badge .fresh-meta { color: var(--muted-text); opacity: .75; }
.fresh-badge--up { color: var(--text); }
.fresh-badge--up .fresh-dot { background: var(--ok); }
.fresh-badge--down { background: var(--danger-soft); color: var(--danger); border-color: transparent; }
.fresh-badge--down .fresh-dot { background: var(--danger); }
.fresh-badge--down .fresh-meta { color: var(--danger); }
.fresh-badge--deploy .fresh-dot { background: var(--info); }
.fresh-badge--rebuild { background: var(--warn-soft); color: var(--warn); border-color: var(--warn-strong); }
.fresh-badge--rebuild .fresh-dot { background: var(--warn); }
.fresh-badge--rebuild .fresh-meta { color: var(--warn); }
.fresh-badge.is-rebuilding .fresh-dot { animation: fresh-pulse var(--dur-3) var(--ease) infinite; }
.fresh-badge.is-rebuilding { animation: fresh-glow var(--dur-3) var(--ease) infinite; }
@keyframes fresh-pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: .4; transform: scale(1.55); } }
@keyframes fresh-glow { 0%, 100% { box-shadow: 0 0 0 0 var(--warn-soft); } 50% { box-shadow: 0 0 0 3px var(--warn-soft); } }
@media (prefers-reduced-motion: reduce) {
  .fresh-badge.is-rebuilding, .fresh-badge.is-rebuilding .fresh-dot { animation: none; }
}

.empty-state {
  text-align: center;
  padding: 56px 24px;
  border: 1px dashed var(--border);
  border-radius: var(--radius-lg);
  color: var(--muted-text);
}
/* One illustration lifts every empty state — a muted stacked-layers glyph. */
.empty-state::before {
  content: "";
  display: block;
  width: 46px; height: 46px;
  margin: 0 auto 16px;
  opacity: 0.45;
  background: center / contain no-repeat
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 3l8 4.5-8 4.5-8-4.5z'/%3E%3Cpath d='M4 12l8 4.5 8-4.5'/%3E%3Cpath d='M4 16.5l8 4.5 8-4.5'/%3E%3C/svg%3E");
}
.empty-state h2 { margin-bottom: 6px; }
.empty-state p { margin: 0 0 18px; font-size: 13px; }

.loading { padding: 40px; text-align: center; color: var(--dim); font-size: 13px; }

/* ---------------------------------------------------------- tiers */
.tier-grid {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 12px;
}
@media (max-width: 900px) { .tier-grid { grid-template-columns: 1fr 1fr; } }
.tier {
  display: flex;
  flex-direction: column;
  padding: 20px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}
.tier-name { font-size: 13px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted-text); }
.tier-price { font-size: 26px; font-weight: 700; letter-spacing: -0.02em; margin: 10px 0 2px; }
.tier-price small { font-size: 13px; font-weight: 500; color: var(--dim); }
.tier-note { font-size: 12px; color: var(--dim); margin: 0 0 18px; flex: 1; }
.tier .btn { width: 100%; }
.tier-free .tier-price { color: var(--muted-text); }

/* ========================================================== MODAL */
/* GR63 OVERFLOW: the modal root is the ONLY scroll container a modal has —
   `place-items: center` on a fixed, non-scrolling root silently amputates any
   card taller than the viewport (the account modal with 9+ sessions renders
   ~1655px in a 900px viewport, putting Log out and 2FA permanently out of
   reach). `align-items: start` + `overflow-y: auto` gives the root a scroll
   path, and a `margin: auto 0` on the card restores true vertical centring for
   every card that still FITS. Shared primitive on purpose — every tall modal
   (launch, env editor, confirm sheets) inherits the same bug, so a
   `:has(.am-modal)`-scoped patch would only move it. */
   /* REVIEW ADDENDUM: making the root a scroll container also opens a scroll-CHAIN
   into the shell behind it — the shell is `inert` (pointer/keyboard) but inert
   does not stop a wheel, so reaching the bottom of a tall modal would start
   scrolling the page underneath. `overscroll-behavior: contain` closes that;
   it is inert on every modal that fits. */
.modal-root { position: fixed; inset: 0; z-index: var(--z-modal); display: grid; justify-items: center; align-items: start; overflow-y: auto; overscroll-behavior: contain; padding: var(--space-6); }
.modal-backdrop { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.5); backdrop-filter: blur(2px); }
.modal-card {
  position: relative;
  z-index: 1;
  width: 100%;
  margin: auto 0;
  max-width: 420px;
  padding: var(--space-6);
  box-shadow: var(--shadow);
  animation: modal-in var(--dur-1) var(--ease);
}
@keyframes modal-in {
  from { opacity: 0; transform: translateY(6px) scale(0.99); }
  to { opacity: 1; transform: none; }
}
.modal-x {
  position: absolute;
  top: 12px; right: 12px;
  width: 28px; height: 28px;
  display: grid; place-items: center;
  border: none;
  background: transparent;
  color: var(--muted-text);
  font-size: 20px;
  line-height: 1;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.modal-x:hover { background: var(--muted-surface); color: var(--text); }
.modal-title { font-size: var(--text-lg); font-weight: 600; margin: 0 0 4px; }
.modal-sub { font-size: var(--text-sm); color: var(--muted-text); margin: 0 0 18px; }
.modal-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 0; border-top: 1px solid var(--border); }
.modal-row:first-of-type { border-top: none; }
.modal-row .k { font-size: 13px; color: var(--muted-text); }
.modal-row .v { font-size: 13px; font-weight: 500; font-family: var(--mono); }
.modal-actions { display: flex; gap: var(--space-2); margin-top: var(--space-5); }
.modal-actions .btn { flex: 1; }
/* Inline action rows (left-aligned, natural-width buttons) — used inside the
   Account & sessions panel so the revoke-all / update-password buttons don't
   stretch full-width like the primary modal footer. */
.modal-actions.inline { justify-content: flex-start; margin-top: 10px; }
.modal-actions.inline .btn { flex: 0 0 auto; }

/* ---- Account & sessions panel ---- */
.modal-section { font-size: 13px; font-weight: 600; margin: 22px 0 8px; }
.sessions-box { display: flex; flex-direction: column; gap: 8px; }
.session-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 10px 12px; border: 1px solid var(--border); border-radius: 8px;
  background: var(--surface);
}
.session-main { min-width: 0; }
.session-device { font-size: 13px; font-weight: 500; display: flex; align-items: center; gap: 8px; }
.session-meta { font-size: 12px; color: var(--muted-text); margin-top: 2px; font-family: var(--mono); }
.badge-current { background: var(--success); color: var(--primary-fg); }
.pw-form { display: flex; flex-direction: column; gap: 4px; }
.pw-form .field { margin-bottom: 10px; }
.pw-form .field span { display: block; font-size: 12px; color: var(--muted-text); margin-bottom: 4px; }
.pw-form input {
  width: 100%; height: 34px; padding: 0 10px; font-size: 13px;
  border: 1px solid var(--border); border-radius: 8px; background: var(--bg); color: var(--text);
}

/* ========================================================== TOASTS */
.toast-stack {
  position: fixed;
  bottom: var(--space-5);
  left: var(--space-5);
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-width: 360px;
}
.toast {
  display: flex;
  gap: 12px;
  padding: 12px 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  animation: toast-in var(--dur-2) var(--ease);
}
@keyframes toast-in {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: none; }
}
.toast-ico {
  width: 18px; height: 18px;
  flex: 0 0 auto;
  border-radius: var(--pill);
  display: grid; place-items: center;
  font-size: 11px; font-weight: 700;
  color: var(--primary-fg); /* glyph-on-status, both themes — contrast-gated */
  margin-top: 1px;
}
.toast-success .toast-ico { background: var(--success); }
.toast-error .toast-ico { background: var(--destructive); }
.toast-info .toast-ico { background: var(--muted-text); }
.toast-main { min-width: 0; flex: 1; }
.toast-title { font-size: var(--text-sm); font-weight: 600; }
.toast-body { font-size: var(--text-xs); color: var(--muted-text); margin-top: 2px; }
.toast-action { margin-top: 8px; }
.toast-close {
  align-self: flex-start;
  border: none;
  background: transparent;
  color: var(--dim);
  cursor: pointer;
  font-size: 16px;
  line-height: 1;
  padding: 2px;
}
.toast-close:hover { color: var(--text); }

/* ===================================================== PROVIDER PICKER */
.choice-list { display: flex; flex-direction: column; gap: 8px; }
.choice {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 12px 14px;
  text-align: left;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  font: inherit;
  color: var(--text);
  transition: border-color var(--t), background var(--t);
}
.choice:hover:not(:disabled) { border-color: var(--dim); background: var(--muted-surface); }
.choice:disabled { opacity: 0.5; cursor: not-allowed; }
.choice-ico {
  width: 32px; height: 32px;
  flex: 0 0 auto;
  border-radius: 7px;
  display: grid; place-items: center;
  color: #fff; font-weight: 700; font-size: 13px;
}
.choice-ico.sm { width: 24px; height: 24px; font-size: 11px; display: inline-grid; vertical-align: middle; margin-right: 8px; }
.choice-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.choice-name { font-size: 14px; font-weight: 600; }
.choice-sub { font-size: 12px; color: var(--muted-text); margin-top: 1px; }
.choice-chev { color: var(--dim); font-size: 18px; line-height: 1; }
.choice-tag { font-size: 11px; color: var(--muted-text); background: var(--muted-surface); padding: 2px 8px; border-radius: var(--pill); }

/* brand tiles — provider colours, not shipped logos. Hetzner + Azure read from
   the shared --provider-* identity tokens (S4) so the picker tile, the fleet
   provider chip, and the CLI glyph all tint from ONE source; the not-yet-wired
   providers keep their raw brand hues until they graduate. */
.brand-hetzner { background: var(--provider-hetzner); }
.brand-azure { background: var(--provider-azure); }
.brand-do { background: #0080ff; }
.brand-aws { background: #232f3e; }
.brand-vultr { background: #007bfc; }

/* Provider IDENTITY chip (Decision 7 — never a status voice). The tint rides
   --provider-<kind> on the border + the little square mark ONLY; the chip body
   stays on surface/text so identity never doubles as a status pill background
   (DESIGN.md §5). */
.provider-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 2px 9px 2px 7px;
  border: 1px solid var(--border); border-radius: var(--pill);
  font-size: 12px; font-weight: 600; color: var(--text);
  background: var(--surface);
}
.provider-mark { width: 8px; height: 8px; border-radius: 2px; background: var(--dim); flex: 0 0 auto; }
.provider-chip--hetzner { border-color: var(--provider-hetzner); }
.provider-chip--hetzner .provider-mark { background: var(--provider-hetzner); }
.provider-chip--azure { border-color: var(--provider-azure); }
.provider-chip--azure .provider-mark { background: var(--provider-azure); }

/* In-sheet remediation: the server-owned copy that names the exact console fix
   when verify-before-save can't authenticate a credential. Amber (informational,
   not crash-red) — the fix is an action, not a failure. */
.cred-remediation {
  display: flex; align-items: flex-start; gap: 8px;
  margin: 4px 0 14px;
  padding: 10px 12px;
  border: 1px solid var(--warn-strong); border-radius: 8px;
  background: var(--warn-soft);
  font-size: 13px; line-height: 1.45; color: var(--text);
}
.cred-remediation-ico {
  flex: 0 0 auto; width: 18px; height: 18px; margin-top: 1px;
  display: grid; place-items: center;
  border-radius: 50%; background: var(--warn); color: var(--primary-fg);
  font-weight: 700; font-size: 12px;
}
.cred-remediation-body { flex: 1; min-width: 0; }

/* Launch hosting picker: provider tabs → a catalog panel (region + priced
   sizes). The active tab reads from aria-pressed so the class stays static. */
.launch-hosting { margin-bottom: 4px; }
.launch-hosting > .label { display: block; margin-bottom: 6px; }
.seg {
  display: inline-flex; gap: 2px; margin-bottom: 12px;
  padding: 2px; border: 1px solid var(--border); border-radius: 9px;
  background: var(--muted-surface);
}
.seg-btn {
  appearance: none; border: 0; background: none; cursor: pointer;
  padding: 5px 14px; border-radius: 7px;
  font: inherit; font-size: 13px; font-weight: 600; color: var(--muted-text);
}
.seg-btn:hover { color: var(--text); }
.seg-btn[aria-pressed="true"] { background: var(--surface); color: var(--text); box-shadow: var(--shadow-sm); }
.launch-catalog { margin-bottom: 8px; }
.launch-catalog-note { margin: 0 0 12px; font-size: 13px; }
.launch-catalog-empty {
  display: flex; flex-direction: column; align-items: flex-start; gap: 10px;
  margin-bottom: 12px; padding: 14px;
  border: 1px dashed var(--border); border-radius: 10px;
}
.launch-catalog-empty p { margin: 0; font-size: 13px; }
.size-list { display: flex; flex-direction: column; gap: 6px; }
.size-opt {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 12px; border: 1px solid var(--border); border-radius: 9px;
  cursor: pointer;
}
.size-opt:hover { border-color: var(--dim); }
/* GR22: --primary-soft retired (this was its sole consumer) — the selected tint
   reads --ok-soft, the same accent channel/alpha by GR6 law (the --ok family
   TRACKS accent.primary), so the bytes painted are identical. */
.size-opt:has(.size-opt-radio:checked) { border-color: var(--primary); background: var(--ok-soft); }
.size-opt-radio { flex: 0 0 auto; accent-color: var(--primary); }
.size-opt-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.size-opt-name { font-size: 13px; font-weight: 600; color: var(--text); }
.size-opt-spec { font-size: 12px; margin-top: 1px; }
.size-opt-price { flex: 0 0 auto; font-size: 12px; font-weight: 600; color: var(--text); white-space: nowrap; }

.modal-back {
  display: inline-flex; align-items: center; gap: 6px;
  margin-bottom: 14px;
  background: none; border: none; padding: 0;
  cursor: pointer; font: inherit; font-size: 13px; color: var(--muted-text);
}
.modal-back:hover { color: var(--text); }
.modal-head { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
.modal-head .choice-ico { width: 28px; height: 28px; border-radius: 6px; }
.modal-head .modal-title { margin: 0; }

.provider-list { display: flex; flex-direction: column; gap: 10px; }

/* ===================================================== INPUT AFFIX (eye) */
.input-affix { position: relative; }
.input-affix .form-input { padding-right: 40px; }
.affix-btn {
  position: absolute; top: 0; right: 0;
  height: 38px; width: 38px; /* mirrors .form-input's 38px control height */
  display: grid; place-items: center;
  background: none; border: none; cursor: pointer; color: var(--dim);
}
.affix-btn:hover { color: var(--muted-text); }

/* label row with a trailing action (Forgot password?) */
.label-row { display: flex; align-items: baseline; justify-content: space-between; }
.label-row .label { margin-bottom: 6px; }
.label-row a { font-size: 12px; font-weight: 500; }

/* remember-me */
.check-row { display: flex; align-items: center; gap: 8px; margin: 2px 0 18px; }
.check-row input { width: 15px; height: 15px; accent-color: var(--primary); }
.check-row label { font-size: 13px; color: var(--muted-text); cursor: pointer; }

/* ===================================================== PLAN CARD (billing) */
.plan-card { max-width: 460px; padding: 28px; }
/* G-01 (GR33 reference composition): inside the Billing settings anatomy the
   .set-section IS the surface, so the plan STATE card renders FLUSH — no nested
   card chrome (a bordered card inside a bordered section double-frames). The tier
   GRID keeps its own selectable cards; only the state card flattens. Text moves
   from --card onto --surface, both already CONTRAST_PAIRS-covered. */
#billing-recommended .plan-card {
  max-width: none;
  padding: 0;
  border: none;
  background: transparent;
  box-shadow: none;
}
.plan-head { display: flex; align-items: center; gap: 10px; }
.plan-name { font-size: 18px; font-weight: 700; }
.plan-rec {
  font-size: 11px; font-weight: 600; color: var(--ok);
  border: 1px solid hsl(var(--ok-hsl) / 0.4);
  background: hsl(var(--ok-hsl) / 0.1);
  padding: 2px 8px; border-radius: var(--pill);
}
.plan-tagline { font-size: 13px; color: var(--muted-text); margin: 4px 0 16px; }
.plan-price { font-size: 34px; font-weight: 700; letter-spacing: -0.02em; }
.plan-price small { font-size: 14px; font-weight: 500; color: var(--dim); }
.plan-feats { list-style: none; padding: 0; margin: 18px 0 22px; display: flex; flex-direction: column; gap: 10px; }
.plan-feats li { display: flex; align-items: center; gap: 10px; font-size: 13px; }
.plan-feats .ck { color: var(--success); font-weight: 700; }
.plan-more { display: block; text-align: center; margin: 14px 0 0; font-size: 13px; color: var(--muted-text); cursor: pointer; }
.plan-more:hover { color: var(--text); }
#billing-tiers { margin-top: 24px; }

/* ============================================= WELCOME RUNWAY + LAUNCH FLOW (A4)
   The empty-fleet first-run: a centred welcome hero + the launch form, and the
   same launchFlow() component reused compact inside the header modal. */
.runway { display: flex; justify-content: center; padding: 24px 0 8px; }
.launch-flow { text-align: left; } /* modal default; the runway centres below */
.runway .launch-flow { width: 100%; max-width: 540px; padding: 32px; text-align: center; }
.runway-hero { margin-bottom: 22px; }
.runway-title { font-size: 26px; letter-spacing: -0.02em; margin: 0 0 10px; }
.runway-lead { color: var(--muted-text); line-height: 1.55; margin: 0 0 12px; }
.runway-sub {
  display: inline-block;
  font-size: 13px; font-weight: 600;
  color: var(--ok);
  background: var(--ok-soft);
  padding: 4px 12px;
  border-radius: var(--pill);
  margin: 0;
}
/* In the runway the form is centred + full-width; in the modal it's the plain
   left-aligned stack. .field-hint / .new-tier* rules are shared, not duplicated. */
.runway .launch-form .field { text-align: left; }
.launch-flow .launch-form { margin-top: 6px; }
.launch-plan-lead { margin: 6px 0 14px; }
.launch-plan-back { margin-top: 4px; }

/* ===================================================== BREADCRUMBS */
.crumbs { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
.crumbs a { color: var(--muted-text); font-size: 13px; font-weight: 500; }
.crumbs a:hover { color: var(--text); text-decoration: none; }
.crumb-cur { display: inline-flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 500; color: var(--text); min-width: 0; }
.crumb-cur .org-avatar { background: var(--primary); }
.crumb-cur span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ===================================================== INSTANCE DETAIL */
.fleet-row[data-id] { cursor: pointer; }
.fleet-row[data-id]:focus-visible { outline: none; border-color: var(--ring); box-shadow: 0 0 0 2px var(--ring-soft); }
.fleet-chev { color: var(--dim); font-size: 18px; line-height: 1; margin-left: 4px; flex: 0 0 auto; }

.detail-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 24px; }
.detail-head h1 { margin-bottom: 4px; }
.detail-grid { display: grid; grid-template-columns: 1fr 260px; gap: 28px; align-items: start; }
/* The instance workspace rail hosts the per-host domain checklist (#instance-
   domains), which needs room for a rung's label + evidence code on one line — so
   its rail is wider than the shared 260px. SCOPED to the instance workspace: the
   site-deploys grid (bare .detail-grid) is byte-identical. The single-column
   collapse below (GR65: ≤768px, moved up from 720) still wins — same specificity,
   later in source — so this only widens the desktop rail. */
.detail-grid--instance { grid-template-columns: 1fr 340px; }
.detail-main h2 { margin-bottom: 12px; }
.detail-rail { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 18px; box-shadow: var(--shadow-sm); }
.detail-rail h2 { font-size: 12px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted-text); margin-bottom: 6px; }
.rail-row { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; padding: 8px 0; border-top: 1px solid var(--border); }
.rail-row:first-of-type { border-top: none; }
.rail-row .k { font-size: 12px; color: var(--muted-text); flex: 0 0 auto; }
.rail-row .v { font-size: 12px; font-weight: 500; font-family: var(--mono); text-align: right; word-break: break-word; min-width: 0; }
.rail-row .v.plain { font-family: var(--font); }
.rail-row .v.copy { display: inline-flex; align-items: center; justify-content: flex-end; gap: 6px; }
.copy-btn {
  display: inline-grid; place-items: center;
  width: 20px; height: 20px;
  border: none; background: transparent;
  color: var(--dim); cursor: pointer;
  border-radius: 4px; flex: 0 0 auto;
}
.copy-btn:hover { color: var(--text); background: var(--muted-surface); }
.copy-btn:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--ring-soft); }

/* ── Instance Overview rhythm + sectioned Details rail ────────────────────────
   .inst-overview gives the Overview main column a uniform vertical rhythm so the
   update panel + Sites read as evenly-spaced sections, not a flat equal-weight
   stack. The rail is grouped into labelled sub-sections (Identity / Runtime /
   Platform / Activity) instead of one long flat list — it scans as chunks. */
.inst-overview > * + * { margin-top: 20px; }
.rail-group + .rail-group { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border); }
.rail-group-label {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted-text);
  margin-bottom: 4px;
}

/* ── S11b lifecycle action row — the persistent "operate this box" cluster ─────
   Previously UNSTYLED (raw inline spans); rendered here as one compact cluster:
   a state pill + the verb controls, with a single "via the bp CLI" caption. The
   pill mirrors .status-pill; the .bp-inst--<state> class tints its colour and the
   dot reads that hue through currentColor while the label stays neutral. */
.inst-lifecycle-actions { margin: 18px 0 4px; }
.inst-life-head { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; margin-bottom: 10px; }
.inst-life-pill {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 24px;
  padding: 0 11px;
  border: 1px solid var(--border);
  border-radius: var(--pill);
  background: var(--muted-surface);
  font-size: 12px;
  font-weight: 600;
}
.inst-life-dot { width: 7px; height: 7px; border-radius: var(--pill); background: currentColor; flex: 0 0 auto; }
.inst-life-label { color: var(--text); }
.inst-life-status { display: inline-flex; align-items: center; gap: 8px; }
.inst-life-note { font-size: 12px; color: var(--muted-text); }
.inst-life-note--warn { color: var(--warn-strong); }
.inst-life-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px; }
.inst-life-cli { display: inline-flex; align-items: center; gap: 8px; }
.inst-life-verb { font-size: 13px; font-weight: 500; color: var(--text); }
.inst-life-via { margin-top: 8px; font-size: 12px; color: var(--muted-text); }
.inst-life-disabled { display: inline-flex; align-items: center; gap: 8px; }
.inst-life-reason { font-size: 12px; color: var(--muted-text); }

.site-row { display: flex; align-items: center; gap: 14px; padding: 14px 16px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); }
.site-row + .site-row { margin-top: 10px; }
.site-main { flex: 1; min-width: 0; }
.site-name { font-size: 14px; font-weight: 600; }
.site-meta { font-size: 12px; color: var(--muted-text); margin-top: 2px; }
.site-meta .mono { font-family: var(--mono); }
.site-inst { color: var(--text); font-weight: 500; }
/* E-01: the global #sites list adopts the fleet-row anatomy — a fixed leading
   status column, then the main block, then the badges. The instance-Overview
   sites card keeps the compact 2-part layout (no .site-row--global), so this
   never disturbs it. */
.site-row--global { align-items: center; }
.site-status { flex: 0 0 auto; min-width: 116px; display: flex; align-items: center; }
.site-host { font-size: 12px; color: var(--muted-text); font-family: var(--mono); margin-top: 3px; }
.site-inst-link { color: var(--text); font-weight: 500; text-decoration: none; }
.site-inst-link:hover { text-decoration: underline; }
.site-inst-link:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--ring-soft); border-radius: var(--radius-sm); }
/* E-03 env editor: the write-only warning aligns to the top (two lines of law,
   not a one-liner), and the KEY=VALUE editor is a tall mono textarea. */
.env-warn { align-items: flex-start; line-height: 1.5; }
.env-textarea {
  height: auto;
  min-height: 168px;
  padding: 10px var(--space-3);
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.6;
  resize: vertical;
  white-space: pre;
  overflow-wrap: normal;
}
.site-row[data-id] { cursor: pointer; }
.site-row[data-id]:focus-visible { outline: none; border-color: var(--ring); box-shadow: 0 0 0 2px var(--ring-soft); }
/* "Visit ↗" live-site link — sits among the row/detail badges. In a list row it
   is a quiet text link (it must not shout over the drill-in affordance); in the
   detail head it also appears as a ghost button. Both stopPropagation so a click
   opens the live site instead of drilling into the detail view. */
.site-open { font-size: 12px; color: var(--muted-text); text-decoration: none; white-space: nowrap; padding: 2px 6px; border-radius: var(--radius-sm); }
.site-open:hover { color: var(--text); text-decoration: underline; }
.site-open:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--ring-soft); }
a.btn.site-open, a.btn.site-open:hover { text-decoration: none; }

/* ===================================================== DEPLOYMENTS
   gr-p3-site-detail (v4, screens2/05): the deploy ladder is ONE card — hairline-
   separated rows inside a single surface — instead of a stack of boxed rows.
   Row anatomy: mono ref · status pill (.dep-pill CONSUMED, never restyled here —
   GR11/D24) · environment · trigger provenance (Manual / Content update, GR27:
   never a named human) · duration · when. */
.deploys {
  display: flex; flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}
/* Inside the card the shared empty-state sheds its own dashed frame. */
.deploys .empty-state { border: none; border-radius: 0; }
/* gh-6: branch previews — own card, each row edged in the decorative amber.
   Reads --cc-amber DIRECTLY (the --accent alias is retired, GR7 endgame). */
.previews-heading { display: flex; align-items: center; gap: 8px; margin-top: 22px; margin-bottom: 12px; }
.previews-count {
  font-size: 12px; font-weight: 500; color: var(--muted-text);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 999px; padding: 1px 8px;
}
.previews .deploy-row.preview-row { border-left: 3px solid var(--cc-amber); }
.preview-url { text-decoration: none; }
.preview-url:hover { text-decoration: underline; }
.deploy-row {
  display: flex; flex-direction: column;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
}
.deploy-row:last-child { border-bottom: none; }
.deploy-head { display: flex; align-items: center; gap: 14px; }
.deploy-main { flex: 1; min-width: 0; }
.deploy-ref { font-size: 13px; font-weight: 500; }
.deploy-ref .mono { font-family: var(--mono); }
.deploy-meta { font-size: 12px; color: var(--muted-text); margin-top: 2px; }
.deploy-meta .mono { font-family: var(--mono); }
/* The v4 failure panel (v4.dc.html:569-575): a red-bordered box carrying the
   server's failure copy behind a danger dot. A SNAP state — deliberately no
   transition/animation anywhere on it (failed never eases, deploy-ladder law). */
.deploy-fail {
  display: flex; gap: 10px; align-items: flex-start;
  margin-top: 8px;
  padding: 9px 12px;
  border: 1px solid hsl(var(--danger-hsl) / 0.35);
  border-radius: var(--radius);
  background: hsl(var(--danger-hsl) / 0.06);
  color: var(--text);
  font-size: 12.5px;
  line-height: 1.55;
}
.deploy-fail-dot {
  flex: 0 0 auto;
  width: 8px; height: 8px;
  margin-top: 5px;
  border-radius: 50%;
  background: var(--danger);
}
/* dwb-webhook-deploy / D11: BLOCKED tone — a born-failed GitHub-push deploy is a
   capability that isn't enabled yet, not a crash. Calm amber/informational, NOT
   error-red. Warn tokens are theme-invariant (status hues are shared across
   themes) and --warn-strong text on a --warn tint is the same pattern .dep-building
   uses in both themes, so no [data-theme] override is needed. Pill stays "Failed". */
.deploy-fail--blocked {
  color: var(--warn-strong);
  background: var(--warn-soft);
  border-color: hsl(var(--warn-hsl) / 0.3);
}
.deploy-fail--blocked .deploy-fail-dot { background: var(--warn); }
/* dwb-19: the "last step before it broke" caption on the failed /new screen. */
.new-failed-caption { font-size: 12px; color: var(--muted-text); margin: 10px 0 0; }

/* dwb-19: the live sub-caption under an active deployment's status — the
   build-side twin of .new-step-detail, same subtle fade/translate on change. */
.deploy-detail {
  font-size: 12px;
  color: var(--muted-text);
  margin-top: 4px;
  word-break: break-word;
  animation: new-detail-in 0.28s ease;
}

/* Pre-claim queued state (#940): calm and muted — visibly quieter than an
   active build console, so "waiting for a builder" never reads as running. */
.deploy-queued {
  font-style: italic;
  opacity: 0.85;
}

/* status pill — distinct from the dot-badge; colour-coded per lifecycle state */
.dep-pill {
  flex: 0 0 auto;
  display: inline-flex; align-items: center;
  height: 22px; padding: 0 10px;
  font-size: 12px; font-weight: 600;
  border-radius: var(--pill);
  border: 1px solid var(--border);
  background: var(--muted-surface);
  color: var(--muted-text);
}
.dep-live    { color: var(--ok); background: hsl(var(--ok-hsl) / 0.12); border-color: hsl(var(--ok-hsl) / 0.35); }
.dep-failed  { color: var(--danger); background: hsl(var(--danger-hsl) / 0.10); border-color: hsl(var(--danger-hsl) / 0.30); }
.dep-building, .dep-pushing { color: var(--warn-strong); background: hsl(var(--warn-hsl) / 0.12); border-color: hsl(var(--warn-hsl) / 0.35); }
.dep-queued  { color: var(--muted-text); }

/* gh-5: per-deployment live build console — the deploy-side twin of .new-console */
.deploy-console { margin-top: 10px; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
.deploy-console-toggle {
  display: flex; align-items: center; gap: 8px;
  width: 100%; padding: 7px 10px;
  background: var(--muted-surface);
  border: none; border-bottom: 1px solid var(--border);
  color: var(--muted-text);
  font: 600 11px var(--font); letter-spacing: 0.02em; text-transform: uppercase;
  cursor: pointer;
}
.deploy-console.is-collapsed .deploy-console-toggle { border-bottom: none; }
.deploy-console-caret {
  width: 0; height: 0;
  border-left: 5px solid transparent; border-right: 5px solid transparent;
  border-top: 6px solid currentColor;
  transition: transform var(--t);
}
.deploy-console.is-collapsed .deploy-console-caret { transform: rotate(-90deg); }
.deploy-console-count { margin-left: auto; font-weight: 500; text-transform: none; letter-spacing: 0; }
.deploy-console-body {
  max-height: 220px; overflow-y: auto;
  padding: 9px 11px;
  background: var(--console-bg); color: var(--console-fg);
  font: 12px/1.55 var(--mono);
  scroll-behavior: smooth;
}
.deploy-console-line { display: flex; gap: 10px; white-space: pre-wrap; word-break: break-word; }
.deploy-console-line.dim { color: var(--console-dim); } /* dim on the dark console = its own theme-invariant token (5.21:1) */
.deploy-console-ts { color: var(--console-dim); flex: 0 0 auto; font-variant-numeric: tabular-nums; }
.deploy-console-text { flex: 1 1 auto; }

/* ---------------------------------------------------------- API tokens */
/* GR33 ACTION page: #token-list is ONE .set-section card; rows are hairline-
   separated line items (no per-row surface — the card owns it, no card-in-card). */
.token-list { display: flex; flex-direction: column; }
.token-row {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 14px 0;
  border-top: 1px solid var(--border);
}
.token-row:first-child { border-top: none; padding-top: 0; }
.token-row:last-child { padding-bottom: 0; }
.token-row.is-revoked { opacity: 0.62; }
.token-meta { font-size: 12px; margin-top: 5px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.token-dot { color: var(--dim); }
.token-chip {
  font-family: var(--mono);
  font-size: 11px;
  padding: 1px 7px;
  border-radius: 999px;
  background: rgba(127, 127, 127, 0.12);
  border: 1px solid var(--border);
}
/* The abilities picker rides GR34's shared .set-check grammar now (see the
   .set-* anatomy block); the retired .token-ability* rules are gone. */
.token-reveal { display: flex; gap: 8px; align-items: center; margin: 4px 0 2px; }
.token-reveal .input-affix { flex: 1 1 auto; }
.token-reveal-input { font-family: var(--mono); font-size: 12px; }

/* ---- .notif-card / .notif-h: the GitHub-connection card in the Providers view
   (the ONLY remaining consumer; G-04 retired the Notifications view's own
   .notif-* body grammar onto the shared GR33 .set-* anatomy). ---- */
.notif-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: 18px;
  margin-bottom: 14px;
}
.notif-h {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted-text);
  margin: 18px 0 10px;
}
.notif-card > .notif-h:first-child { margin-top: 0; }

/* -------------------------------------------- settings anatomy (phase 4)
   GR33/GR34 shared vocabulary that every round-2 Settings page slice
   (G-01 billing … G-06 members) emits. Defined-but-UNEMITTED here — gate-free
   by construction: __css_check only contrast-evaluates classes the SPA actually
   renders, so landing the primitives ahead of their consumers cannot red E5/E6.
   Tokens-only (a raw color literal hard-fails E6). The fg/bg pairings below are
   --text / --muted-text on --surface, both already CONTRAST_PAIRS entries, so
   the skeleton needs no new contrast pair (GR33 contrast law). */
.set-section {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: var(--space-5);
  margin-bottom: var(--space-4);
}
.set-h {
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--text);
  margin: 0 0 var(--space-2);
}
.set-purpose {
  font-size: var(--text-sm);
  color: var(--muted-text);
  margin: 0 0 var(--space-4);
}
.set-save-row {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border);
}

/* GR34 check grammar — generalized from the .token-ability idiom: a bordered
   card row + name + consequence sub-line. The G-05 API-token abilities picker
   (read/write/deploy/root) and any per-item opt-in reuse this instead of
   re-inventing. */
.set-check-list { display: flex; flex-direction: column; gap: var(--space-2); margin-top: var(--space-2); }
.set-check {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  cursor: pointer;
}
.set-check input { margin-top: 3px; accent-color: var(--primary); }
/* Scope variant: a stated read-only scope (plain member), not a pickable row —
   no checkbox, so no pointer affordance (G-05 anti-ghost). */
.set-check--scope { cursor: default; }
.set-check-main { display: flex; flex-direction: column; }
.set-check-name { font-size: var(--text-sm); font-weight: 600; color: var(--text); }
.set-check-sub { font-size: var(--text-xs); color: var(--muted-text); }

/* GR34 toggle grammar — the compact per-event/-transport row, generalized from
   .notif-toggle. The G-04 Notifications routing matrix (event × channel) and
   per-transport toggles reuse this. */
.set-toggle { font-size: var(--text-sm); display: flex; align-items: center; gap: var(--space-2); cursor: pointer; }
.set-toggle input { width: 15px; height: 15px; accent-color: var(--primary); }

/* GR33 absence line — a muted sentence for a section's empty / loading /
   all-connected state (roster empty, matrix loading, no connectable provider).
   --muted-text on --surface is already a CONTRAST_PAIRS entry. */
.set-empty { font-size: var(--text-sm); color: var(--muted-text); margin: 0; }

/* gr-p4 G-02 — provider roster rows. IDENTITY mark + name/label, then the kind
   & connected-at meta in mono; (admin-only) a typed-confirm Disconnect… trails.
   The roster shows kind + label + WHEN only — never a live-validity badge. */
.prov-roster { list-style: none; margin: var(--space-2) 0 0; padding: 0; display: flex; flex-direction: column; }
.prov-row {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-3) 0; border-top: 1px solid var(--border);
}
.prov-row:first-child { border-top: none; }
.prov-row-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.prov-row-name { font-size: var(--text-sm); font-weight: 600; color: var(--text); }
.prov-row-label { font-weight: 400; color: var(--muted-text); }
.prov-row-meta { display: flex; flex-wrap: wrap; gap: var(--space-3); font-family: var(--mono); font-size: var(--text-xs); }
.prov-row-kind { color: var(--dim); }
.prov-row-when { color: var(--muted-text); }

/* gr-p4 G-02 — the inline connect card: a provider picker (.seg) → the armed
   kind's credential subform → the .set-save-row. An already-connected kind is
   shown but not selectable (its seg-btn ships disabled + a "connected" note). */
.prov-connect-picker { margin: var(--space-2) 0 var(--space-4); }
.prov-connect-form { display: flex; flex-direction: column; gap: var(--space-3); }
.prov-seg-note { font-size: var(--text-xs); color: var(--dim); margin-left: 4px; }

/* gr-p4 G-03 — the honest capability matrix: 9 verb rows × prod-provider
   columns. Tokens only. The affirmative mark reads --ok (an identity-invariant
   status green, already contrast-paired on --surface); the dash + server-owned
   gap reason read the muted voices. No new fg/bg pairing is introduced. */
.cap-scroll { overflow-x: auto; margin-top: var(--space-3); }
.cap-matrix { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.cap-matrix th, .cap-matrix td {
  text-align: left; padding: var(--space-2) var(--space-3);
  border-top: 1px solid var(--border); vertical-align: top;
}
.cap-matrix thead th {
  border-top: none; border-bottom: 1px solid var(--border);
  background: var(--muted-surface); color: var(--text); font-weight: 600;
}
.cap-corner { width: 40%; }
.cap-col { text-align: center; }
.cap-verb { white-space: nowrap; }
.cap-verb-key { display: block; font-family: var(--mono); font-size: var(--text-xs); color: var(--muted-text); }
.cap-verb-label { display: block; font-size: var(--text-sm); color: var(--text); font-weight: 500; }
.cap-cell { text-align: center; }
.cap-cell--yes { text-align: center; }
.cap-cell--no { text-align: center; }
.cap-mark { color: var(--ok); font-weight: 700; font-size: var(--text-base); }
.cap-dash { color: var(--muted-text); font-weight: 600; }
.cap-gap { display: block; margin-top: 2px; font-size: var(--text-xs); color: var(--dim); line-height: 1.4; }

/* Screen-reader-only text (matrix cell yes/no + reason spoken to AT). */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* GR33 save-row ack — the inline "Saving…/Saved." status that replaces the old
   loose #notif-status span; sits at the save-row's left, muted voice. */
.set-ack { font-size: var(--text-sm); }

/* Plain-member read-only view (GR33 plain-member law): a definition list, no
   inputs, no save-row. Label/value both on --surface (existing contrast pairs). */
.set-readonly { display: flex; flex-direction: column; gap: var(--space-3); margin: 0; }
.set-readonly > div { display: flex; justify-content: space-between; gap: var(--space-4); }
.set-readonly dt { font-size: var(--text-sm); color: var(--muted-text); }
.set-readonly dd { font-size: var(--text-sm); color: var(--text); margin: 0; text-align: right; }

/* G-04 chat-channel roster: an enable `.set-check` row + its write-only credential
   fields. The configured tag reuses the frozen ok tint (an existing CONTRAST_PAIRS
   pairing); the empty tag is a plain muted word (no bg). */
.set-channel { margin-bottom: var(--space-3); }
.set-channel-creds {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-3);
  margin: var(--space-2) 0 0 var(--space-6);
}
.set-channel-creds .field { margin-bottom: 0; flex: 1 1 200px; }
.set-cred-tag {
  font-size: var(--text-xs);
  font-weight: 600;
  padding: 1px 7px;
  border-radius: var(--pill);
  background: var(--ok-soft);
  color: var(--ok-strong);
}
.set-cred-tag--empty { background: transparent; color: var(--muted-text); }

/* G-04 event×channel routing matrix (GR36): a live toggle grid — 1 label column +
   6 channel columns (email + 5 chat). Scrolls horizontally on narrow viewports so
   the page body never does. Cells are `.set-toggle`-weight checkboxes; a dashed
   cell marks a chat_default_on failure event still on its honest default. */
.set-matrix { overflow-x: auto; margin-top: var(--space-2); }
.set-matrix-grid {
  display: grid;
  grid-template-columns: minmax(150px, 1.6fr) repeat(6, minmax(52px, 1fr));
  align-items: center;
  min-width: 520px;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.set-matrix-corner { border-bottom: 1px solid var(--border); }
.set-matrix-col {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: var(--space-2);
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--text);
  text-align: center;
  border-bottom: 1px solid var(--border);
  background: var(--muted-surface);
}
.set-matrix-off { font-size: var(--text-xs); font-weight: 500; color: var(--muted-text); }
.set-matrix-event {
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-sm);
  color: var(--text);
  border-bottom: 1px solid var(--border);
}
.set-matrix-cell {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-2);
  border-bottom: 1px solid var(--border);
  cursor: pointer;
}
.set-matrix-cell input { width: 15px; height: 15px; accent-color: var(--primary); cursor: pointer; }
.set-matrix-cell input:disabled { cursor: not-allowed; opacity: 0.4; }
.set-matrix-cell--default input { outline: 1px dashed var(--primary); outline-offset: 2px; }
.set-matrix-testcell {
  grid-column: span 6;
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-xs);
  border-bottom: 1px solid var(--border);
}
/* The final row (the always-send "test" label + its spanning cell) sits on the
   card's own bottom edge, so it drops its hairline to avoid a double border. */
.set-matrix-grid > *:nth-last-child(-n + 2) { border-bottom: none; }
.set-matrix-legend { font-size: var(--text-xs); margin-top: var(--space-2); }
.set-matrix-ack { font-size: var(--text-sm); margin-top: var(--space-2); min-height: 1em; }

/* GR33 roster grammar — the settings-list ROW inside a .set-section (G-06
   Members + the greenfield env-vars page). One idiom for both: an optional
   leading avatar/glyph, a name + mono meta stack, and a trailing chip/action
   cluster. Rows are hairline-separated inside their card (never a second card
   family). Every fg/bg here is an existing CONTRAST_PAIRS pairing (--text /
   --muted-text / --dim on --surface, --primary-fg on --primary). */
.set-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) 0;
  border-top: 1px solid var(--border);
}
.set-row:first-child { border-top: 0; padding-top: 0; }
.set-list { margin-top: var(--space-2); }
.set-ava {
  width: 30px; height: 30px;
  border-radius: var(--pill);
  background: var(--primary);
  color: var(--primary-fg);
  display: grid; place-items: center;
  font-size: var(--text-sm); font-weight: 600;
  flex: 0 0 auto;
}
.set-row-main { min-width: 0; flex: 1 1 auto; display: flex; flex-direction: column; gap: 2px; }
.set-row-name { font-size: var(--text-sm); font-weight: 600; color: var(--text); overflow: hidden; text-overflow: ellipsis; }
.set-row-key { font-size: var(--text-sm); font-weight: 600; color: var(--text); font-family: var(--mono); overflow-wrap: anywhere; }
.set-row-meta { font-size: var(--text-xs); color: var(--muted-text); font-family: var(--mono); }
.set-row-note { font-size: var(--text-xs); color: var(--dim); margin-top: 2px; }
.set-row-tags { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2); margin-top: 2px; }
.set-row-side { display: flex; align-items: center; gap: var(--space-2); flex: 0 0 auto; }
/* A bordered metadata chip (scope · secret · write-once). Text-only tint keeps
   it on the existing --muted-text/--surface pairing — no new contrast pair. */
.set-chip {
  font-size: var(--text-xs);
  font-weight: 500;
  color: var(--muted-text);
  border: 1px solid var(--border);
  border-radius: var(--pill);
  padding: 1px var(--space-2);
  white-space: nowrap;
}

/* ---------------------------------------------------------- responsive */
/* GR65 TABLET (768px): three layouts break BEFORE the 720px phone threshold,
   and 768 is exactly the second width __preview__/shoot.sh shoots — so every
   green screenshot pass was taken 48px above the break. Shot at 768 in both
   themes: `.fleet-row` lands the "v0.9.2 available" badge on top of the
   hostname (mixed-fleet), `.detail-grid` stays two-column and clips its right
   column off-screen in BOTH consumers (panel-overview's Host row, rollback's
   DETAILS card), and `.attention-row` hides instance names behind the View
   instance / Open Studio buttons (fleet-usage). Those three moved up, and
   GR116 later moved the topbar tighten + live-chip fold up too (the past-due
   billing chip needs the space at tablet widths). .crumbs / .toast-stack /
   .detail-head / .site-row--global and the webhooks + sites panels were shot
   at 768 and render cleanly, so they keep the 720px phone threshold rather
   than going phone-shaped on a tablet. */
@media (max-width: 768px) {
  .fleet-row { flex-direction: column; align-items: stretch; }
  .fleet-badges { justify-content: flex-start; }
  /* minmax(0, …), NOT a bare 1fr: a 1fr track's automatic minimum is min-content,
     so the stacked panels (whose min-content is ~572px of unbreakable host names
     and code) blew the track back past the viewport and the "fix" clipped exactly
     as much as the two-column layout did. Measured at 768: bare 1fr → track
     572.5px, panel right edge 828.5 (viewport 768); minmax(0,1fr) → track 473px,
     right edge 729. */
  .detail-grid { grid-template-columns: minmax(0, 1fr); }
  /* .attention-row's tablet stack lives NEXT TO ITS BASE in the attention-card
     section now (GR109): authored here, it sat ~700 lines BEFORE the base rule,
     whose later align-items:center silently killed the flex-start half at equal
     (0,0,1,0) specificity — the row stacked but stayed centred. */
  /* GR116 cosmetic half — ships WITH the unconditional `.topbar-right > *
     { min-width: 0 }` pair, never instead of it. The pair alone clips the
     past-due chip to 154.61px of its 169.78px label at 768; tightening the
     topbar here (was 720-only) buys the full money message back at tablet
     widths. Three declarations: topbar padding/gap, topbar-right gap, and the
     live-chip label fold (in the liveness-chip section, moved 720 -> 768). */
  .topbar { padding: 0 14px; gap: 10px; }
  .topbar-right { gap: 8px; }
}

@media (max-width: 720px) {
  .topbar-link, .crumb-sep { display: none; }
  .content { padding: 24px 16px; }
  .site-row--global { flex-direction: column; align-items: stretch; }
  .toast-stack { left: 12px; right: 12px; bottom: 12px; max-width: none; }
  .crumbs { display: none; }
  /* Stack the detail header (title above badges + Retry/Remove) so the actions
     don't wrap awkwardly or clip on phones. */
  .detail-head { flex-direction: column; align-items: stretch; }
  .detail-head .fleet-badges { justify-content: flex-start; }
  .detail-actions { margin-left: 0; }
}

/* ---------- Activity (audit trail) ----------
   Rows reuse .fleet-row; these tune the trail-specific bits: the timestamp
   line reads quiet, and the "Load more" control centres under the list. */
.activity-row .fleet-name { font-weight: 500; }
.activity-more {
  display: flex;
  justify-content: center;
  margin-top: 16px;
}
/* I-01: the server-true target_type filter chips above the coalesced feed. */
.activity-filters { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 14px; }
.actfilter-chip {
  padding: 5px 12px;
  font-size: 12px;
  font-weight: 500;
  color: var(--muted-text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--pill);
  cursor: pointer;
  transition: color var(--t), border-color var(--t), background var(--t);
}
.actfilter-chip:hover { color: var(--text); border-color: var(--dim); }
.actfilter-chip:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--ring-soft); }
.actfilter-chip.is-active {
  color: var(--text);
  background: hsl(var(--primary-hsl) / 0.1);
  border-color: hsl(var(--primary-hsl) / 0.35);
}

/* ===================== /new DEPLOY FLOW (dwb-6) ===================== */
.new-screen {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}
.new-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 24px;
  border-bottom: 1px solid var(--border);
}
.new-center {
  flex: 1;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 48px 20px;
}
.new-body { width: 100%; max-width: 560px; }
/* GR18 theater (v4, screens/09): the progress + failed screens open into the
   two-column stage, so their host body widens; the form steps (auth/launch/
   pricing) keep the focused 560px column. */
.new-body:has(.new-progress), .new-body:has(.new-failed) { max-width: 920px; }
.new-card { padding: 32px; }
.new-eyebrow {
  display: inline-block;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted-text);
  margin-bottom: 10px;
}
.new-eyebrow.ok { color: var(--ok); } /* ladder voice (legacy --success alias retired in-region) */
.new-title { font-size: 26px; letter-spacing: -0.02em; margin: 0 0 8px; }
.new-desc { color: var(--muted-text); margin: 0 0 18px; line-height: 1.5; }
.new-gets {
  list-style: none;
  margin: 0 0 20px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.new-gets li {
  position: relative;
  padding-left: 24px;
  color: var(--text);
  line-height: 1.45;
}
.new-gets li::before {
  content: "\2713";
  position: absolute;
  left: 0;
  top: 0;
  color: var(--ok);
  font-weight: 700;
}
.new-fineprint { font-size: 13px; margin: 12px 0 0; text-align: center; }
.new-fineprint a { color: var(--muted-text); }

.btn-lg { padding: 12px 18px; font-size: 15px; }

/* auth step */
.new-auth { margin-top: 20px; border-top: 1px solid var(--border); padding-top: 20px; }
.new-auth-lead { color: var(--muted-text); margin: 0 0 14px; }
.new-auth .field { margin-bottom: 12px; }

/* launch step */
.new-launch .field { margin-bottom: 16px; }

/* template picker */
.new-picks { display: flex; flex-direction: column; gap: 10px; }
.new-pick {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 14px 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  transition: border-color var(--t), background var(--t);
}
.new-pick:hover { border-color: var(--primary); background: var(--muted-surface); text-decoration: none; }
.new-pick-title { font-weight: 600; color: var(--text); }
.new-pick-desc { font-size: 13px; }

/* pricing step (402) */
.new-pricing { margin-top: 18px; border-top: 1px solid var(--border); padding-top: 18px; }
.new-pricing h2 { font-size: 18px; margin: 0 0 4px; }
.new-tiers { display: flex; flex-direction: column; gap: 12px; margin-top: 14px; }
.new-tier { border: 1px solid var(--border); border-radius: var(--radius); padding: 16px; }
.new-tier-head { display: flex; align-items: baseline; justify-content: space-between; }
.new-tier-name { font-weight: 600; }
.new-tier-price { font-size: 20px; font-weight: 700; }
.new-tier-price .dim { font-size: 13px; font-weight: 400; }
.new-tier .btn { margin-top: 12px; }

/* ── GR18 theater (v4, screens/09): head + price line + two-column stage ─────
   The progress/failed screens share the theater shell: a centred head (title +
   pace + the price-before-charge line), the master bar, then the stage — step
   rail beside the live console. Phone contract (styleguide 10): the rail
   stacks ABOVE the console (DOM order already rail-first), the console caps at
   40vh, and no theater text falls below 13px. */
.new-theater-head { text-align: center; }
.new-theater-head .new-title { font-size: 24px; margin: 0 0 6px; }
.new-theater-head .new-desc { margin: 0; font-size: 13.5px; }
.theater-price {
  margin: 8px 0 0;
  font-size: 13px;
  color: var(--muted-text);
  line-height: 1.5;
}
.theater-price-amount { font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums; }
.new-theater-grid {
  display: grid;
  grid-template-columns: 320px minmax(0, 1fr);
  gap: 22px;
  align-items: start;
  margin-top: 18px;
}
.new-theater-rail {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 16px 18px;
}
.new-theater-rail .new-steps { margin-top: 0; }
.new-theater-side { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.new-theater-side .new-console { margin-top: 0; }
.new-theater-note { margin: 0; font-size: 13px; }
.new-elapsed { font-variant-numeric: tabular-nums; }

/* ── Overall progress (the headline master bar; shared /new + instance timeline).
   A calm evergreen fill over a soft track, with "Step N of M" + "about 40s left".
   Width + text patch in place each 1s tick; the transition smooths the jumps. */
.prov-overall { margin: 18px 0 4px; }
.new-progress .prov-overall { margin: 18px 0 6px; }
.prov-overall-track {
  height: 6px;
  border-radius: var(--pill);
  background: var(--ring-soft);
  overflow: hidden;
}
.prov-overall-fill {
  height: 100%;
  width: 0;
  border-radius: var(--pill);
  background: var(--primary);
  transition: width 0.9s cubic-bezier(0.22, 1, 0.36, 1);
}
.prov-overall.is-done .prov-overall-fill { background: var(--ok); }
.prov-overall.is-failed .prov-overall-fill { background: var(--danger); }
.prov-overall-meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-top: 7px;
  font-size: 12px;
}
.prov-overall-meta > span:first-child { color: var(--text); font-weight: 600; }
.prov-overall.is-failed .prov-overall-meta > span:first-child { color: var(--danger); }
.prov-overall-eta { color: var(--dim); font-variant-numeric: tabular-nums; }

.new-steps { list-style: none; margin: 22px 0 0; padding: 0; display: flex; flex-direction: column; gap: 14px; }
.new-step { display: flex; align-items: center; gap: 12px; }
.new-step-dot {
  width: 22px; height: 22px;
  flex: 0 0 22px;
  border-radius: var(--pill);
  border: 2px solid var(--border);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  color: var(--primary-fg);
  /* Styleguide motion contract: state advance eases 250ms… */
  transition: background-color 250ms ease, border-color 250ms ease, color 250ms ease;
}
.new-step-label { transition: color 250ms ease; }
/* …and failure SNAPS (0ms) — a break is never eased in (ladder snap law). */
.new-step.failed .new-step-dot,
.new-step.failed .new-step-label { transition: none; }
.new-step.done .new-step-dot { background: var(--ok); border-color: var(--ok); } /* done glyph = mint (--ok tracks the accent, GR6) */
/* The active dot IS the per-phase loading bar: a conic ring filled by --p
   (stepRingProgress over the phase's expected duration, ticked in place every
   second). Registered as a <percentage> so the fill animates smoothly between
   1s ticks; hollowed with a radial mask so it stays a ring, not a pie. */
@property --p { syntax: "<percentage>"; inherits: false; initial-value: 0%; }
.new-step.active .new-step-dot {
  border: 0;
  background: conic-gradient(var(--primary) var(--p, 0%), var(--ring-soft) 0);
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 2.5px));
  mask: radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 2.5px));
  transition: --p 0.9s linear;
}
/* dwb-14: a worker-reported failed step renders honest (red), not a stuck spinner. */
.new-step.failed .new-step-dot { background: var(--danger); border-color: var(--danger); }
/* GR18(4): steps behind a failure never ran and never will — dashed hollow,
   dimmed, no plan hint (markup drops it), clearly distinct from live pending. */
.new-step.skipped .new-step-dot { border-style: dashed; background: transparent; }
.new-step.skipped .new-step-label { color: var(--dim); }
/* Between-steps honesty: the `next` row (previous step done, this one about to
   report — e.g. the SSH boot wait) pulses instead of sitting frozen. */
.new-step.next .new-step-dot { border-color: var(--primary); animation: new-next-pulse 1.4s ease-in-out infinite; }
.new-step.next .new-step-label { color: var(--text); }
.new-step.next .new-step-spin { opacity: 0.55; }
@keyframes new-next-pulse {
  0%, 100% { box-shadow: 0 0 0 0 var(--ring-soft); }
  50% { box-shadow: 0 0 0 4px var(--ring-soft); }
}
.new-step-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.new-step-label { color: var(--muted-text); }
.new-step.active .new-step-label { color: var(--text); font-weight: 600; }
.new-step.done .new-step-label { color: var(--text); }
.new-step.failed .new-step-label { color: var(--danger); font-weight: 600; }
/* dwb-19: the live sub-caption under the active step — muted, smaller, with a
   subtle fade/translate that re-plays each time the caption element re-mounts
   (its text changed). A done/failed step keeps its final caption, dimmer. */
.new-step-detail {
  font-size: 12px;
  color: var(--muted-text);
  line-height: 1.35;
  word-break: break-word;
  animation: new-detail-in 0.28s ease;
}
.new-step.done .new-step-detail,
.new-step.failed .new-step-detail { color: var(--dim); }
/* verify-gate probe checklist under its step (shared rows — both mounts) */
.new-step-probes { list-style: none; margin: 4px 0 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.new-step-probe {
  font: 12px/1.4 var(--mono);
  color: var(--muted-text);
  padding-left: 14px;
  position: relative;
  word-break: break-word;
}
.new-step-probe::before {
  content: "";
  position: absolute; left: 2px; top: 6px;
  width: 5px; height: 5px;
  border-radius: var(--pill);
  background: var(--info);
}
@keyframes new-detail-in {
  from { opacity: 0; transform: translateY(-2px); }
  to { opacity: 1; transform: none; }
}
.new-step-spin {
  width: 14px; height: 14px;
  flex: 0 0 14px;
  margin-left: auto;
  border: 2px solid var(--ring-soft);
  border-top-color: var(--primary);
  border-radius: var(--pill);
  animation: new-spin 0.7s linear infinite;
}
@keyframes new-spin { to { transform: rotate(360deg); } }
/* Pace column: pending "~30s" (the plan up front), active "12s · ~30s" (live),
   done the real elapsed. When a spinner follows, the time span carries the
   auto margin and the spinner sits flush after it. */
.new-step-time {
  margin-left: auto;
  font-size: 12px;
  color: var(--dim);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.new-step-time + .new-step-spin { margin-left: 10px; }

/* ready step */
.new-actions { display: flex; flex-direction: column; gap: 10px; margin: 20px 0; }
.btn-vercel { background: #000; color: #fff; border-color: #000; }
.btn-vercel:hover { background: #111; text-decoration: none; }
[data-theme="dark"] .btn-vercel { background: #fff; color: #000; border-color: #fff; }
[data-theme="dark"] .btn-vercel:hover { background: #eee; }
.new-env { border: 1px solid var(--border); border-radius: var(--radius); padding: 14px; margin: 6px 0 18px; }
.new-env-head { display: flex; align-items: center; justify-content: space-between; font-size: 13px; font-weight: 600; margin-bottom: 8px; }
.new-env-body {
  font-family: var(--mono);
  font-size: 12px;
  background: var(--muted-surface);
  border-radius: var(--radius-sm);
  padding: 12px;
  overflow-x: auto;
  white-space: pre;
  margin: 0;
}
/* Guided Vercel fallback (no platform token): values FIRST with per-field copy,
   then the Deploy button, so the empty Vercel form is an expected next step. */
.new-vercel-guide { border: 1px solid var(--border); border-radius: var(--radius); padding: 14px; margin: 6px 0 18px; }
.new-vercel-guide .new-env-head { margin-bottom: 6px; }
.new-vercel-guide .btn-vercel { margin-top: 12px; }
.new-env-rows { list-style: none; margin: 10px 0 0; padding: 0; display: flex; flex-direction: column; gap: 6px; counter-reset: envrow; }
.new-env-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 10px;
  background: var(--muted-surface);
  border-radius: var(--radius-sm);
}
.new-env-key { font-size: 12px; word-break: break-all; }
.new-env-row .btn { flex: 0 0 auto; }

.new-golive { border-top: 1px solid var(--border); padding-top: 16px; }
.new-golive-row { display: flex; gap: 8px; margin-top: 8px; }
.new-golive-row .form-input { flex: 1; }
.new-gh { border: 1px solid var(--border); border-radius: var(--radius); padding: 14px; text-align: left; }
.new-gh .label { display: block; margin-bottom: 4px; }
.new-gh-steps { margin: 8px 0 0; padding-left: 18px; font-size: 13px; }
.new-ready .mono { font-family: var(--mono); font-size: 13px; }

/* failed step — the theater's failure stage (GR18(4)): head carries the honest
   server-owned failCopy; the rail shows the snap (failed red, rest skipped);
   ONE recovery action below the stage. */
.new-fail-copy {
  margin: 0 0 8px;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  background: var(--danger-soft);
  border: 1px solid hsl(var(--danger-hsl) / 0.3);
  color: var(--text);
  font-size: 13px;
  line-height: 1.5;
  text-align: left;
}
.new-failed .new-actions { margin: 18px 0 0; }
.new-failed .new-failed-caption {
  margin: 0;
  font-size: 13px;
  color: var(--muted-text);
  min-height: 18px;
}

/* dwb-16: live provisioning console — dark, monospace, auto-scrolling, visible by
   default during provisioning, collapsible. Stays on the failed screen too. */
.new-console {
  margin-top: 22px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}
.new-console-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 9px 12px;
  background: var(--muted-surface);
  border: none;
  border-bottom: 1px solid var(--border);
  color: var(--muted-text);
  font: 600 12px var(--font);
  letter-spacing: 0.02em;
  text-transform: uppercase;
  cursor: pointer;
}
.new-console.is-collapsed .new-console-toggle { border-bottom: none; }
.new-console-caret {
  width: 0; height: 0;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-top: 6px solid currentColor;
  transition: transform var(--t);
}
.new-console.is-collapsed .new-console-caret { transform: rotate(-90deg); }
.new-console-body {
  max-height: 260px;
  overflow-y: auto;
  padding: 10px 12px;
  background: var(--console-bg);
  color: var(--console-fg);
  font: 12px/1.55 var(--mono);
  scroll-behavior: smooth;
}
.new-console-line { display: flex; gap: 10px; white-space: pre-wrap; word-break: break-word; }
.new-console-line.dim { color: var(--console-dim); } /* dim on the dark console = its own theme-invariant token (5.21:1) */
.new-console-ts { color: var(--console-dim); flex: 0 0 auto; font-variant-numeric: tabular-nums; }
.new-console-text { flex: 1 1 auto; }

/* dwb-16: connection-lost banner in the progress screen. */
.new-conn-lost { margin: 0 0 14px; }

/* The C3 timeline's expandable console — the --console-* twin of .new-console.
   It renders inside .bp-timeline (instance detail), but its BASES are authored
   here, beside its twin and ABOVE the shared theater/phone block below (GR115):
   authored after that block in the C3 section, the base rules' identical
   (0,0,1,0) specificity plus later source position silently discarded the
   block's 40vh cap and 13px legibility floor for the whole .bp-console family
   — one declaration list, two console families, opposite computed outcomes
   (320px/13px vs 260px/12px at 700x800), varying only on source order. Keep
   these bases above that media block. */
.bp-console {
  margin-top: 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}
.bp-console-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 9px 12px;
  background: var(--muted-surface);
  border: none;
  border-bottom: 1px solid var(--border);
  color: var(--muted-text);
  font: 600 12px var(--font);
  letter-spacing: 0.02em;
  text-transform: uppercase;
  cursor: pointer;
}
.bp-console.is-collapsed .bp-console-toggle { border-bottom: none; }
.bp-console-caret {
  width: 0; height: 0;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-top: 6px solid currentColor;
  transition: transform var(--t);
}
.bp-console.is-collapsed .bp-console-caret { transform: rotate(-90deg); }
.bp-console-body {
  max-height: 260px;
  overflow-y: auto;
  padding: 10px 12px;
  background: var(--console-bg);
  color: var(--console-fg);
  font: 12px/1.55 var(--mono);
  scroll-behavior: smooth;
}
.bp-console-line { display: flex; gap: 10px; white-space: pre-wrap; word-break: break-word; }
.bp-console-empty { color: var(--console-dim); }
.bp-console-ts { color: var(--console-dim); flex: 0 0 auto; font-variant-numeric: tabular-nums; }
.bp-console-text { flex: 1 1 auto; }

/* Theater/phone (styleguide contract 10): the stage stacks — rail above the
   console (DOM order is already rail-first) — the console caps at 40vh, the
   done-banner buttons go full-width, and no theater text falls below 13px.
   BOTH console families' bases sit ABOVE this block (GR115) — order is load-
   bearing here, not style. */
@media (max-width: 720px) {
  .new-theater-grid { grid-template-columns: 1fr; gap: 16px; }
  .new-console-body, .bp-console-body { max-height: 40vh; }
  .new-ready .new-actions .btn { width: 100%; }
  .new-step-detail, .new-step-probe, .new-step-time,
  .new-console-toggle, .new-console-body,
  .bp-console-toggle, .bp-console-body { font-size: 13px; }
}

/* Reduced motion (styleguide contract 10): nothing conveys information through
   motion alone — the glyphs/colours already carry every state, so the next-row
   pulse goes static (full opacity), state changes cut instead of easing, the
   caption fade is dropped, and the ring stops tweening between ticks. The
   .new-step-spin spinner keeps its literal duration by house law: a loading
   indicator is state, not decoration. */
@media (prefers-reduced-motion: reduce) {
  .new-step.next .new-step-dot { animation: none; }
  .new-step-dot, .new-step-label { transition: none; }
  .new-step.active .new-step-dot { transition: none; }
  .new-step-detail { animation: none; }
  .prov-overall-fill { transition: none; }
}

/* ===================== /activate DEVICE LOGIN (bp-login-ux W1) =====================
   The device-auth screen borrows the /new shell wholesale (.new-screen +
   .new-topbar + .new-center + .new-body), so .activate-screen is the identity
   hook that lets this flow diverge WITHOUT forking that shell. The one real
   divergence: /new is a multi-step wizard whose column grows downward, while
   /activate is a single short decision — top-aligned, a 3-row confirm card is
   left stranded against 100vh of empty page. So this screen optically centres
   its card instead (the bottom bias keeps it off dead-centre). */
.activate-screen .new-center { align-items: center; padding-bottom: 72px; }

/* The approve-screen fact rail. The .rail-row / .k / .v children are already
   styled globally, so this is the WRAPPER only — .detail-rail's surface, border
   and radius, at the tighter vertical padding a 3–4 row confirm panel wants (the
   rows carry their own 8px, and .rail-row:first-of-type drops the top border). */
.activate-rail {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 4px 14px;
  box-shadow: var(--shadow-sm);
}

/* ========================================================= C3 PROVISION TIMELINE
   ONE component, every mount (unified-provision-view): the instance-detail
   timeline renders the SAME .new-step rows as the /new progress screen — ring,
   pace column, captions, probes, next pulse. Only the shell (head + total
   clock + failure block + console) keeps .bp-timeline classes; the per-row
   .bp-tl-step family is GONE. Role colours come straight from the token
   contract; the console shell uses the --console-* tokens. Both themes. */
.bp-timeline {
  margin: 0 0 20px;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 16px 18px;
  background: var(--surface);
}
/* GR18(3): the price-before-charge line inside the fold sits under the head,
   left-aligned (the fold has no centred theater head). */
.bp-timeline .theater-price { margin: 6px 0 0; }
.bp-tl-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.bp-tl-title { font-size: 15px; margin: 0; }
.bp-tl-total { font-size: 13px; color: var(--muted-text); font-variant-numeric: tabular-nums; }
/* The shared rows sit a touch tighter inside the timeline card than on /new. */
.bp-timeline .new-steps { margin-top: 16px; }
.bp-tl-fail {
  margin-top: 16px;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  background: var(--danger-soft);
  border: 1px solid hsl(var(--danger-hsl) / 0.3);
  color: var(--text);
  font-size: 13px;
  line-height: 1.45;
}
.bp-tl-fail b { color: var(--danger); }
.bp-tl-retry { margin-top: 12px; }

/* The timeline's expandable console (.bp-console-*) is authored NEXT TO its
   .new-console twin, ABOVE the shared theater/phone media block — see GR115:
   authored here, after that block, its base rules silently discarded the
   40vh cap and the 13px legibility floor at equal specificity. */

/* ============================================================ STATUS PILL
   The single semantic status affordance (charter decision 6): one dot + label
   + optional detail, coloured by the --ok/--info/--warn/--danger token roles.
   Replaces the old multi-badge soup in the fleet row and instance-detail
   header. Token-driven so it adapts to both themes; the [data-theme="dark"]
   block below tunes the soft-tint strength on the darker surface. */
.status-pill {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 24px;
  padding: 0 11px;
  border: 1px solid var(--border);
  border-radius: var(--pill);
  background: var(--muted-surface);
  font-size: 12px;
  font-weight: 500;
  color: var(--text);
  white-space: nowrap;
  max-width: 100%;
  min-width: 0;
}
.status-pill-dot {
  width: 7px; height: 7px;
  border-radius: var(--pill);
  background: var(--dim);
  flex: 0 0 auto;
}
.status-pill-label { font-weight: 600; flex: 0 0 auto; }
.status-pill-detail {
  color: var(--muted-text);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
.status-pill-detail::before { content: "·"; margin-right: 6px; color: var(--dim); }
.status-pill--ok { background: var(--ok-soft); border-color: hsl(var(--ok-hsl) / 0.28); }
.status-pill--ok .status-pill-dot { background: var(--ok); }
.status-pill--info { background: var(--info-soft); border-color: hsl(var(--info-hsl) / 0.28); }
.status-pill--info .status-pill-dot { background: var(--info); }
.status-pill--warn { background: var(--warn-soft); border-color: hsl(var(--warn-hsl) / 0.3); }
.status-pill--warn .status-pill-dot { background: var(--warn); }
.status-pill--danger { background: var(--danger-soft); border-color: hsl(var(--danger-hsl) / 0.28); }
.status-pill--danger .status-pill-dot { background: var(--danger); }
.status-pill--neutral { background: var(--muted-surface); border-color: var(--border); }
.status-pill--neutral .status-pill-dot { background: var(--dim); }
/* dark-theme refinement: lift the role tints for legibility on the dark surface */
[data-theme="dark"] .status-pill--ok { background: hsl(var(--ok-hsl) / 0.22); }
[data-theme="dark"] .status-pill--info { background: hsl(var(--info-hsl) / 0.22); }
[data-theme="dark"] .status-pill--warn { background: hsl(var(--warn-hsl) / 0.22); }
[data-theme="dark"] .status-pill--danger { background: hsl(var(--danger-hsl) / 0.22); }

/* ============================================================ SETTINGS MENU
   The Settings cluster is a native <details> disclosure (charter decision 6).
   It sits outside .tabnav-primary so its dropdown escapes the primary scroller;
   applyRoute() closes it after every navigation. */
.nav-menu { position: relative; flex: 0 0 auto; display: flex; align-items: center; }
.nav-menu-trigger { cursor: pointer; list-style: none; user-select: none; }
.nav-menu-trigger::-webkit-details-marker { display: none; }
.nav-caret {
  display: inline-block;
  margin-left: 6px;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid currentColor;
  opacity: 0.6;
  transition: transform var(--t);
}
.nav-menu[open] .nav-caret { transform: rotate(180deg); }
.nav-menu-list {
  position: absolute;
  top: 100%;
  right: 0;
  min-width: 184px;
  padding: 6px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  display: flex;
  flex-direction: column;
  gap: 2px;
  z-index: var(--z-menu);
}
.nav-menu-item {
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  font-size: 13px;
  color: var(--muted-text);
}
.nav-menu-item:hover { background: var(--muted-surface); color: var(--text); text-decoration: none; }

/* ============================================================ OVERVIEW (home)
   gr-p2 HOME TRIAGE (C-01/C-02, v4): a time-of-day greeting, page-header fleet
   chips + a REAL slots meter, a mint self-healing runway OR the GR17 past-due
   banner, the attention queue, a v4 instances grid, and the activity digest. */

/* Page header: greeting left, chips + slots + Launch right. */
.overview-head { align-items: flex-start; }
.overview-greet h1 { margin: 0; font-size: 26px; letter-spacing: -0.02em; }
.overview-head-right { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; justify-content: flex-end; }
.overview-chips { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ov-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px; border-radius: var(--pill);
  font-size: 12px; font-weight: 600; white-space: nowrap;
  background: var(--muted-surface); color: var(--muted-text);
}
.ov-chip-glyph { font-size: 10px; line-height: 1; }
.ov-chip--attention { color: var(--warn); background: hsl(var(--warn-hsl) / 0.12); }
.ov-chip--inflight { color: var(--info); background: hsl(var(--info-hsl) / 0.12); }
.ov-chip--healthy { color: var(--ok); background: hsl(var(--ok-hsl) / 0.12); }
.ov-chip-pulse { animation: ov-pulse 1.6s ease-in-out infinite; }
@keyframes ov-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
@media (prefers-reduced-motion: reduce) { .ov-chip-pulse { animation: none; } }
.ov-slots { display: inline-flex; align-items: center; gap: 8px; }
.ov-slots:empty { display: none; }
.ov-slots-label { font-size: 12px; font-weight: 600; color: var(--muted-text); font-variant-numeric: tabular-nums; }
.ov-slots-track { width: 48px; height: 4px; border-radius: 2px; background: var(--muted-surface); overflow: hidden; }
.ov-slots-fill { display: block; height: 100%; background: var(--ok); }

/* Attention queue — amber card, one row per box that needs an operator. */
.overview-attention { margin-top: 4px; }
.attention-card {
  border: 1px solid hsl(var(--warn-hsl) / 0.3);
  border-radius: var(--radius); background: var(--surface); overflow: hidden;
}
.attention-row {
  display: flex; align-items: center; gap: 14px;
  padding: 14px 18px; border-bottom: 1px solid var(--border);
}
.attention-row:last-child { border-bottom: none; }
.attention-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.attention-name { font-size: 14px; font-weight: 600; color: var(--text); }
.attention-name:hover { color: var(--primary); }
.attention-reason { font-size: 13px; color: var(--muted-text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.attention-acts { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }

/* GR65 tablet stack, revived by GR109. STACK, don't just wrap: `.attention-main`
   is flex:1 min-width:0, so a mere flex-wrap lets it collapse instead of
   pushing the buttons to a new line — measured at 768, the Staging row's
   .attention-name box shrank to 3px and its text painted straight through the
   View instance / Open Studio buttons. Stacking gives every name the full row.
   THIS BLOCK SITS AFTER THE .attention-row BASE ON PURPOSE (GR109): it used to
   live in the shared 768 block ~700 lines ABOVE the base, where the base's
   later `align-items: center` killed the flex-start half at equal (0,0,1,0)
   specificity — media queries add no specificity, source order decides — so
   the row stacked but stayed CENTRED (acts left edge 441.28, not 275). Source
   order is the fix; keep this below the base rules, and no !important. */
@media (max-width: 768px) {
  .attention-row { flex-direction: column; align-items: flex-start; }
  /* …but the name/reason column still takes the full row, or it re-collapses. */
  .attention-row .attention-main { align-self: stretch; }
}

/* v4 instances grid — auto-fill cards. */
.overview-instances { margin-top: 4px; }
.instances-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); gap: 16px;
}
.instance-card {
  display: flex; flex-direction: column; gap: 12px;
  padding: 18px 20px; border: 1px solid var(--border);
  border-left-width: 3px; border-left-color: var(--border);
  border-radius: var(--radius); background: var(--surface);
  transition: border-color var(--t), box-shadow var(--t);
}
.instance-card:hover { box-shadow: var(--shadow-sm); }
.instance-card--warn { border-left-color: var(--warn); }
.instance-card--danger { border-left-color: var(--danger); }
.instance-card--info { border-left-color: var(--info); }
.instance-card--ok { border-left-color: var(--ok); }
.instance-card-head { display: flex; align-items: center; gap: 10px; }
.instance-card-name { font-size: 16px; font-weight: 700; letter-spacing: -0.01em; color: var(--text); margin-right: auto; }
.instance-card-name:hover { color: var(--primary); }
.instance-card-url {
  font-family: var(--mono); font-size: 12px; color: var(--muted-text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.instance-card-spark { height: 30px; color: var(--dim); border-bottom: 1px solid var(--border); }
.instance-card-spark .spark { display: block; width: 100%; height: 30px; }
.spark--ok { color: var(--ok); } .spark--info { color: var(--info); }
.spark--warn { color: var(--warn); } .spark--danger { color: var(--danger); }
.spark--neutral { color: var(--dim); }
.instance-card-stats { display: flex; align-items: center; gap: 18px; padding-top: 4px; }
.instance-card-stat { display: flex; flex-direction: column; gap: 2px; }
.instance-card-stat-k { font-family: var(--mono); font-size: 10px; letter-spacing: 0.08em; color: var(--dim); }
.instance-card-stat-v { font-size: 13px; font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums; }
.instance-card-stat-v.is-warn { color: var(--warn); }
.instance-card-studio { margin-left: auto; color: var(--ok); font-weight: 600; }

/* C-02 self-healing runway — mint-tinted onboarding card. */
.runway-card {
  display: flex; flex-direction: column; gap: 14px;
  padding: 20px 22px; border: 1px solid hsl(var(--ok-hsl) / 0.28);
  border-radius: var(--radius); background: hsl(var(--ok-hsl) / 0.05); margin-bottom: 8px;
}
.runway-card-head { display: flex; align-items: baseline; gap: 12px; }
.runway-card-head h2 { font-size: 15px; margin: 0; }
.runway-progress { font-size: 12.5px; color: var(--muted-text); }
.runway-dismiss { margin-left: auto; font-size: 12.5px; }
.runway-steps { display: flex; flex-direction: column; gap: 12px; }
.runway-step { display: flex; align-items: center; gap: 12px; }
.runway-mark {
  flex: 0 0 auto; width: 22px; height: 22px; border-radius: var(--pill);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 700;
  border: 1px solid var(--border); color: var(--text); background: transparent;
}
.runway-step.is-done .runway-mark { border-color: hsl(var(--ok-hsl) / 0.28); background: var(--ok-soft); color: var(--ok); }
.runway-step-main { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
.runway-step-label { font-size: 13.5px; font-weight: 500; color: var(--text); }
.runway-step.is-done .runway-step-label { color: var(--muted-text); }
.runway-step-hint { font-size: 12.5px; color: var(--dim); }
.runway-step-action { flex: 0 0 auto; font-size: 12.5px; }

/* GR17 past-due surfaces — overview banner + suspended instance-card banner. */
.overview-dunning { margin-bottom: 8px; }
.suspended-card-banner {
  display: flex; flex-direction: column; gap: 4px;
  padding: 12px 14px; margin: -18px -20px 0; border-radius: 0;
  border: none; border-bottom: 1px solid hsl(var(--danger-hsl) / 0.3);
  background: hsl(var(--danger-hsl) / 0.08);
}
.suspended-card-title { font-size: 13px; font-weight: 700; color: var(--danger); }
.suspended-card-body { font-size: 12.5px; color: var(--muted-text); margin: 0; }

/* A plain-text link styled as a button (Open Studio in a card, runway actions). */
.btn-link {
  background: none; border: none; padding: 0; cursor: pointer;
  font: inherit; color: var(--primary); text-align: left;
}
.btn-link:hover { text-decoration: underline; }
.btn-link:disabled { opacity: 0.55; cursor: not-allowed; text-decoration: none; }

.overview-sub {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin: 28px 0 12px;
}
.overview-sub h2 { font-size: 15px; }
.overview-sub a { font-size: 13px; }
.overview-ok {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
  padding: 22px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.overview-ok p { font-size: 13px; color: var(--muted-text); margin: 0; }

/* Fleet bucket-filter bar (shown on #fleet/<bucket>). */
.fleet-filter-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 14px;
  margin-bottom: 4px;
  font-size: 13px;
  color: var(--muted-text);
  background: var(--muted-surface);
  border-radius: var(--radius-sm);
}

@media (max-width: 620px) {
  .instances-grid { grid-template-columns: 1fr; }
  .overview-head { flex-direction: column; }
  .overview-head-right { justify-content: flex-start; }
  /* .attention-row already STACKS from 768px down — see the GR65 tablet block
     above; the old flex-wrap here would only re-collapse the name column. */
}

/* ============================================================ C6 INSTANCE TABS
   Sub-tabbed instance workspace (charter D49): a persistent header, an a11y tab
   strip, and the active tab's panel. Overview keeps the pre-C6 detail body; the
   Webhooks tab is the C4/C5 proxy console. All colour comes from the semantic
   status tokens — no raw status literals (the __css_check contract). */

.inst-tabs {
  display: flex;
  align-items: center;
  gap: 2px;
  margin-bottom: 22px;
  border-bottom: 1px solid var(--border);
  overflow-x: auto;
}
.inst-tab {
  position: relative;
  display: flex;
  align-items: center;
  height: 40px;
  padding: 0 12px;
  font-size: 13px;
  font-weight: 500;
  color: var(--muted-text);
  white-space: nowrap;
  transition: color var(--t);
}
.inst-tab:hover { color: var(--text); text-decoration: none; }
.inst-tab.is-active { color: var(--text); }
.inst-tab.is-active::after {
  content: "";
  position: absolute;
  left: 8px; right: 8px; bottom: -1px;
  height: 2px;
  background: var(--primary);
  border-radius: 2px 2px 0 0;
}
.inst-tabpanel { display: block; }

/* ---------- Metrics tab (S12: the on-box agent vitals beat) ---------- */
/* Colours READ the S4 role vars (var(--info)/--ok/--warn) — no new hex. The
   sparkline strokes currentColor, inherited from the card's role class. */
.metrics-fresh { margin: 0 0 12px; }
.metrics-stale {
  margin: 0 0 12px;
  padding: 8px 12px;
  border: 1px solid var(--warn);
  border-radius: 8px;
  background: var(--warn-soft);
  color: var(--warn-strong);
  font-size: 13px;
}
.metrics-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 12px;
}
/* Rendered as the compound `fleet-body metrics-body`, so this is the metrics
   DELTA on .fleet-body's column, not a second panel: the grid's own 12px rhythm
   (panel gap and card gap agree) and a floor tall enough that the one-line
   "Loading metrics…" state does not collapse the panel and then snap it taller
   when the first grid paints. */
.metrics-body { gap: 12px; min-height: 96px; }
/* Aligned to the C10 .usage-card chrome so Metrics + Usage read as one system:
   the same radius/padding/shadow, the label + headline value on one row, and a
   full-width area-filled hero sparkline. */
.metric-card {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 14px 16px;
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}
.metric-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
.metric-label { font-size: 13px; font-weight: 600; color: var(--text); }
.metric-value { font-size: 22px; font-weight: 650; color: var(--text); font-variant-numeric: tabular-nums; }
.metric-spark { margin-top: 12px; color: var(--muted-text); }
.metric-spark .spark { display: block; width: 100%; height: 44px; }
.metric--info .metric-spark { color: var(--info); }
.metric--ok .metric-spark { color: var(--ok); }
.metric--warn .metric-spark { color: var(--warn); }
/* D-07 (GR27): the request-level signals the agent doesn't report yet — honest
   DASHED stubs beneath the vitals grid. The dashed border reads "planned, not
   present" against the solid vitals cards; the value is dimmed "Not yet metered"
   (never a fabricated number — GR28 killed the 24h-warmup fiction). */
.metrics-stubs {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 12px;
  margin-top: 12px;
}
.metrics-stub {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 14px 16px;
  border: 1px dashed var(--border);
  border-radius: var(--radius-lg);
}
.metrics-stub-label { flex: 1; font-size: 13px; font-weight: 600; color: var(--text); min-width: 0; }
.metrics-stub-value { font-size: 12px; }
.metrics-health { margin-top: 12px; }

/* ---------- Webhooks tab ---------- */
.wh-toolbar {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 16px;
  flex-wrap: wrap;
}
.wh-dataset { display: flex; align-items: center; gap: 8px; }
.wh-dataset-label {
  font-size: 12px;
  font-weight: 500;
  color: var(--muted-text);
}
.wh-dataset-input { width: 160px; height: 32px; }

.wh-list { display: flex; flex-direction: column; gap: 14px; }
.wh-list-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}
.wh-list-count { font-size: 13px; color: var(--muted-text); }
.wh-empty { margin: 0; }

.wh-card {
  padding: 16px 18px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}
.wh-card-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}
.wh-card-id { min-width: 0; }
.wh-name { font-size: 14px; font-weight: 600; margin-bottom: 2px; }
.wh-url {
  font-family: var(--mono);
  font-size: 13px;
  color: var(--text);
  word-break: break-all;
}
.wh-events { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.wh-event-chip {
  font-size: 11px;
  font-family: var(--mono);
  padding: 2px 7px;
  border-radius: var(--radius-sm);
  background: var(--muted-surface);
  color: var(--muted-text);
}
.wh-event-chip--all { font-style: italic; font-family: var(--font); }

.wh-autodisable { align-items: center; }
.wh-autodisable-text { min-width: 0; }

.wh-meta { margin-top: 10px; font-size: 12px; color: var(--muted-text); }
.wh-meta-fail { color: var(--danger); font-weight: 500; }

.wh-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 14px;
}
.wh-toggle-note { font-size: 12px; color: var(--warn-strong); font-weight: 500; }

.wh-cli { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 12px; }
.wh-cli--center { justify-content: center; margin-top: 16px; }
.cli-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 4px 2px 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--muted-surface);
  max-width: 100%;
}
.cli-chip-code {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--muted-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* v4 "Recent deliveries" card (D-05 / screens2-06 + v4.dc.html:1286-1300): a
   heading over a bordered, hairline-divided card of delivery rows. */
.wh-deliveries {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.wh-del-hint { font-size: 12px; }
.wh-del-empty { font-size: 13px; padding: 6px 0; }
.wh-del-heading { font-size: 13px; font-weight: 600; color: var(--text); }
.wh-del-card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  overflow: hidden;
}
.wh-del-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 12px;
  padding: 11px 14px;
  border-bottom: 1px solid var(--border);
}
.wh-del-card > .wh-del-row:last-child { border-bottom: none; }
.wh-del-event {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--text);
}
/* The status PILL — a rounded, toned capsule carrying the v4 "HTTP 500" / "200
   OK" label. The failed tone leans on the danger colour for the text (the
   designed red treatment); soft-tint bg stays inside the frozen alpha ladder. */
.wh-del-status {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  font-family: var(--mono);
  font-size: 11.5px;
  font-weight: 600;
  padding: 2px 9px;
  border-radius: 99px;
  border: 1px solid var(--border);
  background: var(--muted-surface);
  color: var(--muted-text);
}
.wh-del-status--ok { background: var(--ok-soft); border-color: hsl(var(--ok-hsl) / 0.28); color: var(--ok-strong); }
.wh-del-status--danger { background: var(--danger-soft); border-color: hsl(var(--danger-hsl) / 0.28); color: var(--danger); }
.wh-del-status--info { background: var(--info-soft); border-color: hsl(var(--info-hsl) / 0.28); color: var(--info); }
.wh-del-meta { font-size: 12px; color: var(--muted-text); }
.wh-del-spacer { flex: 1 1 auto; }
.wh-del-when { font-size: 12px; color: var(--muted-text); }
/* Replay — the blue text affordance from the designed row (v4 --blue → --info). */
.wh-del-replay {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  font: inherit;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--info);
}
.wh-del-replay:hover { text-decoration: underline; }
.wh-del-replay:disabled { opacity: 0.55; cursor: not-allowed; text-decoration: none; }
/* The verbatim last_error_text of a failed delivery — wraps to its own full-
   width line under the row (flex-basis:100% inside the wrap row). */
.wh-del-err {
  flex-basis: 100%;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--danger);
  word-break: break-word;
}

.wh-error { margin: 0; }

/* Shown-once rotated-secret block. */
.wh-secret {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px;
  margin-bottom: 4px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--muted-surface);
}
.wh-secret-code {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--mono);
  font-size: 13px;
  color: var(--text);
  word-break: break-all;
}

/* Create-webhook form. */
.wh-form { display: flex; flex-direction: column; }
.wh-events-pick {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: 6px 12px;
  margin-bottom: calc(var(--step) * 2);
}
.wh-event-opt {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  color: var(--text);
}

@media (max-width: 620px) {
  .wh-card-head { flex-direction: column; }
  .wh-dataset-input { width: 120px; }
}

/* ==========================================================================
   Zero broken promises slice (charter D5/D7/D25/D26) — appended block.
   Shared confirm modal (mutate + destroy tiers), deploy-row promote actions,
   and the invitation-accept landing. Uses only the wave-1 token contract.
   ========================================================================== */

/* -- confirm modal ---------------------------------------------------------
   Mutate tier reuses .modal-sub for its single consequence sentence; destroy
   tier gets the consequence LIST + the typed-echo field. GR41: the danger
   (no-echo) tier's rich-body slot rides .modal-sub styling too — .cm-body only
   restores the ink for its <b> emphasis (inherits --muted-text otherwise, so
   no new fg/bg pairing enters the CONTRAST_PAIRS manifest). */
.cm-body b { color: var(--text); font-weight: 600; }
.cm-body { margin-bottom: 4px; }
.cm-consequence { margin-bottom: 2px; }
.cm-consequences {
  margin: 0 0 16px;
  padding-left: 18px;
  font-size: 13px;
  color: var(--muted-text);
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.cm-typed-field { margin-top: 12px; }
.cm-name { font-family: var(--mono); font-weight: 600; color: var(--text); user-select: none; }
/* Inline failure surface (D25): the human sentence lives HERE, next to the one
   recovery action the primary button morphs into — never a dead toast. */
.cm-error {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin-top: 14px;
  padding: 8px 10px;
  border: 1px solid hsl(var(--danger-hsl) / 0.28);
  border-radius: var(--radius-sm);
  background: var(--danger-soft);
}
.cm-error-msg { margin: 0; font-size: 13px; line-height: 1.5; color: var(--text); }

/* -- deploy-row promote actions (rollback / redeploy) ---------------------- */
.dep-side { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }
.dep-promote { white-space: nowrap; }
.dep-current {
  display: inline-block;
  margin-left: 8px;
  padding: 1px 8px;
  font-size: 11px;
  font-weight: 600;
  border-radius: var(--pill);
  background: var(--ok-soft);
  color: var(--ok);
  border: 1px solid hsl(var(--ok-hsl) / 0.28);
  vertical-align: 1px;
}

/* -- invitation-accept landing --------------------------------------------- */
.invite-wrap { display: flex; justify-content: center; padding: 48px 16px; }
.invite-card { max-width: 460px; width: 100%; text-align: center; padding: 32px 28px; }
.invite-ico {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto 16px;
  font-size: 20px;
  font-weight: 600;
}
.invite-ico--ok { background: var(--ok-soft); color: var(--ok); }
.invite-ico--info { background: var(--info-soft); color: var(--info); }
.invite-ico--warn { background: var(--warn-soft); color: var(--warn-strong); }
/* gr-blk-invite-ico-danger-variant. The two failure-shaped landings both wore
   ICO_WARN's "!" and were distinguishable only by reading the copy. The split
   is by RECOVERABILITY, not by severity-vibe: warn = "this link is fine, come
   back" (expired, and the retryable error — nothing changed, try again);
   danger = "this link is dead" (revoked or already used), which no amount of
   retrying fixes. --danger on --danger-soft over --surface is already an
   AA-gated pairing in __css_check's CONTRAST_PAIRS, so this needs no new
   token — and --danger-hsl stays hue 0 in both themes (per-accent separation
   is gr-blk-ok-danger-hue-separation, deliberately not this slice). */
.invite-ico--danger { background: var(--danger-soft); color: var(--danger); }
.invite-title { font-size: 20px; font-weight: 600; margin: 0 0 8px; }
.invite-copy { font-size: 14px; color: var(--muted-text); margin: 0 0 20px; line-height: 1.55; }
.invite-copy b { color: var(--text); font-weight: 600; }
.invite-actions { display: flex; flex-direction: column; align-items: center; gap: 10px; }
.invite-action { min-width: 200px; }
.invite-skip { font-size: 13px; color: var(--muted-text); }
.invite-skip:hover { color: var(--text); }

/* The sign-in card's invitation banner (logged-out landing). :empty hides the
   slot while the preview fetch is in flight, so no husk flashes. Info-tinted
   on the ladder (styleguide banner grammar: tint + border say "info"; copy
   stays readable --muted-text, never colour-on-colour). */
.auth-invite {
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--muted-text);
  background: var(--info-soft);
  border: 1px solid hsl(var(--info-hsl) / 0.3);
  border-radius: var(--radius);
  padding: 10px 12px;
  margin-bottom: 18px;
}
.auth-invite:empty { display: none; }
.auth-invite-title { display: block; font-weight: 600; color: var(--text); }
.auth-invite-email { font-weight: 600; color: var(--text); }
/* ============================================================================
   C8 — instance Timeline tab + golden-path verify chips (charter D10/D53).
   Appended per slice contract; consumes only the semantic status tokens.
   ========================================================================== */

/* The merged events+audit feed. */
.tlv { display: flex; flex-direction: column; gap: 8px; }
.tlv-quiet {
  font-size: 12px;
  color: var(--muted-text);
  padding: 6px 10px;
  border: 1px dashed var(--border);
  border-radius: var(--radius-sm);
}
.tlv-row {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 10px 12px;
}
.tlv-head { display: flex; align-items: center; gap: 10px; min-width: 0; }
.tlv-badge {
  flex: 0 0 auto;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 2px 7px;
  border-radius: var(--pill);
  border: 1px solid var(--border);
}
.tlv-badge--event { background: var(--info-soft); color: var(--info); border-color: hsl(var(--info-hsl) / 0.28); }
.tlv-badge--verify { background: var(--ok-soft); color: var(--ok); border-color: hsl(var(--ok-hsl) / 0.28); }
/* A failed run's badge must not read green — coloured by outcome, not source. */
.tlv-badge--verify-fail { background: var(--danger-soft); color: var(--danger); border-color: hsl(var(--danger-hsl) / 0.28); }
.tlv-badge--audit { background: var(--muted-surface); color: var(--muted-text); }
.tlv-title {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 13px;
  font-weight: 500;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tlv-when { flex: 0 0 auto; font-size: 12px; color: var(--muted-text); }
.tlv-toggle {
  flex: 0 0 auto;
  font: inherit;
  font-size: 12px;
  color: var(--muted-text);
  background: none;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 2px 8px;
  cursor: pointer;
  transition: color var(--t), border-color var(--t);
}
.tlv-toggle:hover { color: var(--text); border-color: var(--ring); }
.tlv-toggle[aria-expanded="true"] { color: var(--text); }
.tlv-detail {
  margin: 8px 0 0;
  padding: 10px;
  font-family: var(--mono);
  font-size: 11px;
  line-height: 1.5;
  color: var(--text);
  background: var(--muted-surface);
  border-radius: var(--radius-sm);
  overflow-x: auto;
  white-space: pre-wrap;
  word-break: break-word;
}

/* Golden-path verify card (instance Overview). */
.vf-card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 14px 16px;
  margin-bottom: calc(var(--step) * 2);
}
.vf-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 10px; }
.vf-head h2 { margin: 0; font-size: 14px; }
.vf-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.vf-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  font-weight: 500;
  padding: 4px 10px;
  border-radius: var(--pill);
  border: 1px solid var(--border);
  color: var(--text);
}
/* GR77/GR90 — the ember pass/fail wash collision, fixed on a HUE-INDEPENDENT
   axis. Both washes used to derive from --ok-soft / --danger-soft, and --ok-hsl
   is mapped 1:1 to --primary-hsl for EVERY identity while --danger-hsl is pinned
   at hue 0. Under ember the brand sits ~19° from red where the other four sit
   150°+ away, so the two washes converged: sampled RGB distance 6.1 (light) /
   8.3 (dark) against 19.5–48.2 elsewhere — a 2.5–6x outlier, and the one defect
   the ~1,585-shot accent matrix found.
   The fix separates the pair on ALPHA, not hue, so it works identically in all
   five identities and gets no worse where hue already separates. --ok-hsl is NOT
   touched (it must keep tracking the brand) and the FAIL wash is NOT touched
   either — deepening it was measured and rejected: it does separate (ember-light
   6.1 → 45.9 at 0.28) but it drags the --danger glyph from 4.71:1 to 3.71:1,
   and because __css_check measures the --danger-soft TOKEN rather than this
   rule, that regression would have shipped under a green gate.
   So the PASS chip becomes the quiet one instead — the honest reading anyway: a
   passing check should not shout, and the fail chip keeps its full weight. The
   spread across all ten theme-states collapses from [6.1 … 48.2] to
   [27.7 … 42.5]; ember-light goes 6.1 → 27.7 and ember-dark 8.3 → 32.4. The
   border keeps the tone at full 0.28 so the pass chip stays an OBJECT, not a
   tint (measured 15.6–21.9 from bare --surface). */
.vf-chip--pass { background: hsl(var(--ok-hsl) / 0.07); border-color: hsl(var(--ok-hsl) / 0.28); }
.vf-chip--fail { background: var(--danger-soft); border-color: hsl(var(--danger-hsl) / 0.28); }
.vf-chip--unknown { background: var(--muted-surface); color: var(--muted-text); }
.vf-chip-glyph { flex: 0 0 auto; }
.vf-chip--pass .vf-chip-glyph { color: var(--ok); }
.vf-chip--fail .vf-chip-glyph { color: var(--danger); }
.vf-chip-code { font-family: var(--mono); font-size: 11px; color: var(--muted-text); }
.vf-meta { margin-top: 10px; font-size: 12px; color: var(--muted-text); }
.vf-note {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 10px;
  padding: 8px 10px;
  font-size: 13px;
  color: var(--text);
  background: var(--warn-soft);
  border: 1px solid hsl(var(--warn-hsl) / 0.3);
  border-radius: var(--radius-sm);
}

@media (max-width: 620px) {
  .tlv-head { flex-wrap: wrap; }
  .tlv-title { white-space: normal; }
}

/* ---------------------------------------------------------- liveness chip (OC6)
   Topbar SSE health dot, injected into .topbar-right by the SPA. The dot colour
   tracks stream up-vs-dropped exactly (green family vs amber) — the page never
   lies about whether it is current — and the "as of" label carries recency.
   Append-only; no token-block edits. Motion is state (reconnect pulse) or a
   data-changed ping, never idle decoration — both collapse under reduced-motion. */
.live-chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 26px;
  padding: 0 10px;
  border-radius: var(--pill);
  border: 1px solid var(--border);
  background: var(--muted-surface);
  font-size: 12px;
  font-weight: 500;
  color: var(--muted-text);
  white-space: nowrap;
  user-select: none;
}
.live-dot {
  position: relative;
  width: 8px;
  height: 8px;
  border-radius: var(--pill);
  background: var(--dim);
  flex: 0 0 auto;
}
.live-chip-label { color: var(--text); }
.live-chip-ago { color: var(--muted-text); font-variant-numeric: tabular-nums; }

/* live — connected + recently confirmed. The ping ring fires once per data frame
   (added by pingLivenessChip); it sits invisible until re-armed. */
.live-chip[data-state="live"] .live-dot { background: var(--ok); }
.live-dot::after {
  content: "";
  position: absolute;
  inset: -3px;
  border-radius: var(--pill);
  background: var(--ok);
  opacity: 0;
  pointer-events: none;
}
.live-chip[data-state="live"] .live-dot.is-ping::after {
  animation: live-ping 1s ease-out 1;
}
@keyframes live-ping {
  0%   { opacity: 0.45; transform: scale(0.6); }
  100% { opacity: 0;    transform: scale(2.2); }
}

/* stale — stream is up but quiet (no data frame in 90s). Same green family as
   live (the connection IS up), dimmed to say "can't prove currency"; the "as of"
   label ages honestly. NOT amber — a quiet fleet is not an error. */
.live-chip[data-state="stale"] .live-dot { background: hsl(var(--ok-hsl) / 0.55); }
.live-chip[data-state="stale"] .live-chip-label { color: var(--muted-text); }

/* reconnecting — onerror fired, EventSource is retrying. Amber, gently pulsing
   to signal active state (like a spinner — state, not decoration). */
.live-chip[data-state="reconnecting"] {
  border-color: hsl(var(--warn-hsl) / 0.35);
  background: var(--warn-soft);
}
.live-chip[data-state="reconnecting"] .live-dot { background: var(--warn); animation: live-breathe 1.4s ease-in-out infinite; }
/* Label stays --text on --warn-soft (the legible pattern .vf-note uses) — the
   amber dot + border already carry the "warning" tone. */
@keyframes live-breathe {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.35; }
}

/* dead (cch-w3) — TERMINAL: the stream is down and cannot come back on its own
   (the session credential itself was refused when reminting the stream ticket).
   Danger, and deliberately STATIC: there is no activity to signal here, and a
   pulse would be the same lie the amber "reconnecting" state used to tell.
   This rule is load-bearing, not decoration. Without it a fourth data-state
   matches ZERO author rules and inherits .live-dot { background: var(--dim) } =
   rgb(95,106,123) — a calm neutral grey for the MOST severe state, quieter than
   the less-severe amber it replaces. And no gate would catch that: __css_check
   E2 extracts CLASS emissions only (/\.className\s*=/, /classList\.(add|remove|
   toggle)\(/) and is structurally blind to setAttribute("data-state", …), while
   cssom-parity.mjs has no data-state references at all. */
.live-chip[data-state="dead"] {
  border-color: hsl(var(--danger-hsl) / 0.35);
  background: var(--danger-soft);
}
.live-chip[data-state="dead"] .live-dot { background: var(--danger); }

@media (prefers-reduced-motion: reduce) {
  /* Motion is redundant with colour + label here — drop it entirely. */
  .live-chip[data-state="reconnecting"] .live-dot { animation: none; }
  .live-chip[data-state="live"] .live-dot.is-ping::after { animation: none; }
}

/* On a narrow topbar the label/ago fold away (same rule as .topbar-link); the
   dot alone still carries the health signal. 720 -> 768 with GR116: the fold is
   the third cosmetic declaration that buys the past-due billing chip its full
   169.78px label back at tablet widths. */
@media (max-width: 768px) {
  .live-chip-label, .live-chip-ago { display: none; }
  .live-chip { padding: 0 7px; gap: 0; }
}

/* ============================================================================
   S14 portable archives — the fleet-view Archives panel. Archived-instance
   bundles read from object storage, each with a copy-paste resurrect command.
   Reuses the shared surface / border / text tokens (no raw color).
   ========================================================================== */
.archives-panel {
  margin-top: 32px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
}
.archives-title {
  font-size: 15px;
  font-weight: 600;
  margin: 0;
}
.archives-sub {
  font-size: 13px;
  margin: 2px 0 14px;
}
.archives-body { display: block; }
.archives-note {
  font-size: 13px;
  color: var(--muted-text);
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.archives-note p { margin: 0; }
.archives-note--warn { color: var(--text); }
/* storage-unconfigured: a distinct, non-transient state (amber dot + what an
   archive is + Retry), not a red outage. The server owns the headline copy
   verbatim. */
.archives-note--unconfigured { color: var(--text); }
.archives-note-dot { width: 8px; height: 8px; border-radius: var(--pill); background: var(--warn); flex: 0 0 auto; }
/* The in-place explanation that replaced the dead "How archives work" link
   (gr-blk-archives-doc-link). `.archives-note` is a wrapping flex ROW, so the
   sub-copy claims a full line of its own rather than being squeezed beside the
   dot and the headline. Secondary voice — the headline stays --text. */
.archives-note-sub {
  flex: 1 0 100%;
  margin: 0;
  color: var(--muted-text);
  line-height: 1.5;
  max-width: 62ch;
}

.archive-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.archive-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}
.archive-id {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  min-width: 0;
}
.archive-fqdn {
  font-weight: 600;
  font-size: 14px;
}
.archive-when {
  font-size: 12px;
  color: var(--muted-text);
}
.archive-meta {
  flex-basis: 100%;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--muted-text);
}
.archive-resurrect {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
}
.archive-resurrect-btn {
  flex: 0 0 auto;
}
/* The portable-restore caption under the provider picker in the resurrect sheet. */
.resurrect-portable {
  margin-top: 6px;
  font-size: 12px;
}

/* ── Usage quota bars (OC7) ────────────────────────────────────────────────
   A meter with a real plan ceiling draws a track + fill under its name; the
   fill tone escalates ok → warn → over. Neutral track (--muted-surface) so an
   unfilled bar never reads as an alarm; tones reuse the S4 role tokens (no new
   literals). An unlimited/unmetered meter draws no bar at all (honest — set in
   the renderer, not here). */
.usage-bar {
  margin-top: 8px;
  height: 6px;
  border-radius: var(--radius-sm);
  background: var(--muted-surface);
  overflow: hidden;
}
.usage-bar-fill {
  display: block;
  height: 100%;
  border-radius: var(--radius-sm);
  background: var(--ok);
  transition: width var(--t);
}
.usage-bar--warn .usage-bar-fill { background: var(--warn); }
.usage-bar--over .usage-bar-fill { background: var(--danger); }
.usage-bar-meta {
  justify-content: space-between;
}
.usage-bar-quota {
  font-family: var(--mono);
}
.usage-row--warn .usage-bar-quota { color: var(--warn); }
.usage-row--over .usage-bar-quota { color: var(--danger); }
.usage-bar-action {
  font-weight: 600;
  color: var(--danger);
}
@media (prefers-reduced-motion: reduce) {
  .usage-bar-fill { transition: none; }
}



/* ── Command palette (Cmd+K, wave 3) ───────────────────────────────────────
   A fleet-wide launcher rendered into the SHARED modal root. :has() widens the
   card, drops its padding, and hides the × so the palette owns the whole surface
   (Escape / backdrop still close it through the modal seam — the palette footer
   reminds the operator). The list is a scrolling listbox; the KEYBOARD selection
   carries .is-active while hover tints independently, so mouse and keyboard never
   fight over one highlight. Every colour reuses an existing token — no literals. */
.modal-root:has(.cmdk) .modal-card { max-width: 600px; padding: 0; overflow: hidden; }
.modal-root:has(.cmdk) .modal-x { display: none; }
.cmdk { display: flex; flex-direction: column; max-height: min(70vh, 560px); }
.cmdk-search-wrap { padding: 14px 16px; border-bottom: 1px solid var(--border); }
.cmdk-search {
  width: 100%;
  border: none;
  background: transparent;
  color: var(--text);
  font-size: 15px;
  font-family: inherit;
  outline: none;
}
.cmdk-search::placeholder { color: var(--muted-text); }
.cmdk-list { overflow-y: auto; padding: 6px; min-height: 64px; }
.cmdk-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 9px 12px;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.cmdk-row-label {
  font-size: 14px;
  font-weight: 500;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.cmdk-row-meta { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }
.cmdk-row-hint { font-size: 12px; color: var(--muted-text); }
.cmdk-row-group {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted-text);
}
.cmdk-row:hover { background: var(--muted-surface); }
.cmdk-row.is-active { background: var(--muted-surface); }
.cmdk-empty { padding: 24px 12px; text-align: center; color: var(--muted-text); font-size: 13px; }
.cmdk-foot {
  display: flex;
  gap: 16px;
  padding: 10px 16px;
  border-top: 1px solid var(--border);
  font-size: 12px;
  color: var(--muted-text);
}
.cmdk-hint { display: inline-flex; align-items: center; gap: 5px; }
.cmdk-foot kbd {
  font-family: var(--mono);
  font-size: 11px;
  padding: 1px 5px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--muted-surface);
}

/* ── Wave 4 (OC19): Usage-tab sparklines ──────────────────────────────────────
   Each meter row's quiet 14-day trend, painted by the shared sparklineSvg (which
   owns the shape: null-is-gap runs, isolated-point dots, empty-frame honesty). This
   block only sizes and TINTS it — currentColor inherits the row's quota tone so an
   over-limit meter's trend reads danger, a warn meter amber, and an ok/plain meter
   stays muted ink. ~120×32 keeps it subordinate to the value on a row already
   carrying name + freshness + quota bar. Append-only; no token-block/index/styleguide
   edits, no Overview-strip fetch (OC19 cut), no worst-state accents (OC22). */
.usage-spark { display: inline-flex; align-items: center; flex: 0 0 auto; color: var(--muted-text); }
.usage-spark .spark { display: block; width: 120px; height: 32px; }
.usage-row--warn .usage-spark { color: var(--warn); }
.usage-row--over .usage-spark { color: var(--danger); }

/* ── Wave 5 (OC23/OC26): machine-meter threshold tinting ──────────────────────
   req_per_s / p95_ms carry warn_at/over_at but NO quota — a rate/latency has no
   plan ceiling to draw a bar to — so the OC25 state tints the row's VALUE badge
   instead of a fill. Percent meters (cpu/ram/disk) keep their true 0-100 bar AND
   pick this up, so a hot box's number reddens alongside its bar. Reuses the S4
   role tokens (no new literals); append-only, no token-block/index edits. */
.usage-row--warn .fleet-badges strong { color: var(--warn); }
.usage-row--over .fleet-badges strong { color: var(--danger); }

/* ── C10 Usage tab: the meter grid rendered as stat cards ─────────────────────
   Mirrors the S12 .metrics-grid/.metric-card language so Usage and Metrics read
   as one system. Each meter is a card: label + headline value on one row, the
   freshness sub, a full-width hero sparkline (area-filled via sparklineSvg's
   opts.area), then the quota bar when a real ceiling exists. warn/over tint the
   value + spark (currentColor) + bar through ONE --usage-card-- tone vocabulary
   (the S4 role tokens — no new literals). The pre-card .usage-row/.fleet-row
   rules above still serve the Overview fleet strip; these are additive. */
.usage-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 12px;
}
.usage-card {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 14px 16px;
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}
.usage-card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
.usage-card-label { font-size: 13px; font-weight: 600; color: var(--text); min-width: 0; }
.usage-card-value {
  font-size: 22px;
  font-weight: 650;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  flex: 0 0 auto;
}
.usage-card-value .dim { font-size: 13px; font-weight: 500; }
.usage-card-sub { margin-top: 2px; font-size: 12px; color: var(--muted-text); }
.usage-card .usage-spark { display: block; margin-top: 12px; }
.usage-card .usage-spark .spark { display: block; width: 100%; height: 44px; }
.usage-card--warn .usage-card-value,
.usage-card--warn .usage-spark { color: var(--warn); }
.usage-card--over .usage-card-value,
.usage-card--over .usage-spark { color: var(--danger); }
.usage-card--warn .usage-bar-quota { color: var(--warn); }
.usage-card--over .usage-bar-quota { color: var(--danger); }

/* ═══════════════════════════════════════════════ DEPLOY RAIL (site detail)
   The console's live deploy rail — the premium twin of .bp-timeline (stw5 D22).
   Same vertical card shell (border / radius / surface); the BODY is the shared
   .new-steps step list (dot / ring / spin, already mature), so only this wrapper
   chrome is authored here. Tone-only status pill + honest live/fail footers.
   Every colour resolves through the token contract — zero new literals, so the
   theme compiler and design/check.mjs Part E stay in lockstep across all themes. */
.deploy-rail {
  margin: 0 0 22px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px 18px;
  background: var(--surface);
}
.deploy-rail-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.deploy-rail-title { font-size: 15px; margin: 0; }
/* The shared step list sits a touch tighter inside the card, matching the
   .bp-timeline house rhythm. */
.deploy-rail .new-steps { margin-top: 16px; }

/* Honest status pill: neutral by default, tinted per tone. --active carries a
   soft breathing ring while the box is still working; --live / --failed settle. */
.deploy-rail-status {
  flex: 0 0 auto;
  align-self: center;
  font-size: 12px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.01em;
  padding: 5px 10px;
  border-radius: var(--pill);
  border: 1px solid var(--border);
  color: var(--muted-text);
  background: var(--muted-surface);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.deploy-rail-status--active {
  color: var(--info);
  background: var(--info-soft);
  border-color: hsl(var(--info-hsl) / 0.35);
  animation: deploy-rail-breathe 1.8s var(--ease) infinite;
}
.deploy-rail-status--live {
  color: var(--ok);
  background: var(--ok-soft);
  border-color: hsl(var(--ok-hsl) / 0.35);
}
.deploy-rail-status--failed {
  color: var(--danger);
  background: var(--danger-soft);
  border-color: hsl(var(--danger-hsl) / 0.35);
}
@keyframes deploy-rail-breathe {
  0%, 100% { box-shadow: 0 0 0 0 hsl(var(--info-hsl) / 0.28); }
  50% { box-shadow: 0 0 0 4px hsl(var(--info-hsl) / 0); }
}
@media (prefers-reduced-motion: reduce) {
  /* The pill's word already names the state — the ring is redundant motion. */
  .deploy-rail-status--active { animation: none; }
}

/* Live footer: the copyable URL once every stage is done — a calm top rule
   separates it from the last (RETIRE) step. */
.deploy-rail-live {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
  font-size: 13px;
}
.deploy-rail-live .site-open { font-family: var(--mono); word-break: break-all; min-width: 0; }
.deploy-rail-live .copy-btn { flex: 0 0 auto; }

/* Failure footer: the observed→next hint — the danger-soft twin of .bp-tl-fail. */
.deploy-rail-fail {
  margin-top: 16px;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  background: var(--danger-soft);
  border: 1px solid hsl(var(--danger-hsl) / 0.3);
  color: var(--text);
  font-size: 13px;
  line-height: 1.45;
}

/* ── Create-site "Deploy now" field ────────────────────────────────────────
   The one-motion create-and-deploy checkbox reads as a grouped, opt-in card
   rather than a loose form field — bordered, muted, with a dim sub-note aligned
   under the label when deploy isn't available yet (instance still provisioning). */
.field-check {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 11px 12px;
  background: var(--muted-surface);
}
.field-check .check-row {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  font-size: 13px;
  line-height: 1.4;
  cursor: pointer;
}
.field-check .check-row input[type="checkbox"] { margin-top: 1px; flex: 0 0 auto; }
.field-check .check-row input:disabled { cursor: not-allowed; }
.field-check .check-note { margin-top: 7px; padding-left: 24px; font-size: 12px; line-height: 1.4; }
/* -- stw5 deploy-history panel + site-level synchronous rollback (D25/D28) ---- */
/* The Deployments section header — title on the left, the one-click "Roll back"
   affordance on the right. Reuses existing button classes; only the row layout is
   new. Tokens only (below the GENERATED marker), so the design gate stays parity. */
.deploys-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin: 6px 0 10px;
}
.deploys-head h2 { margin: 0; }
/* D28: the honest capped-history footer — "Showing the N most recent deployments."
   Muted + small; never a "full history" claim. Lives INSIDE the v4 ladder card
   (gr-p3-site-detail), so it reads as the card's own footer row. */
.deploys-note { font-size: 12px; color: var(--muted-text); margin: 0; padding: 9px 16px 11px; }
/* The restored "Now live" chip — distinguished from a plain restore-free current
   chip by an info tint so "rolled back" reads as an intentional state, not a fault.
   Info tokens are theme-invariant (shared status hues), so no [data-theme] override. */
.dep-current--restored {
  background: var(--info-soft);
  color: var(--info);
  border-color: hsl(var(--info-hsl) / 0.32);
}
/* The deployment_id:null completion banner — a "Rolled back" pill + the previous-
   release sentence + a link to the now-serving URL. Calm info tone (a settled
   success, not a warning). */
.deploys-rollback-note {
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
  font-size: 13px; color: var(--text);
  background: var(--info-soft);
  border: 1px solid hsl(var(--info-hsl) / 0.28);
  border-radius: var(--radius);
  padding: 9px 12px; margin: 0 0 10px;
  animation: new-detail-in 0.28s ease;
}
.deploys-rollback-pill {
  flex: 0 0 auto;
  display: inline-flex; align-items: center;
  height: 20px; padding: 0 9px;
  font-size: 11px; font-weight: 600;
  border-radius: var(--pill);
  background: hsl(var(--info-hsl) / 0.16);
  color: var(--info);
  border: 1px solid hsl(var(--info-hsl) / 0.32);
}
.deploys-rollback-note a { color: var(--info); text-decoration: none; }
.deploys-rollback-note a:hover { text-decoration: underline; }
/* search-template W8: the inline theme picker in the site-detail rail — a compact
   select that reuses the token palette; all colours resolve through var(--…). */
.rail-select {
  font: inherit; font-size: 13px; padding: 3px 8px; border-radius: var(--radius-sm);
  border: 1px solid var(--border); background: var(--surface); color: var(--text);
  max-width: 60%; cursor: pointer;
}
.rail-select:hover { border-color: var(--ring); }
.rail-select:disabled { opacity: 0.6; cursor: progress; }

/* ============================================================ V4 SHELL (gr-w3)
   The designer's v4 chrome — a fixed sidebar (workspace switcher + Find/⌘K +
   context-morphing nav + account block) beside a scrolling main column (topbar +
   content). Presentation over the UNCHANGED router/IA (GR10). Shell surfaces ride
   the identity-invariant cloudChrome passthrough family (--cc-*, GR2); only the
   accent (--primary…) varies per identity. Append-only tail (OC9). */

/* Row layout — overrides the earlier column .app-shell (source-order wins). */
.app-shell { display: flex; flex-direction: row; min-height: 100vh; align-items: stretch; }

.sidebar {
  flex: 0 0 232px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 16px 12px;
  background: var(--cc-bg-side);
  border-right: 1px solid rgba(var(--cc-line-rgb), 0.08);
  position: sticky;
  top: 0;
  align-self: flex-start;
  height: 100vh;
  overflow-y: auto;
}
.shell-main { flex: 1 1 auto; display: flex; flex-direction: column; min-width: 0; }

/* workspace switcher + plan chip */
.ws-switch {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px; margin-bottom: 6px;
  border: none; background: transparent; cursor: pointer;
  border-radius: var(--radius); text-align: left; color: var(--text);
  transition: background var(--t);
}
.ws-switch:hover { background: rgba(var(--cc-line-rgb), 0.05); }
.ws-switch:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--cc-bg-side), 0 0 0 4px var(--ring-soft); }
.ws-avatar {
  width: 26px; height: 26px; flex: 0 0 auto;
  border-radius: 7px; background: var(--primary); color: var(--primary-fg);
  display: grid; place-items: center; font-size: 12px; font-weight: 700;
}
.ws-name { font-size: var(--text-base); font-weight: 700; letter-spacing: -0.01em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ws-plan {
  flex: 0 0 auto; padding: 2px 8px; border-radius: var(--pill);
  font-size: 10.5px; font-weight: 600;
  background: hsl(var(--primary-hsl) / 0.12); color: var(--primary);
}
.ws-caret { flex: 0 0 auto; margin-left: auto; opacity: 0.55; color: var(--muted-text); }

/* Find / ⌘K */
.nav-find {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 10px; margin-bottom: 14px;
  border: 1px solid rgba(var(--cc-line-rgb), 0.1); border-radius: var(--radius);
  background: transparent; color: var(--cc-fg4); cursor: pointer;
  font: inherit; font-size: var(--text-sm); transition: border-color var(--t), color var(--t);
}
.nav-find:hover { border-color: rgba(var(--cc-line-rgb), 0.22); color: var(--muted-text); }
.nav-find:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--cc-bg-side), 0 0 0 4px var(--ring-soft); }
.nav-find-ico { flex: 0 0 auto; }
.nav-find-key {
  margin-left: auto; font-family: var(--mono); font-size: 10.5px;
  padding: 1px 6px; border: 1px solid rgba(var(--cc-line-rgb), 0.12); border-radius: 5px;
}

/* nav layers + eyebrows */
.nav-layer { display: flex; flex-direction: column; gap: 2px; }
.nav-eyebrow {
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.14em;
  color: var(--cc-fg4); padding: 8px 10px 4px; text-transform: uppercase;
}
.nav-group { margin-top: 12px; padding-top: 10px; border-top: 1px solid rgba(var(--cc-line-rgb), 0.06); display: flex; flex-direction: column; gap: 2px; }

/* sidebar nav items (the primary nav-links carry data-view; applyRoute lights them) */
.sidebar .nav-link {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px; border-radius: var(--radius);
  font-size: var(--text-sm); font-weight: 500; color: var(--muted-text);
  transition: background var(--t), color var(--t);
}
.sidebar .nav-link:hover { background: rgba(var(--cc-line-rgb), 0.05); color: var(--text); text-decoration: none; }
.sidebar .nav-link.is-active { background: hsl(var(--primary-hsl) / 0.1); color: var(--text); }
.sidebar .nav-link.is-active .nav-ico { color: var(--primary); }
.nav-ico { flex: 0 0 auto; color: currentColor; }
.nav-sub { padding-left: 12px; font-weight: 500; }

/* morph: back row + drilled-in context */
.nav-back {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 10px; border-radius: var(--radius);
  font-size: 12.5px; color: var(--cc-fg4); transition: background var(--t), color var(--t);
}
.nav-back:hover { background: rgba(var(--cc-line-rgb), 0.05); color: var(--muted-text); text-decoration: none; }
.nav-ctx { display: flex; align-items: center; gap: 8px; padding: 10px 10px 2px; }
.nav-ctx-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; background: var(--muted-text); }
.nav-ctx-name { font-size: var(--text-base); font-weight: 700; letter-spacing: -0.01em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* footer cluster */
.nav-spacer { flex: 1 1 auto; }
.nav-foot { display: flex; flex-direction: column; gap: 2px; padding-top: 10px; border-top: 1px solid rgba(var(--cc-line-rgb), 0.06); }
.nav-account {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px; margin-top: 4px;
  border: none; background: transparent; cursor: pointer; text-align: left;
  border-radius: var(--radius); color: var(--text); transition: background var(--t);
}
.nav-operator { color: var(--muted-text); }
.nav-account:hover { background: rgba(var(--cc-line-rgb), 0.05); }
.nav-account:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--cc-bg-side), 0 0 0 4px var(--ring-soft); }
.acct-id { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; }
.acct-name { font-size: var(--text-sm); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.acct-email { font-size: 11.5px; color: var(--cc-fg4); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---------- topbar: instance-scope dropdown ---------- */
.topbar-scope { position: relative; }
.scope-switch {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 12px; border: 1px solid rgba(var(--cc-line-rgb), 0.1); border-radius: var(--radius);
  background: transparent; color: var(--text); cursor: pointer;
  font: inherit; font-size: var(--text-sm); font-weight: 600; transition: border-color var(--t);
}
.scope-switch:hover { border-color: rgba(var(--cc-line-rgb), 0.22); }
.scope-switch:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--ring-soft); }
.scope-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 220px; }
.scope-caret { flex: 0 0 auto; opacity: 0.6; }
.scope-menu {
  position: absolute; top: calc(100% + 8px); left: 0; width: 300px; z-index: var(--z-menu);
  background: var(--cc-modal); border: 1px solid rgba(var(--cc-line-rgb), 0.12);
  border-radius: var(--radius-lg); box-shadow: var(--shadow); padding: 6px;
  display: flex; flex-direction: column; gap: 2px;
}
.scope-eyebrow { font-family: var(--mono); font-size: 10px; letter-spacing: 0.14em; color: var(--cc-fg4); padding: 6px 10px 4px; text-transform: uppercase; }
.scope-empty { padding: 10px; font-size: var(--text-sm); color: var(--muted-text); }
.scope-item {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 10px; border-radius: var(--radius); cursor: pointer;
  font: inherit; font-size: var(--text-sm); font-weight: 500; color: var(--text);
  border: none; background: transparent; width: 100%; text-align: left; transition: background var(--t);
}
.scope-item:hover { background: rgba(var(--cc-line-rgb), 0.06); text-decoration: none; }
.scope-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; background: var(--muted-text); }
.scope-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.scope-foot { color: var(--muted-text); border-top: 1px solid rgba(var(--cc-line-rgb), 0.07); margin-top: 4px; }

/* Narrow viewports: the sidebar folds to a full-width strip above the content so
   nothing is stranded (the nav stays reachable; content flows below). */
@media (max-width: 720px) {
  .app-shell { flex-direction: column; }
  .sidebar { flex-basis: auto; width: 100%; height: auto; position: static; border-right: none; border-bottom: 1px solid rgba(var(--cc-line-rgb), 0.08); }
  .nav-spacer { display: none; }
}

/* ---------------------------------------------------------- gr-p2 plan & dunning (C-03/C-04)
   Trial state (countdown chip + ratified CTA), the GR17 dunning banner, the
   quota line on tier cards, and the GR20 topbar billing chip. Append-only tail;
   new rules ride the v4 ladder (--cc-*) directly — no new legacy-alias uses. */
.trial-chip {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  padding: 3px 9px;
  border-radius: var(--pill);
  color: var(--cc-blue);
  background: hsl(var(--info-hsl) / 0.12);
  border: 1px solid hsl(var(--info-hsl) / 0.3);
  white-space: nowrap;
}
.trial-chip--low {
  color: var(--danger);
  background: hsl(var(--danger-hsl) / 0.1);
  border-color: hsl(var(--danger-hsl) / 0.3);
}
.trial-cta { font-size: 14px; font-weight: 600; color: var(--cc-fg); margin: 4px 0 10px; }

/* The GR17 banner stacks (chip+title / body / CTA) inside the .notice frame. */
.dunning-banner { flex-direction: column; align-items: flex-start; gap: 8px; }
.dunning-banner .btn { margin-left: 0; }
.dunning-head { display: flex; align-items: center; gap: 10px; }
.dunning-plan {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 600;
  padding: 2px 8px;
  border-radius: var(--pill);
  border: 1px solid rgba(var(--cc-line-rgb), 0.25);
  color: var(--cc-fg2);
  white-space: nowrap;
}
.dunning-title { color: var(--warn-strong); font-weight: 700; }
.dunning-body { margin: 0; max-width: 62ch; }

/* Quota-honest tier cards: the real ceiling, mono, right above the action. */
.tier-quota { font-family: var(--mono); font-size: 11px; color: var(--cc-fg3); margin: 0 0 12px; }

/* GR20 topbar billing chip — sits AHEAD of the SSE liveness chip; trial rides
   the info blue, past-due the danger red. It is a link to #billing. */
.billing-chip {
  display: flex;
  align-items: center;
  height: 26px;
  padding: 0 10px;
  border-radius: var(--pill);
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
  user-select: none;
  text-decoration: none;
  /* GR116 correctness half: the chip is the widest topbar-right child and the
     one that must absorb the squeeze in the 721-782 band. Clipping here (with
     min-width:0 from `.topbar-right > *`) is what turns a page-level
     horizontal scrollbar into a truncated chip; the 768-block cosmetic trio
     then buys the full label back at tablet widths.
     HONEST NAMING (review): `text-overflow: ellipsis` is INERT on this element
     — it is a flex container, and Chrome does not ellipsize a flex container's
     anonymous items. In the 769-782 band the chip CLIPS; it does not paint a
     literal "…". `overflow: hidden` is the declaration doing the work (it also
     zeroes the chip's automatic minimum size, which is why the guard's GR108
     mutation only flips when the WHOLE pair is reverted — each half alone
     measures clean at today's label widths, verified both directions at
     review). The ellipsis is kept as charter-authored intent and as correct
     behaviour the day the chip stops being a flex container; it is documented
     here rather than described as polish it does not deliver. */
  overflow: hidden;
  text-overflow: ellipsis;
}
.billing-chip:hover { text-decoration: none; }
.billing-chip--trial {
  color: var(--cc-blue);
  background: hsl(var(--info-hsl) / 0.1);
  border: 1px solid hsl(var(--info-hsl) / 0.3);
}
.billing-chip--trial:hover { background: hsl(var(--info-hsl) / 0.16); }
.billing-chip--past_due {
  color: var(--cc-red-strong);
  background: hsl(var(--danger-hsl) / 0.1);
  border: 1px solid hsl(var(--danger-hsl) / 0.3);
}
.billing-chip--past_due:hover { background: hsl(var(--danger-hsl) / 0.16); }

/* The current-plan status pill in its warning state — "Past due" must never
   ride the green Recommended skin. */
.plan-rec--due {
  color: var(--warn-strong);
  border-color: hsl(var(--warn-hsl) / 0.4);
  background: hsl(var(--warn-hsl) / 0.1);
}

/* ============================================================================
   GR24 — INSTANCE WORKSPACE v4 (D-02 header + D-03 Overview, screens/02)
   The persistent header (H1 + the two-axis compound pill, mono address + copy,
   Open Studio / Attach domain / the bp CLI disclosure), the collapsible bp-CLI
   card (conduit-driven; the pause sentence is server copy), and the composed
   Overview (golden path + updates + sites cards left; identity / runtime /
   platform / activity card rail right). .status-pill and the domain-checklist
   internals are CONSUMED as-is (GR25/GR31) — no rule of theirs is edited here.
   ========================================================================== */

/* ── header (screens/02) ── */
.detail-head--inst { align-items: flex-start; }
.detail-head-main { display: flex; flex-direction: column; gap: 7px; min-width: 0; }
.detail-title-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; min-width: 0; }
.detail-title-row h1 { margin: 0; font-size: 26px; letter-spacing: -0.02em; }
.detail-head--inst .detail-actions {
  display: flex; align-items: center; gap: 8px;
  flex-wrap: wrap; justify-content: flex-end; flex: 0 0 auto;
}
.detail-url { display: inline-flex; align-items: center; gap: 6px; min-width: 0; max-width: 100%; }
.detail-url-text {
  font-family: var(--mono); font-size: 12.5px; color: var(--muted-text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.inst-cli-toggle { font-family: var(--mono); gap: 6px; }
.inst-cli-caret { font-size: 10px; line-height: 1; display: inline-block; transition: transform var(--t); }
.inst-cli-toggle.is-open { background: var(--muted-surface); }
.inst-cli-toggle.is-open .inst-cli-caret { transform: rotate(180deg); }

/* ── the bp CLI card (disclosed by #inst-cli-toggle) ── */
.inst-lifecycle-actions { margin: 0 0 20px; }
.cli-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: 16px 18px;
}
.cli-card-title { flex: 1; min-width: 160px; font-size: 13px; font-weight: 600; color: var(--muted-text); }
.cli-card .inst-life-head { margin-bottom: 12px; }
.cli-card .inst-life-actions {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(270px, 1fr));
  gap: 10px;
}
.cli-card .inst-life-cli {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 12px; min-width: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--muted-surface);
}
.cli-card .inst-life-verb { flex: 0 0 auto; font-size: 12.5px; }
.cli-card .inst-life-cli .cli-chip { flex: 1; min-width: 0; justify-content: space-between; border: none; background: transparent; padding: 0; }
.cli-card .inst-life-disabled { padding: 8px 12px; border: 1px dashed var(--border); border-radius: var(--radius-sm); }
.cli-card-foot {
  display: flex; align-items: center; gap: 12px;
  margin-top: 14px; padding-top: 14px;
  border-top: 1px solid var(--border);
}
.inst-life-footnote { flex: 1; font-size: 12px; color: var(--muted-text); line-height: 1.5; }

/* ── overview cards (D-03) ── */
.inst-sites-card { padding: 16px 18px; }
.inst-sites-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 10px; }
.inst-sites-head h2 { margin: 0; }
.inst-sites-card .site-row { border: none; box-shadow: none; border-radius: 0; padding: 10px 0; border-top: 1px solid var(--border); }
.inst-sites-card .site-row + .site-row { margin-top: 0; }
.inst-sites-card .empty-state { border: none; padding: 8px 0 4px; }

/* The rail as v4 cards: the aside itself goes transparent; each labelled
   sub-section becomes its own card with a mono eyebrow. */
.detail-rail--cards { background: transparent; border: none; box-shadow: none; padding: 0; }
.detail-rail--cards .rail-group {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: 14px 16px;
}
.detail-rail--cards .rail-group + .rail-group { margin-top: 14px; padding-top: 14px; border-top: none; }
.detail-rail--cards .rail-group-label {
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--dim);
  margin-bottom: 6px;
}

/* Golden-path card head: heading + the verdict subtitle left, the one action right.
   Inside the Overview main column the rhythm comes from .inst-overview, so the
   card's own bottom margin is dropped (no double gap above the Updates card). */
.inst-overview .vf-card { margin-bottom: 0; }
.vf-head-main { flex: 1; min-width: 0; }
.vf-head-main h2 { margin: 0; font-size: 14px; }
.vf-head-main .vf-meta { margin: 3px 0 0; }

@media (max-width: 620px) {
  .detail-head--inst { flex-direction: column; }
  .detail-head--inst .detail-actions { justify-content: flex-start; }
}

/* ==========================================================================
   D-04 — Timeline event-coalescing grammar (gr-p3, GR26, styleguide §07).
   "<thing> × N · cadence · shared verdict" with an expand affordance. The
   tlv-coalesce family EXTENDS the tlv-* feed row — new classes, never sg-*
   imports (the .notice/.sg-banner precedent). Appended per the OC9 tail law.
   ========================================================================== */
.tlv-coalesce { position: relative; }
/* The "× N" count rides the title in mono — repetition reads as a figure. */
.tlv-coalesce-count {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 600;
  color: var(--muted-text);
}
/* "every ~1m for 9m · all reporting health: down" — the §07 meta segment. */
.tlv-coalesce-meta {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12px;
  color: var(--dim);
}
/* "Show all N" / "Collapse" — the §07 link affordance, button-for-a11y. */
.tlv-coalesce-toggle {
  flex: 0 0 auto;
  font: inherit;
  font-size: 12px;
  color: var(--info);
  background: none;
  border: 0;
  padding: 2px 0;
  cursor: pointer;
}
.tlv-coalesce-toggle:hover { text-decoration: underline; }
/* Expanded members sit on an inset rail; the summary row stays the anchor. */
.tlv-coalesce-members {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 10px;
  padding-left: 12px;
  border-left: 2px solid hsl(var(--info-hsl) / 0.25);
}
.tlv-coalesce-members .tlv-row { background: var(--muted-surface); }
@media (max-width: 720px) {
  .tlv-coalesce-meta { white-space: normal; }
}
/* ═══════════════════════════ DOMAIN CHECKLIST (gr-p3-site-detail, v4 rungs)
   The shared per-host DNS/TLS checklist restyled onto the v4 rung-pill grammar
   (v4.dc.html:605-617 + 2025-2033): one bordered host card, the 4 ordered rungs
   as tinted pills (● ok / ✕ failed / ◐ in-flight / · waiting / ● proxied), the
   server's remediation verbatim underneath. ONE component, two mounts — the
   instance rail (#instance-domains) and the site detail (#site-domains) consume
   the same domainChecklistHtml unchanged. Status roles ride the shared token
   families; the informational `proxied` rung reads info (Cloudflare fronting is
   a mode, never a fault). Append-only tail (OC9). */
.dom-card {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  padding: 12px 14px;
}
.dom-card + .dom-card { margin-top: 10px; }
.dom-head { display: flex; align-items: center; gap: 8px; margin-bottom: 9px; min-width: 0; }
.dom-host {
  font-family: var(--mono);
  font-size: 12.5px;
  font-weight: 500;
  color: var(--text);
  overflow-wrap: anywhere;
  min-width: 0;
}
.dom-kind {
  flex: 0 0 auto;
  font-size: 11px;
  color: var(--muted-text);
  border: 1px solid var(--border);
  border-radius: var(--pill);
  padding: 1px 8px;
}
.dom-rungs { display: flex; flex-wrap: wrap; gap: 6px; min-width: 0; }
/* GR65 REVIEW: a rung is `white-space: nowrap` so its glyph + label never break
   mid-phrase — but a FAILED rung also carries a server-authored reason ("No
   A/AAAA record for acme.barkpark.cloud"), and an unbreakable 509px pill inside
   a 340px rail pushed the whole DOCUMENT to scrollWidth 1533 in a 1440 viewport.
   That is a horizontal page scroll on the instance workspace at DESKTOP width,
   in every accent and both themes — not a tablet-only tightness. `max-width:
   100%` bounds the pill to its flex line and the code span alone is allowed to
   wrap (`anywhere`, because the reason ends in an unbreakable hostname); the
   label chrome keeps its nowrap. Measured on the static harness, panel-overview:
   1440 scrollWidth 1533 → 1440, 768 scrollWidth 797 → 768. */
.dom-rung {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 10px;
  border-radius: var(--pill);
  border: 1px solid var(--border);
  background: var(--muted-surface);
  color: var(--muted-text);
  font-size: 11px;
  font-weight: 600;
  white-space: nowrap;
  max-width: 100%;
  min-width: 0;
}
.dom-rung-glyph { font-size: 9px; line-height: 1; }
.dom-rung-code {
  font-family: var(--mono); font-weight: 400; color: var(--dim);
  min-width: 0;
  white-space: normal;
  overflow-wrap: anywhere;
  text-align: left; /* a wrapped reason must read as a paragraph, not a centred blob */
}
.dom-rung--ok { color: var(--ok-strong); background: var(--ok-soft); border-color: hsl(var(--ok-hsl) / 0.3); }
.dom-rung--ok .dom-rung-glyph { color: var(--ok); }
.dom-rung--failed { color: var(--danger); background: var(--danger-soft); border-color: hsl(var(--danger-hsl) / 0.3); }
.dom-rung--active { color: var(--info); background: var(--info-soft); border-color: hsl(var(--info-hsl) / 0.3); }
.dom-rung--proxied { color: var(--info); background: hsl(var(--info-hsl) / 0.08); border-color: hsl(var(--info-hsl) / 0.25); }
/* The server's remediation line — amber note, rendered verbatim (the SPA never
   invents fix text). */
.dom-note {
  margin-top: 8px;
  padding: 7px 10px;
  border: 1px solid hsl(var(--warn-hsl) / 0.3);
  border-radius: var(--radius-sm);
  background: var(--warn-soft);
  color: var(--warn-strong);
  font-size: 12px;
  line-height: 1.5;
}
.dom-meta { margin-top: 7px; font-size: 11px; color: var(--dim); }
/* The site-detail Domains section: painted only when the site has attached
   domains (the mount leaves it empty otherwise — no empty shell, D17). */
.site-domains { margin-top: 22px; }
/* The v4 site-detail headline: domain + honest state chip on one line. */
.site-title { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

/* ------------------------------------------------ OPERATOR CONSOLE (gr-p5)
   GR39/GR50. The four cards ride the shared GR33 .set-* anatomy (.set-section /
   .set-h / .set-purpose / .set-row* / .set-empty) — these rules add only what
   that vocabulary has no word for: the read-only warm-pool metric, the staging
   gate line, and the rollout's footnote. Tokens only; every fg/bg pairing here
   (--text / --muted-text / --dim on --surface) is an existing CONTRAST_PAIRS
   entry, so the console needs no new pair (GR33 contrast law). */

/* The read-only link that replaces the brake button on the team-scoped Fleet
   banner (GR49). Inherits the notice's own --text, so it adds no contrast pair. */
.notice-link {
  margin-left: auto;
  color: inherit;
  font-weight: 600;
  white-space: nowrap;
}

/* Warm pool: ONE number. No bar and no percentage — there is no denominator
   anywhere in the control plane to draw one against (GR50). */
.op-metric { display: flex; align-items: baseline; gap: var(--space-2); }
.op-metric-v {
  font-size: 30px;
  font-weight: 650;
  line-height: 1;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.op-metric-k { font-size: var(--text-sm); color: var(--muted-text); }
.op-metric-cap {
  margin: var(--space-3) 0 0;
  font-size: var(--text-sm);
  line-height: 1.55;
  color: var(--muted-text);
}

/* The staging-gate sentence above the canary rows — a pill + the honest state. */
.op-gate {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0 0 var(--space-3);
  font-size: var(--text-sm);
  color: var(--text);
}

/* The rollout's own vocabulary, stated once under the rows. */
.op-foot {
  margin: var(--space-4) 0 0;
  padding-top: var(--space-3);
  border-top: 1px solid var(--border);
  font-size: var(--text-xs);
  line-height: 1.6;
  color: var(--dim);
}

/* ==================================================== ACCOUNT MODAL + 2FA
   gr-p5-account-2fa (GR54/GR56/GR57) — the account modal recomposed in place:
   identity row with password on demand, sessions, two-factor.

   GR57 WIDTH: the design measures ~518px, but `.modal-card { max-width: 420px }`
   is the SHARED base for 15+ modal bodies (env editor, token creation, provider
   picker …) and widening it fattens every one. Follow the live :has() precedent
   the command palette set — a SECOND, scoped rule keyed off a class unique to
   this body. The base is never touched. */
.modal-root:has(.am-modal) .modal-card { max-width: 520px; }

/* GR57 LINKS: `.btn-link` colours itself var(--primary), which IS the
   user-selectable accent (redefined across five identities) — the design's
   links are a FIXED blue. --info resolves to --cc-blue and is declared ONLY in
   :root / [data-theme="dark"], with zero [data-bp-theme] overrides, so these
   stay blue under Ember exactly as drawn. --primary is never repurposed. */
.am-link { color: var(--info); font-size: var(--text-sm); white-space: nowrap; }

/* Identity row: avatar · name over "email · owner of Team" · password link. */
.am-identity { display: flex; align-items: center; gap: 12px; margin: 14px 0 4px; }
.am-face {
  flex: 0 0 auto; width: 38px; height: 38px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--muted-surface); color: var(--text);
  font-size: 15px; font-weight: 600;
}
.am-who { min-width: 0; flex: 1 1 auto; }
.am-name { font-size: 15px; font-weight: 600; line-height: 1.3; }
.am-line {
  font-size: 12px; color: var(--muted-text); font-family: var(--mono);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Progressive disclosure — the form ships in the DOM `hidden` so its ids never
   move; only visibility changes. */
.am-pw { margin-top: 12px; }
/* Section header with a right-aligned affordance (revoke-all, the 2FA badge). */
.am-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.am-head .modal-section { margin-bottom: 8px; }

/* ---- two-factor ---- */
.a2f-badge { height: 20px; padding: 0 8px; font-size: 11px; }
.a2f-badge--on { background: var(--success); color: var(--primary-fg); }
.a2f-badge--off { background: var(--muted-surface); color: var(--muted-text); }
.a2f-panel { margin-top: 2px; }
.a2f-off-line, .a2f-on-line { font-size: 13px; color: var(--muted-text); margin: 0 0 10px; }
.a2f-error { margin-bottom: 10px; }
.a2f-on-actions { display: flex; gap: 18px; align-items: center; }
.a2f-off-link { color: var(--danger); }

/* Enroll: QR plate beside the manual-entry column. When the URI overruns the
   encoder's capacity the QR is absent and the manual column takes the row
   alone — never a broken or blank QR box. */
.a2f-enroll { display: flex; gap: 16px; align-items: flex-start; }
.a2f-enroll--noqr { display: block; }
.a2f-qr { flex: 0 0 auto; width: 118px; height: 118px; border-radius: 10px; overflow: hidden; }
/* Literal black/white, not tokens: a scanner needs true contrast, and a themed
   QR that reads in light mode and fails in dark is a silent lockout. */
.a2f-qr-svg { display: block; width: 100%; height: 100%; }
.a2f-manual { flex: 1 1 auto; min-width: 0; }
.a2f-manual p { font-size: 13px; color: var(--muted-text); margin: 0 0 10px; }
.a2f-secret {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 8px 10px; border: 1px solid var(--border); border-radius: 8px;
  background: var(--surface); margin-bottom: 10px;
}
.a2f-secret-value { font-family: var(--mono); font-size: 12.5px; letter-spacing: 0.04em; overflow: hidden; text-overflow: ellipsis; }
.a2f-confirm-row { display: flex; gap: 10px; align-items: center; }
.a2f-otp {
  width: 108px; font-family: var(--mono); letter-spacing: 0.22em; text-align: left;
}

/* One-shot recovery sheet — 8 chips, 4 across. */
.a2f-warn { font-size: 13px; color: var(--warn); margin: 0 0 12px; }
.a2f-codes { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
.a2f-code {
  display: block; text-align: center; padding: 8px 4px;
  border: 1px solid var(--border); border-radius: 8px; background: var(--surface);
  font-family: var(--mono); font-size: 12.5px;
}
.a2f-codes-actions { display: flex; align-items: center; justify-content: flex-end; gap: 16px; margin-top: 14px; }
.a2f-saved { flex: 0 0 auto; }

/* GR56: `.modal-x` carries data-close, so a pin DEADENS it. An author rule beats
   the UA sheet regardless of specificity, so `.modal-x { display: grid }` would
   keep a DEAD × visible — this restores [hidden] for the pinned sheet.
   The same applies to every [data-close] a BODY renders (the account footer's
   "Close" is a `.btn`, i.e. `display: inline-flex`): same delegated handler,
   same pin, same need for [hidden] to actually hide. */
.modal-x[hidden] { display: none; }
#modal-body [data-close][hidden] { display: none; }

/* ═══ MVP-0 Personal Dev Fleet (PDF-D84/D88/D92) — the fleet card, support
   nesting, presence chips and the BYO-key step. Appended at the tail per the
   OC9 slice law; the generated token block above is machine-owned. ═══ */

/* The fleet card on a main's Overview. */
.fleet-support-card { display: flex; flex-direction: column; gap: 12px; }
.fleet-support-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.fleet-support-head h2 { margin: 0; }
.fleet-support-empty p { margin: 0 0 10px; color: var(--muted-text); font-size: 13.5px; }
.fleet-support-empty p.dim { margin: 0; }

/* One nested support row inside the card. */
.fleet-support-row { border-top: 1px solid var(--border); padding: 12px 0 4px; display: flex; flex-direction: column; gap: 10px; }
.fleet-support-row-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.fleet-support-name { font-weight: 600; color: var(--text); text-decoration: none; }
.fleet-support-name:hover { text-decoration: underline; }
.fleet-support-slot { display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.fleet-support-theater { padding: 2px 0 6px; }

/* Presence chip — status vocabulary idle|working|blocked|provisioning, offline
   computed server-side, "online" DERIVED (status !== offline). One static class
   per state (__css_check E3). */
.fleet-presence {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; font-weight: 600; padding: 2px 8px;
  border-radius: 999px; border: 1px solid var(--border);
}
.fleet-presence::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.fleet-presence--online { color: var(--ok); background: var(--ok-soft); border-color: transparent; }
.fleet-presence--working { color: var(--info); background: var(--info-soft); border-color: transparent; }
.fleet-presence--blocked { color: var(--warn); background: var(--warn-soft); border-color: transparent; }
.fleet-presence--provisioning { color: var(--info); background: var(--info-soft); border-color: transparent; }
.fleet-presence--offline { color: var(--danger); background: var(--danger-soft); border-color: transparent; }
.fleet-presence--unknown { color: var(--muted-text); }
.fleet-presence-cap { font-family: var(--mono); font-size: 11.5px; color: var(--muted-text); }

/* BYO model key — a visible named step, never a hidden afterthought (PDF-D88). */
.support-key-step { display: flex; flex-direction: column; gap: 6px; padding: 2px 0 6px; }
.support-key-title { font-weight: 600; font-size: 13.5px; }
.support-key-sub { margin: 0; color: var(--muted-text); font-size: 12.5px; }

/* #fleet nesting: a support rides indented under its main (PDF-D92). */
.fleet-row--support { margin-left: 28px; }
.fleet-role-chip {
  display: inline-block; margin-left: 8px; padding: 1px 7px;
  font-size: 10.5px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--muted-text); border: 1px solid var(--border); border-radius: 999px;
  vertical-align: 1px;
}

/* MVP-0 offload: file an order to a support + watch it (pdf-mvp0-offload-spa).
   The action + the live watch ladder (the SHARED .new-steps grammar) sit under
   a live support's BYO-key step. Appended at the app.css tail only (OC9). */
.offload-action { display: flex; flex-direction: column; gap: 8px; padding: 4px 0 2px; }
.offload-slot:empty { display: none; }
.offload-watch { display: flex; flex-direction: column; gap: 8px; margin-top: 4px; padding: 10px 12px; border: 1px solid var(--border); border-radius: 10px; }
.offload-watch-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.offload-watch-title { font-weight: 600; font-size: 13px; color: var(--text); }
