/* ============================================================
   REVERE PORTAL · v0.3 hi-fi
   tokens · type · components
   ============================================================ */

:root {
  color-scheme: light;

  /* Colors — from revere design system */
  --paper: #FBFBF8;
  --paper-2: #F4F4EE;
  --paper-3: #ECEBE3;
  --bg-canvas: #FBFBF8;
  --bg-surface: #FFFFFF;
  --ink: #18181B;
  --ink-2: #2A2F38;
  --ink-soft: #3F4248;
  --fg-1: #18181B;
  --fg-2: #5A5D63;
  --fg-3: #8A8D93;
  --note: #6B6E73;
  --line-1: #D7D6CE;
  --line-2: #C2C0B6;
  --line-hairline: #E6E5DD;
  --gray-1: #E2E1DA;
  --gray-2: #C7C6BE;
  --gray-3: #A8A79F;
  /* Tool-tile surface — the warm paper tone, on-theme and light on the white
     hub panel (was --gray-1, which read as a heavy grey slab). */
  --rv-tool-tile-bg: var(--paper-2);

  /* cyan ramp */
  --color-sky: #2A6FDB;
  --color-cyan: #1E8FE0;
  --color-cyan-soft: #5BB8FF;
  --color-cyan-dim: #1A6FA8;
  --color-cyan-wash: #EDF7FF;
  --color-cyan-edge: #B5DCF7;

  /* status — standardized design-system tokens (Phase 9.3.0). Values copied
     from design_system/revere/colors_and_type.css (--status-*); base = fill/dot,
     -wash = soft badge/banner bg, -deep = readable text on -wash / on white. */
  --ok: #15A06B;          /* --status-success */
  --ok-wash: #E3F4EC;     /* --status-success-soft */
  --ok-deep: #0B6446;     /* --status-success-deep */
  --warn: #D8941A;        /* --status-warning */
  --warn-wash: #FAF0DA;   /* --status-warning-soft */
  --warn-deep: #885806;   /* --status-warning-deep */
  --bad: #DD3B4B;         /* --status-error */
  --bad-wash: #FBE6E9;    /* --status-error-soft */
  --bad-deep: #9E1F2C;    /* --status-error-deep */
  --info: #2E8BE6;        /* --status-info */
  --info-wash: #E4F0FB;   /* --status-info-soft */
  --info-deep: #1B5FB8;   /* --status-info-deep */
  --idle: #8B939B;        /* --status-neutral */
  --idle-wash: #EDF0F3;   /* --status-neutral-soft */
  --idle-deep: #5C6470;   /* --status-neutral-deep */
  /* Phase 8.28.4 — "dependent" status (a field that auto-resolves once its
     driver is fixed + the automation re-runs). Violet so it reads distinctly
     from the amber review / blue unimplemented / green saved / grey idle
     states already in use. Mapped to the design-system violet profile. */
  --dep: #9B5BE0;         /* --profile-violet */
  --dep-wash: #F2EAFC;    /* --profile-violet-soft */
  --dep-deep: #5A2C94;    /* --profile-violet-deep */

  /* brushed metal — design-system cool blue-grey + horizontal striations + bevel */
  --metal-mid: rgb(168, 176, 184);
  --metal-light: rgb(217, 221, 226);
  --metal-lighter: rgb(232, 236, 242);
  --metal-edge: rgb(140, 148, 158);

  --metal-brushed:
    /* top highlight + bottom shade */
    linear-gradient(180deg, rgba(255,255,255,0.55) 0%, rgba(255,255,255,0) 18%, rgba(255,255,255,0) 78%, rgba(0,0,0,0.10) 100%),
    /* horizontal brushed striations only (no perpendicular grain — addresses pattern-overlap feedback) */
    repeating-linear-gradient(90deg,
      rgba(255,255,255,0.05) 0px,
      rgba(255,255,255,0.05) 1px,
      rgba(0,0,0,0.04) 1px,
      rgba(0,0,0,0.04) 2px),
    /* base cool-grey gradient (from design system) */
    linear-gradient(180deg, rgb(255,255,255) 0%, rgb(217,221,226) 38%, rgb(168,176,184) 100%);

  --metal-brushed-r:
    linear-gradient(90deg, rgba(255,255,255,0.5) 0%, rgba(255,255,255,0) 18%, rgba(255,255,255,0) 82%, rgba(0,0,0,0.10) 100%),
    repeating-linear-gradient(0deg,
      rgba(255,255,255,0.07) 0px,
      rgba(255,255,255,0.07) 1px,
      rgba(0,0,0,0.05) 1px,
      rgba(0,0,0,0.05) 2px),
    repeating-linear-gradient(90deg,
      rgba(255,255,255,0.04) 0px,
      rgba(255,255,255,0.04) 1px,
      transparent 1px,
      transparent 3px),
    linear-gradient(90deg, rgb(255,255,255) 0%, rgb(217,221,226) 38%, rgb(168,176,184) 100%);

  /* design-system bevel */
  --bevel-chrome:
    inset 0 1px 0 rgba(255,255,255,0.85),
    inset 0 -2px 0 rgba(0,0,0,0.18),
    0 8px 24px rgba(15,20,25,0.06);

  --font-display: 'Chakra Petch', system-ui, sans-serif;
  --font-body: 'Questrial', system-ui, sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, monospace;

  --radius: 4px;
  /* Phase 17.8.2 — rail hover sweep (brushed default: dark gradient + fine
     vertical striations). finishes.css overrides this per finish. */
  --rail-hover-fill:
    repeating-linear-gradient(90deg,
      rgba(255,255,255,.05) 0px, rgba(255,255,255,.05) 1px,
      rgba(0,0,0,.10) 1px, rgba(0,0,0,.10) 2px),
    linear-gradient(90deg, var(--ink) 0%, rgba(24,24,27,.92) 100%);
  --shadow-1: 0 1px 0 var(--line-hairline), 0 4px 14px rgba(20,18,12,.04);
  --shadow-2: 0 1px 0 var(--line-1), 0 14px 40px -10px rgba(20,18,12,.10), 0 4px 10px rgba(20,18,12,.05);

  /* canonical REVERE lockup tracking — half the font size between glyphs */
  --wordmark-tracking: 0.5em;

  /* motion (Phase 7.1.0 — mirrors design_system/revere motion scale;
     Phase 14.4.0 — reconciled the full canonical scale into production) */
  --dur-instant: 80ms;
  --dur-fast: 150ms;
  --dur-base: 200ms;          /* design_system --dur-base */
  --dur-slow: 250ms;          /* design_system --dur-slow */
  --dur-deliberate: 400ms;
  --ease-out: cubic-bezier(.22, 1, .36, 1);
  --ease-in-out: cubic-bezier(.65, 0, .35, 1);    /* design_system --ease-in-out */
  --ease-spring: cubic-bezier(.34, 1.36, .64, 1); /* design_system --ease-spring */

  /* breakpoints (Phase 14.1.0 — mirrors design_system/revere --bp-* scale).
     @media can't read a custom property in its condition, so these are the
     canonical record; every @media writes the same literal px. App collapses
     to its mobile shell below --bp-md. */
  --bp-sm: 640px;
  --bp-md: 900px;
  --bp-lg: 1024px;
  --bp-xl: 1280px;

  /* focus ring — the only ambient cyan without user interaction (on brand) */
  --glow-cyan-ring: 0 0 0 3px var(--color-cyan-wash), 0 0 0 1px var(--color-cyan);

  /* ROYGBIV avatar palette — brand-tuned, muted for legibility on white text (Phase 7.1.0) */
  /* Avatar / profile hues — standardized to the design-system --profile-*
     spectrum (Phase 9.3.0). 7 ROYGBIV keys; `yellow` maps to --profile-amber. */
  --av-red: #E0455A;      /* --profile-red */
  --av-orange: #E07A3D;   /* --profile-orange */
  --av-yellow: #D89A1E;   /* --profile-amber */
  --av-green: #3DA85F;    /* --profile-green */
  --av-blue: #4A74E0;     /* --profile-blue */
  --av-indigo: #6A5BE0;   /* --profile-indigo */
  --av-violet: #9B5BE0;   /* --profile-violet */

  /* Phase 14.8.0 — dark-mode seam tokens. These give the few "inverted" / hard-
     coded surfaces a name so the [data-theme="dark"] block can re-pitch them.
     Light values below preserve the pre-14.8.0 appearance exactly. */
  --on-ink: #FFFFFF;            /* text/icon on a var(--ink) chip/button/tag */
  --card-ink-bg: var(--ink);    /* .card.ink emphasis panel — stays a dark panel */
  --card-ink-fg: #FFFFFF;
  --card-ink-border: #2A2F38;
  --card-ink-soft-bg: #1E2128;  /* .card.ink-soft panel */
  --card-ink-soft-fg: #E2E4E7;
  --card-ink-soft-mono: #9EA1A8;
}

/* Shared accent for native checkboxes/radios (Phase 14.9.0). Consolidates what
   used to be ~10 scattered inline accent-color declarations into one rule so
   new checkboxes are correct by default. */
input[type="checkbox"],
input[type="radio"] {
  accent-color: var(--color-cyan);
}

/* ============================================================
   DARK THEME (Phase 14.8.0)
   Reverse-engineered from the production light theme: same REVERE
   palette (surgical cyan, status tri-tones, brushed chrome), inverted
   in luminance. One cool-graphite hue; lightness climbs with elevation.
   The app is ~fully tokenized, so re-declaring the color/elevation
   tokens here re-themes nearly everything; a few component overrides
   below catch the surfaces that bake in "light".
   ------------------------------------------------------------
   Status tri-tone flip: the token *names* stay, but in dark the -wash
   (a pale text background in light) becomes a DEEP translucent tint,
   and -deep (dark text-on-pale in light) becomes a LIGHT tint so it
   stays readable on the now-dark wash. Base fills carry, desaturated.
   ============================================================ */
html[data-theme="dark"] {
  color-scheme: dark;

  /* surfaces — climb lighter with elevation */
  --paper: #1A1D20;
  --paper-2: #1F2227;
  --paper-3: #24272C;
  --bg-canvas: #131517;
  --bg-surface: #1C1F22;

  /* text — primary near-white (not pure), 4 levels preserved */
  --ink: #E9EAEC;
  --ink-2: #C8CBD0;
  --ink-soft: #AEB2B8;
  --fg-1: #E9EAEC;
  --fg-2: #9A9EA5;
  --fg-3: #6E7278;
  --note: #82868C;

  /* borders carry structure on dark (shadows are weak) */
  --line-1: #2E3236;
  --line-2: #3A3E43;
  --line-hairline: #25282C;
  --gray-1: #2A2E33;
  --gray-2: #3A3E43;
  --gray-3: #4C5158;
  /* Dark: keep the elevated card tone that separates from the darker panel. */
  --rv-tool-tile-bg: var(--gray-1);

  /* cyan ramp — keep the brand fill; flip the text/wash to read on dark */
  --color-sky: #4C8BE6;
  --color-cyan: #2A97E6;
  --color-cyan-soft: #7CC4FF;
  --color-cyan-dim: #8CC9FF;          /* cyan TEXT flips lighter */
  --color-cyan-wash: rgba(43,143,222,.16);
  --color-cyan-edge: rgba(91,184,255,.30);

  /* status tri-tones — base carries, -wash → deep translucent, -deep → light */
  --ok: #2BB47C;       --ok-wash: rgba(33,176,123,.16);    --ok-deep: #6FE0B0;
  --warn: #E0A23A;     --warn-wash: rgba(216,148,26,.16);  --warn-deep: #F2C879;
  --bad: #E85565;      --bad-wash: rgba(221,59,75,.18);    --bad-deep: #F4A0A9;
  --info: #4CA0EC;     --info-wash: rgba(46,139,230,.16);  --info-deep: #9CC8F2;
  --idle: #9AA1A9;     --idle-wash: rgba(139,147,155,.16); --idle-deep: #C2C7CE;
  --dep: #B07BE6;      --dep-wash: rgba(155,91,224,.18);   --dep-deep: #D2B0F0;

  /* inverted "ink" seams — chips/tags/buttons invert to a light fill with dark
     text; the large emphasis panels stay elevated DARK surfaces (not white). */
  --on-ink: #16181B;
  --card-ink-bg: #0F1113;          /* deepest — reads as inset emphasis */
  --card-ink-fg: #E9EAEC;
  --card-ink-border: rgba(255,255,255,.10);
  --card-ink-soft-bg: #24272C;     /* elevated emphasis panel */
  --card-ink-soft-fg: #E9EAEC;
  --card-ink-soft-mono: #9AA0A8;

  /* brushed metal → gunmetal: dark base gradient, softened highlights */
  --metal-mid: rgb(42, 46, 51);
  --metal-light: rgb(54, 59, 65);
  --metal-lighter: rgb(64, 70, 77);
  --metal-edge: rgb(28, 31, 35);

  --metal-brushed:
    linear-gradient(180deg, rgba(255,255,255,0.08) 0%, rgba(255,255,255,0) 18%, rgba(255,255,255,0) 78%, rgba(0,0,0,0.28) 100%),
    repeating-linear-gradient(90deg,
      rgba(255,255,255,0.035) 0px,
      rgba(255,255,255,0.035) 1px,
      rgba(0,0,0,0.10) 1px,
      rgba(0,0,0,0.10) 2px),
    linear-gradient(180deg, rgb(54,59,65) 0%, rgb(38,42,47) 42%, rgb(26,29,33) 100%);

  --metal-brushed-r:
    linear-gradient(90deg, rgba(255,255,255,0.07) 0%, rgba(255,255,255,0) 18%, rgba(255,255,255,0) 82%, rgba(0,0,0,0.28) 100%),
    repeating-linear-gradient(0deg,
      rgba(255,255,255,0.05) 0px,
      rgba(255,255,255,0.05) 1px,
      rgba(0,0,0,0.12) 1px,
      rgba(0,0,0,0.12) 2px),
    repeating-linear-gradient(90deg,
      rgba(255,255,255,0.03) 0px,
      rgba(255,255,255,0.03) 1px,
      transparent 1px,
      transparent 3px),
    linear-gradient(90deg, rgb(54,59,65) 0%, rgb(38,42,47) 42%, rgb(26,29,33) 100%);

  --bevel-chrome:
    inset 0 1px 0 rgba(255,255,255,0.10),
    inset 0 -2px 0 rgba(0,0,0,0.40),
    0 8px 24px rgba(0,0,0,0.40);

  /* elevation leans on a ring + minimal depth (drop shadows don't read on dark) */
  --shadow-1: 0 0 0 1px var(--line-1), 0 2px 8px rgba(0,0,0,.32);
  --shadow-2: 0 0 0 1px var(--line-1), 0 18px 48px -12px rgba(0,0,0,.55), 0 6px 16px rgba(0,0,0,.40);
}

/* Dark-mode component catches — surfaces whose light values are baked into the
   rule (not a token) and don't read once the palette flips. */
html[data-theme="dark"] .btn.ink:active { background: #C7CACE; }
html[data-theme="dark"] .btn.bad:active { background: rgba(221,59,75,.30); }

* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { background: var(--bg-canvas); }
body {
  font-family: var(--font-body);
  color: var(--fg-1);
  line-height: 1.5;
  padding: 0;
  margin: 0;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
/* Phase 3.8 — body padding removed entirely. The `.doc` design-system
   reference page also drops its 48/28/96 padding; individual templates
   supply their own gutters (e.g. hub.html's `padding: 26px 32px` on the
   inner content div). The app fills the viewport edge-to-edge. */
.doc { max-width: 1640px; margin: 0 auto; min-width: 0; }

/* ===================== DOC INTRO ===================== */
.doc-intro { padding-bottom: 32px; border-bottom: 1.5px solid var(--ink); margin-bottom: 56px; }
.doc-intro h1.display {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 56px;
  line-height: 0.96;
  letter-spacing: -0.01em;
  margin: 14px 0 22px;
  color: var(--ink);
}
.lede { max-width: 920px; font-size: 16px; color: var(--ink-soft); }
.lede em { color: var(--ink); font-style: normal; font-weight: 500; }
.lede code { font-family: var(--font-mono); font-size: 12px; background: var(--paper-2); padding: 1px 5px; }

.toc {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(290px, 1fr));
  gap: 2px 28px;
  margin: 32px 0 0;
  font-family: var(--font-mono);
  font-size: 11px;
}
.toc a {
  color: var(--ink); text-decoration: none;
  display: flex; gap: 10px;
  padding: 5px 0;
  border-bottom: 1px dotted var(--gray-2);
  letter-spacing: 0.04em;
}
.toc a:hover { color: var(--color-cyan); border-bottom-color: var(--color-cyan-edge); }
.toc .n { color: var(--note); }

.doc-foot {
  margin-top: 80px; padding-top: 24px;
  border-top: 0.5px solid var(--gray-2);
  display: flex; justify-content: space-between;
  font-family: var(--font-mono); font-size: 11px;
  color: var(--note); letter-spacing: 0.06em;
}

/* ===================== PAGE SECTIONS ===================== */
.page {
  padding: 64px 0 24px;
  border-top: 1px solid var(--gray-1);
}
.page:first-of-type { border-top: none; padding-top: 24px; }
.page-head { display: flex; align-items: center; gap: 18px; margin-bottom: 6px; flex-wrap: wrap; }
.page-num { font-family: var(--font-mono); font-size: 11px; color: var(--note); letter-spacing: 0.16em; flex-shrink: 0; }
.page-head h2 {
  font-family: var(--font-display);
  font-size: 26px; font-weight: 600;
  letter-spacing: -0.01em; color: var(--ink);
  flex-shrink: 0;
}
.page-route { font-family: var(--font-mono); font-size: 11px; color: var(--note); letter-spacing: 0.04em; flex: 1; min-width: 200px; }
.page-roles { font-family: var(--font-mono); font-size: 10px; color: var(--note); text-transform: uppercase; letter-spacing: 0.1em; margin-bottom: 22px; display: flex; flex-wrap: wrap; gap: 4px; }
.page-roles .r { background: var(--paper-2); border: 0.5px solid var(--gray-2); padding: 2px 8px; white-space: nowrap; }

.page-body { display: flex; flex-direction: column; gap: 24px; align-items: stretch; }

.thoughts {
  border-top: 1px solid var(--gray-2);
  padding-top: 18px;
  font-size: 13px; color: var(--ink-soft);
  display: grid; grid-template-columns: 220px 1fr;
  gap: 32px;
  align-items: start;
}
.thoughts h3 {
  font-family: var(--font-mono); font-size: 10px;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--note); font-weight: 500;
  align-self: start;
}
.thoughts ul {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 14px 28px;
}
.thoughts li { padding-left: 14px; position: relative; line-height: 1.5; }
.thoughts li::before {
  content: '▸'; position: absolute; left: 0; top: 0;
  color: var(--color-cyan); font-family: var(--font-mono); font-size: 11px;
}
.thoughts li b { color: var(--ink); font-weight: 600; }
.thoughts li code, .thoughts li .mono { font-family: var(--font-mono); font-size: 11.5px; background: var(--paper-2); padding: 1px 5px; color: var(--ink); }

/* ===================== FRAME (the hi-fi mock container) ===================== */
.frame {
  position: relative;
  background: var(--bg-surface);
  border: 1px solid var(--line-1);
  box-shadow: var(--shadow-2);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  width: 100%;
  border-radius: 2px;
}
.frame.ratio-16x9 { aspect-ratio: 16/9; }
.frame.ratio-16x10 { aspect-ratio: 16/10; }
.frame.ratio-4x3 { aspect-ratio: 4/3; }
.frame.ratio-3x2 { aspect-ratio: 3/2; }
.frame.ratio-5x3 { aspect-ratio: 5/3; }

/* corner brackets */
.frame::before, .frame::after,
.frame > .brackets::before, .frame > .brackets::after {
  content: ''; position: absolute; width: 11px; height: 11px;
  border: 1.2px solid var(--ink); pointer-events: none;
}
.frame::before { top: -1px; left: -1px; border-right: none; border-bottom: none; }
.frame::after { top: -1px; right: -1px; border-left: none; border-bottom: none; }
.frame > .brackets::before { bottom: -1px; left: -1px; border-right: none; border-top: none; }
.frame > .brackets::after { bottom: -1px; right: -1px; border-left: none; border-top: none; }

/* ===================== APP SHELL ===================== */
.shell { display: flex; flex: 1; min-height: 0; background: var(--bg-canvas); }

/* expanding left rail */
.rail {
  position: sticky;
  top: 0;
  align-self: flex-start;
  /* Phase 18.0.0 — under CSS `zoom` the shell head script sets --rv-fill-h to the
     real viewport height so the full-height rail doesn't stretch past it. */
  height: var(--rv-fill-h, 100vh);
  width: 56px;
  background: var(--metal-brushed);
  border-right: 0.5px solid var(--metal-edge);
  box-shadow: var(--bevel-chrome);
  display: flex; flex-direction: column;
  padding: 0;
  flex-shrink: 0;
  overflow-y: auto;
  /* Phase 17.8.4 — `overflow-y: auto` forces the unspecified overflow-x from
     `visible` to `auto`, so any transient horizontal overflow (labels revealed
     mid width-transition, hover transforms) flashes a horizontal scrollbar.
     `clip` forbids scrolling on the axis entirely. See also Phase 3.83. */
  overflow-x: clip;
  transition: width var(--dur-base) var(--ease-out);
  z-index: 5;
}
.rail.wide { width: 168px; }
/* Phase 7.3.0 — logo centered in the header band (top of rail → divider line),
   clickable to the hub, REVERE enlarged toward the divider width when expanded.
   Band height matches the content statusbar (~36.5px: 9px padding ×2 + 18px icon
   + 0.5px border) so the first rail divider lines up with the statusbar's bottom
   edge. The first divider's top margin is zeroed (the band owns the spacing) so
   REV reads as centered between the screen top and the actual divider line —
   section dividers below keep their default margin. */
.rail .rail-logo {
  display: flex; align-items: center; justify-content: center;
  height: 37px; margin: 0; padding: 0 8px;
  font-family: var(--font-display); font-weight: 700;
  letter-spacing: 0.16em; font-size: 12px;
  color: var(--ink); text-decoration: none;
  transition: opacity var(--dur-fast) var(--ease-out);
}
.rail .rail-logo:hover { opacity: 0.6; }
.rail.wide .rail-logo { justify-content: flex-start; padding: 0 14px; font-size: 17px; letter-spacing: var(--wordmark-tracking); }
.rail .rail-logo + .rail-divider { margin-top: 0; }
.rail .rail-divider { height: 1px; background: var(--gray-3); margin: 6px 10px 10px; opacity: .55; }
.rail .rail-group {
  font-family: var(--font-mono); font-size: 8.5px;
  letter-spacing: 0.18em; color: var(--note);
  padding: 0 14px 4px;
  text-transform: uppercase;
  display: none;
}
.rail.wide .rail-group { display: block; }
.rail .rail-item {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 14px;
  font-family: var(--font-mono); font-size: 10px;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--ink-soft);
  cursor: default;
  position: relative;
  text-decoration: none; /* rail items are <a>; no underline (incl. expanded labels) */
}
.rail:not(.wide) .rail-item { justify-content: center; padding: 7px 0; }
.rail .rail-item.on { color: var(--ink); background: rgba(255,255,255,.4); box-shadow: inset 2px 0 0 var(--color-cyan); }
.rail .rail-item:not(.on):hover { background: rgba(255,255,255,.35); color: var(--ink); }
.rail .rail-icn {
  width: 14px; height: 14px; flex-shrink: 0;
  border: 1px solid var(--ink-soft);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 9px;
  background: rgba(255,255,255,.5);
}
.rail .rail-item.on .rail-icn { border-color: var(--ink); background: var(--ink); color: var(--on-ink); }
.rail .rail-label { display: none; }
.rail.wide .rail-label { display: inline; }
.rail .rail-badge {
  font-family: var(--font-mono); font-size: 9px;
  color: var(--color-cyan-dim); display: none;
}
.rail.wide .rail-badge { display: inline; }
.rail .rail-spacer { flex: 1; }
.rail .rail-me {
  display: flex; align-items: center; gap: 4px;
  padding: 8px 14px; border-top: 0.5px solid var(--gray-3);
  font-family: var(--font-mono); font-size: 10px; color: var(--ink);
  letter-spacing: 0.08em; text-transform: uppercase;
  background: rgba(255,255,255,.2);
}
/* Phase 17.8.4 — rail-style hover (background shift, no transform). The row
   previously carried .panel-anim, whose hover scale(1.015) bled past the rail's
   clipped edge and blinked a horizontal scrollbar between profile and sign out. */
.rail .rail-me { transition: background var(--dur-base) var(--ease-out); }
.rail .rail-me:hover { background: rgba(255,255,255,.35); }
.rail:not(.wide) .rail-me { justify-content: center; padding: 8px 0; }
.rail .rail-me-av {
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--ink); color: var(--on-ink);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 8px; font-weight: 600; font-family: var(--font-mono);
  flex-shrink: 0;
}
.rail .rail-me-name { display: none; }
.rail.wide .rail-me-name { display: flex; flex-direction: column; gap: 1px; line-height: 1.05; }
.rail .rail-me-name small { font-size: 8px; color: var(--note); letter-spacing: 0.12em; }

/* metal strip — the signature divider */
.metal-strip {
  height: 6px;
  background: var(--metal-brushed-r);
  border-top: 0.5px solid var(--metal-edge);
  border-bottom: 0.5px solid var(--metal-edge);
  box-shadow: var(--bevel-chrome);
  position: relative;
  overflow: hidden;
}
.metal-strip::before {
  content: ''; position: absolute; inset: 0;
  background-image: repeating-linear-gradient(90deg, rgba(0,0,0,.07) 0 1px, transparent 1px 4px);
  opacity: .6;
}

/* top status bar — sits inside body just below frame top */
.statusbar {
  /* Phase 18.1.0 — pin the breadcrumb bar to the top while the page
     scrolls (document scroll; mirrors the rail's sticky top:0). */
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: center; gap: 16px;
  padding: 9px 22px;
  background: var(--paper);
  border-bottom: 0.5px solid var(--line-hairline);
  font-family: var(--font-mono); font-size: 10px;
  letter-spacing: 0.1em; color: var(--note);
  flex-shrink: 0;
  white-space: nowrap;
}
.statusbar .sb-tenant {
  display: inline-flex; align-items: center; gap: 8px;
  color: var(--ink);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-out), font-weight var(--dur-fast) var(--ease-out);
}
.statusbar a.sb-tenant:hover { color: var(--color-cyan-dim); font-weight: 700; }
.statusbar .sb-sep { color: var(--gray-2); }
.statusbar .sb-crumb {
  color: var(--note);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-out), font-weight var(--dur-fast) var(--ease-out);
}
.statusbar .sb-crumb:hover { color: var(--ink); font-weight: 700; }
.statusbar .sb-here { color: var(--ink); }
.statusbar .sb-right { margin-left: auto; display: flex; gap: 10px; align-items: center; }
/* Phase 18.4.0 — the clock slot between the theme toggle and the bell is empty
   on every current page; collapse it when empty so it doesn't add a second flex
   gap and leave the two icons looking oddly far apart. */
.statusbar .sb-right > span:empty { display: none; }
.statusbar .sb-icon {
  position: relative; display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; border: 0.5px solid var(--gray-2); background: var(--bg-surface);
  font-size: 9px; color: var(--ink);
}
.statusbar .sb-icon.bell { font-size: 11px; }
.statusbar .sb-icon::after {
  content: ''; position: absolute; top: -3px; right: -3px; width: 6px; height: 6px;
  background: var(--color-cyan); border-radius: 50%; border: 1px solid var(--bg-surface);
  display: none;
}
.statusbar .sb-icon.has-badge::after { display: block; }
/* Phase 14.8.0 — dark-mode toggle. Inherits the .sb-icon box; reset the button
   chrome and swap the sun/moon glyph by the resolved theme (set on <html>). */
.statusbar button.sb-theme { padding: 0; cursor: pointer; font: inherit; line-height: 0;
  transition: color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out); }
.statusbar .sb-theme:hover { color: var(--color-cyan); border-color: var(--color-cyan); }
.statusbar .sb-theme:active { transform: translateY(0.5px); }
.statusbar .sb-theme:focus-visible { outline: none; box-shadow: var(--glow-cyan-ring); }
.statusbar .sb-theme svg { display: block; }
.statusbar .sb-theme .sb-theme-sun { display: none; }
html[data-theme="dark"] .statusbar .sb-theme .sb-theme-sun { display: block; }
html[data-theme="dark"] .statusbar .sb-theme .sb-theme-moon { display: none; }
.statusbar .sb-cmd {
  display: inline-flex; gap: 4px; align-items: center;
  padding: 2px 8px; border: 0.5px solid var(--ink); background: var(--ink); color: var(--on-ink);
  font-size: 9px; letter-spacing: 0.14em;
}

/* ============================================================
   Phase 14.2.1 / 14.2.2 — mobile navigation: a fixed TOP tab bar +
   drop-down "More" sheet (replaced the 14.2.0 side drawer; moved from
   bottom to top in 14.2.2 so it no longer collides with iOS Safari's
   bottom toolbar). Below --bp-md the side rail is hidden and primary
   nav lives in the top bar; secondary nav in the sheet. Desktop
   (≥900px) is untouched.
   ============================================================ */
/* Tab bar, sheet, and scrim are desktop-hidden; the mobile media query below
   gives each its specific display (so the blunt .rv-show-md-down !important
   revert can't fight the flex/block layout these need). */
.rv-tabbar, .rv-moresheet, .rv-sheet-scrim { display: none; }

@media (max-width: 900px) {
  /* Rail leaves the screen entirely; content uses the full width. */
  .rv-portal .rail.rv-rail { display: none; }

  /* Phase 14.2.2 — the bar is fixed at the TOP (Safari's persistent bottom
     toolbar made a bottom bar collide; the top address bar auto-hides). The
     statusbar (first child of the body) starts below it. */
  #rv-portal-body { padding-top: 48px; }
  .rv-portal-footer { flex-wrap: wrap; gap: 6px 16px; }

  /* Fixed top tab bar. */
  .rv-tabbar {
    display: flex; position: fixed; left: 0; right: 0; top: 0;
    height: 48px;
    background: var(--metal-brushed);
    border-bottom: 0.5px solid var(--metal-edge);
    box-shadow: 0 2px 10px rgba(15, 20, 25, 0.08);
    z-index: 40;            /* above content; below modals (1000) + navprogress */
  }
  .rv-tab {
    flex: 1; min-width: 0; display: flex; flex-direction: column;
    align-items: center; justify-content: center; gap: 3px;
    position: relative; padding: 6px 2px;
    border: 0; background: transparent; cursor: pointer;
    color: var(--fg-2); text-decoration: none;
    font-family: var(--font-mono); font-size: 8.5px;
    letter-spacing: 0.1em; text-transform: uppercase;
  }
  .rv-tab__icn { font-size: 17px; line-height: 1; }
  .rv-tab .ic { width: 20px; height: 20px; }
  .rv-tab.on { color: var(--ink); }
  .rv-tab.on::before {
    content: ''; position: absolute; bottom: 0; left: 50%;
    width: 26px; height: 2px; transform: translateX(-50%);
    background: var(--color-cyan); border-radius: 2px 2px 0 0;
  }
  .rv-tab:focus-visible { outline: none; box-shadow: var(--glow-cyan-ring); }
  .rv-tab__badge {
    position: absolute; top: 2px; left: calc(50% + 6px);
    min-width: 15px; height: 15px; padding: 0 4px;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--color-cyan); color: #fff;
    font-family: var(--font-mono); font-size: 9px; font-weight: 600;
    line-height: 1; border-radius: 999px;
  }

  /* "More" sheet — drops DOWN from under the top tab bar, over a scrim. */
  .rv-moresheet {
    display: block; position: fixed; left: 0; right: 0;
    top: 48px; bottom: auto;
    max-height: 70vh; overflow-y: auto;
    background: var(--bg-surface);
    border-bottom: 0.5px solid var(--line-1);
    border-radius: 0 0 12px 12px;
    box-shadow: 0 10px 30px rgba(15, 20, 25, 0.16);
    transform: translateY(-110%);
    transition: transform var(--dur-base) var(--ease-out);
    z-index: 45;            /* above the tab bar, below modals */
    padding: 4px 10px 12px;
  }
  body.rv-more-open .rv-moresheet { transform: translateY(0); }
  .rv-moresheet__grip {
    width: 36px; height: 4px; border-radius: 2px;
    background: var(--gray-2); margin: 6px auto 6px;
  }
  .rv-more-group {
    font-family: var(--font-mono); font-size: 8.5px; letter-spacing: 0.16em;
    text-transform: uppercase; color: var(--fg-3); padding: 10px 10px 4px;
  }
  .rv-more-item {
    display: flex; align-items: center; gap: 12px;
    padding: 11px 10px; border-radius: 6px;
    color: var(--ink); text-decoration: none; font-size: 14px;
  }
  .rv-more-item .rail-icn { width: 22px; text-align: center; flex: none; }
  .rv-more-item .ic { width: 18px; height: 18px; }
  .rv-more-item.on { background: var(--paper-2); box-shadow: inset 2px 0 0 var(--color-cyan); }
  .rv-more-item:active { background: var(--paper-3); }
  .rv-more-signout { color: var(--bad-deep); }

  /* Scrim behind the sheet. */
  .rv-sheet-scrim {
    display: block; position: fixed; inset: 0;
    background: rgba(15, 20, 25, 0.42);
    z-index: 44; opacity: 0; pointer-events: none;
    transition: opacity var(--dur-base) var(--ease-out);
  }
  body.rv-more-open .rv-sheet-scrim { opacity: 1; pointer-events: auto; }
}

@media (max-width: 900px) and (prefers-reduced-motion: reduce) {
  .rv-moresheet, .rv-sheet-scrim { transition: none; }
}

/* file-path breadcrumb (slashes) */
.crumb {
  /* Phase 15.3.0 — full-bleed top bar: flush to the top + side edges (raised out
     of the padded .rv-stripped column in the shell), anchored by a real bottom
     border. 24px gutter aligns the text with the content column. */
  display: flex; align-items: center; gap: 8px;
  padding: 10px 24px;
  background: var(--paper-2);
  border-bottom: 1px solid var(--line-1);
  font-family: var(--font-mono); font-size: 10px;
  letter-spacing: 0.12em; color: var(--note);
  text-transform: lowercase;
  flex-shrink: 0;
}
.crumb .seg { color: var(--note); }
.crumb .sep { color: var(--gray-3); font-weight: 600; }
.crumb .here { color: var(--ink); }
.crumb .crumb-right { margin-left: auto; color: var(--gray-3); text-transform: uppercase; letter-spacing: 0.14em; font-size: 9.5px; }

/* page body wrapper inside frame */
.app-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  position: relative;
  overflow: hidden;
  background: var(--bg-canvas);
}

/* ===================== HUD micro-labels ===================== */
.mono {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--note);
}
.mono.lg { font-size: 11px; }
.mono.xs { font-size: 9px; letter-spacing: 0.16em; }
.mono.ink { color: var(--ink); }
.mono.cy { color: var(--color-cyan-dim); }
.mono.dim { color: var(--gray-3); }
.mono.ok { color: var(--ok-deep); }
.mono.warn { color: var(--warn-deep); }
.mono.bad { color: var(--bad-deep); }

/* ============================================================
   Phase 3.83 label set — replaces the old ".kicker" + literal "// ..."
   pattern with three intentional components plus real <h2> headings
   where the kicker was decoration over an existing heading.
   ============================================================ */
.rv-eyebrow {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--color-cyan-dim);
}
.rv-eyebrow.dim { color: var(--note); }
.rv-eyebrow.ink { color: var(--ink-soft); }

.rv-step {
  display: inline-flex;
  align-items: baseline;
  gap: 10px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--color-cyan-dim);
}
.rv-step__num {
  display: inline-block;
  min-width: 22px;
  padding: 1px 6px;
  background: var(--color-cyan-wash);
  border: 1px solid var(--color-cyan-edge);
  color: var(--color-cyan-dim);
  font-variant-numeric: tabular-nums;
  border-radius: 2px;
  text-align: center;
}
.rv-step__sep { color: var(--gray-3); }

.rv-section-label {
  display: block;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--note);
  padding-bottom: 6px;
  border-bottom: 1px solid var(--gray-2);
  margin-bottom: 10px;
  width: 100%;
}

/* Phase 20.13.0 — third-party trademark attribution. A copyright-weight
   footnote, not an alert: no icon, no border, no background, no cyan. Used
   wherever a surface displays a vendor's marks and their terms require an
   attribution line beside them.

   DO NOT REDUCE BELOW 11px. FedEx's Integrator Additional Guidance §3c requires
   the disclaimer "in at least an 8 pt font"; 8pt is ~10.67px, so 11px is the
   compliance floor, not a design preference. The 8-10px micro-label sizes used
   elsewhere in this file would all fail it. A stack of attributions renders as
   sibling <p>s inside one .rv-legal. */
.rv-legal {
  font-size: 11px;
  line-height: 1.45;
  color: var(--note);
  margin-top: 12px;
}
.rv-legal p { margin: 0; }
.rv-legal p + p { margin-top: 4px; }

/* Phase 9.0.0 — headings render in the display face (Chakra Petch) by
   default, per the REVERE type rules ("Chakra Petch — display: headings").
   Class selectors (.mono, .rv-eyebrow, .rv-section-label) override by
   specificity, so HUD/mono labels keep JetBrains Mono. */
h1, h2, h3, h4 { font-family: var(--font-display); }
.display { font-family: var(--font-display); font-weight: 600; letter-spacing: -0.005em; color: var(--ink); }
.display.xl { font-size: 36px; line-height: 1.04; }
.display.lg { font-size: 28px; line-height: 1.08; }
.display.md { font-size: 22px; line-height: 1.12; }
.display.sm { font-size: 17px; line-height: 1.2; }

/* ===================== INDICATOR DOTS & PIPS ===================== */
.dot {
  display: inline-block;
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--gray-2);
  flex-shrink: 0;
  position: relative;
}
.dot.ok { background: var(--ok); }
.dot.cy { background: var(--color-cyan); }
.dot.warn { background: var(--warn); }
.dot.bad { background: var(--bad); }
.dot.idle { background: var(--idle); }

/* indicator light — with optional halo for "active" state */
.led {
  display: inline-block;
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--gray-2);
  position: relative;
  flex-shrink: 0;
  cursor: help;
  box-shadow: 0 0 0 1px rgba(0,0,0,.06);
}
.led.ok    { background: var(--ok);    box-shadow: 0 0 0 1px rgba(21,160,107,.25), 0 0 6px rgba(21,160,107,.4); }   /* --status-success */
.led.cy    { background: var(--color-cyan-soft); box-shadow: 0 0 0 1px rgba(91,184,255,.3), 0 0 8px rgba(91,184,255,.6); }   /* Revere cyan #5BB8FF (design-system --color-sky) */
.led.warn  { background: var(--warn);  box-shadow: 0 0 0 1px rgba(216,148,26,.25), 0 0 6px rgba(216,148,26,.4); }   /* --status-warning */
.led.bad   { background: var(--bad);   box-shadow: 0 0 0 1px rgba(221,59,75,.3), 0 0 6px rgba(221,59,75,.45); }   /* --status-error */
.led.idle  { background: var(--idle); }
.led.pulse::after {
  content: ''; position: absolute; inset: -4px; border-radius: 50%;
  border: 1px solid currentColor; opacity: .35;
  animation: pulseRing 1.6s var(--ease-out) infinite;
}
.led.cy.pulse { color: var(--color-cyan-soft); }
.led.ok.pulse { color: var(--ok); }
.led.warn.pulse { color: var(--warn); }
@keyframes pulseRing {
  0%   { transform: scale(.6); opacity: .55; }
  100% { transform: scale(1.8); opacity: 0; }
}

/* tooltip on hover */
.tip { position: relative; }
.tip::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 6px); left: 50%; transform: translateX(-50%);
  background: var(--ink); color: var(--on-ink);
  font-family: var(--font-mono); font-size: 9px;
  letter-spacing: 0.08em; text-transform: uppercase;
  padding: 4px 8px; white-space: nowrap;
  opacity: 0; pointer-events: none;
  transition: opacity var(--dur-fast) var(--ease-out);
  z-index: 20;
}
.tip:hover::after { opacity: 1; }

/* pip — bordered cell that wraps a dot, used in tile grids */
.pip {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px;
  border: 1px solid var(--gray-2);
  background: var(--bg-surface);
  flex-shrink: 0;
}
.pip.cy { border-color: var(--color-cyan); background: var(--color-cyan-wash); }
.pip.ok { border-color: var(--ok); background: var(--ok-wash); }
.pip.warn { border-color: var(--warn); background: var(--warn-wash); }
.pip.bad { border-color: var(--bad); background: var(--bad-wash); }

/* ===================== BUTTONS ===================== */
/* Phase 7.1.0 — every variant now has hover + active(press) + focus-visible feedback.
   (Phase 17.4.0 removed the former cyan status-light dot button variant.) */
.btn {
  font-family: var(--font-mono); font-size: 10px;
  letter-spacing: 0.14em; text-transform: uppercase;
  padding: 7px 12px; border: 1px solid var(--ink);
  border-radius: var(--radius);
  background: var(--bg-surface); color: var(--ink); cursor: pointer;
  display: inline-flex; align-items: center; gap: 6px;
  line-height: 1;
  white-space: nowrap;
  text-decoration: none;               /* buttons-as-links must not underline */
  user-select: none;
  transition:
    background var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out),
    box-shadow var(--dur-fast) var(--ease-out),
    transform var(--dur-instant) var(--ease-out);
}
.btn:hover { background: var(--paper-2); }
.btn:active { transform: translateY(0.5px); background: var(--paper-3); }
.btn:focus-visible { outline: none; box-shadow: var(--glow-cyan-ring); }
.btn[disabled], .btn.disabled { opacity: .5; pointer-events: none; }

/* PRIMARY — brushed metal; identity is the surface + bevel + hover sweep, not the dot */
.btn.cy {
  background: var(--metal-brushed);
  color: var(--ink); border-color: var(--gray-3); /* #A8A79F */
  position: relative;
  padding: 8px 14px;
  box-shadow: var(--bevel-chrome);
  overflow: hidden; /* contains the specular sweep */
}
.btn.cy::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(115deg, transparent 35%, rgba(255,255,255,.28) 50%, transparent 65%);
  transform: translateX(-100%);
  transition: transform var(--dur-deliberate) var(--ease-out);
  pointer-events: none;
}
.btn.cy:hover { border-color: var(--ink); box-shadow: var(--bevel-chrome), 0 0 0 1px var(--color-cyan-edge); }
.btn.cy:hover::after { transform: translateX(100%); }
.btn.cy:active { transform: translateY(0.5px); box-shadow: inset 0 2px 4px rgba(0,0,0,.18); }

/* Phase 17.4.0 — the cyan status-light dot button variant was removed from the
   design system; circular status lights are no longer used on buttons. */

.btn.ink { background: var(--ink); color: var(--on-ink); border-color: var(--ink); }
.btn.ink:hover { background: var(--ink-2); }
.btn.ink:active { transform: translateY(0.5px); background: #0B0B0D; box-shadow: inset 0 2px 4px rgba(0,0,0,.5); }
.btn.ghost { background: transparent; border-color: var(--gray-2); color: var(--ink-soft); }
.btn.ghost:hover { color: var(--ink); border-color: var(--ink-soft); background: var(--paper-2); }
.btn.ghost:active { transform: translateY(0.5px); background: var(--paper-3); }
/* Phase 12.2.2 — destructive ghost action (e.g. Revoke handler). Mirrors
   .btn.ghost in the --bad token family (--bad #DD3B4B / --bad-wash #FBE6E9 /
   --bad-deep #9E1F2C), consistent with .tag.bad / .led.bad. Quiet until hover so
   an inline table action stays restrained; focus ring is bad, not cyan (cyan is
   surgical). */
.btn.bad { background: transparent; border-color: rgba(221,59,75,.4); color: var(--bad-deep); }
.btn.bad:hover { color: var(--bad-deep); border-color: var(--bad); background: var(--bad-wash); }
.btn.bad:active { transform: translateY(0.5px); background: #F6D5DA; }
.btn.bad:focus-visible { outline: none; box-shadow: 0 0 0 1px var(--bad); }
.btn.sm { padding: 4px 8px; font-size: 9px; letter-spacing: 0.12em; }
.btn.lg { padding: 10px 18px; font-size: 11px; }
.btn.full { display: flex; width: 100%; justify-content: center; }
.btn.icon-only { padding: 6px 8px; }

/* ============================================================
   Phase 14.3.0 — loading affordances: busy spinner + skeletons.
   The global prefers-reduced-motion rule (further down this file)
   freezes both animations to a static state automatically.
   ============================================================ */
/* Busy state for any action element. `.is-loading` appends a spinning ring in
   the current text color and blocks interaction; the label is kept so the box
   barely moves. window.rvBtnBusy() toggles this + aria-busy + disabled. The
   ::after is `flex: none` so it sits inline on the flex-row .btn. */
.is-loading { pointer-events: none; }
.is-loading::after {
  content: ""; flex: none; display: inline-block;
  width: 11px; height: 11px; margin-left: 6px; vertical-align: -1px;
  border: 2px solid currentColor; border-top-color: transparent;
  border-radius: 50%;
  animation: rv-spin 0.6s linear infinite;
}
@keyframes rv-spin { to { transform: rotate(360deg); } }

/* Skeleton placeholder toolkit. Inert utilities — defined here, first applied
   to real per-page placeholders by 14.3.1 (sales report) / 14.3.2 (dashboard).
   A tinted block with a left-to-right shimmer; reduced motion → static tint. */
.rv-skeleton {
  position: relative; overflow: hidden;
  background: var(--paper-2);
  border-radius: var(--radius);
}
.rv-skeleton::after {
  content: ""; position: absolute; inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.55), transparent);
  animation: rv-skeleton-shimmer 1.3s var(--ease-in-out) infinite;
}
@keyframes rv-skeleton-shimmer { 100% { transform: translateX(100%); } }
.rv-skeleton--text { height: 0.8em; margin: 0.25em 0; border-radius: 3px; }
.rv-skeleton--line { height: 10px; width: 100%; }
.rv-skeleton--block { height: 96px; width: 100%; }

/* button group / segmented */
.btn-group { display: inline-flex; }
.btn-group .btn { border-right-width: 0; }
.btn-group .btn:last-child { border-right-width: 1px; }
.btn-group .btn.on { background: var(--ink); color: var(--on-ink); }

/* ===================== INPUTS ===================== */
.field { display: flex; flex-direction: column; gap: 5px; }
.field-label {
  font-family: var(--font-mono); font-size: 9.5px;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--note);
}
.field-hint { font-family: var(--font-mono); font-size: 9px; color: var(--note); letter-spacing: 0.06em; }
.input {
  font-family: var(--font-body); font-size: 14px;
  padding: 10px 12px;
  border: 1px solid var(--gray-2);
  border-radius: var(--radius);
  background: var(--bg-surface);
  color: var(--ink);
  height: 38px;
}
.input:focus { outline: none; border-color: var(--color-cyan); box-shadow: 0 0 0 3px var(--color-cyan-wash); }
.input.locked { background: var(--paper-2); color: var(--fg-2); }
.input.sm { height: 28px; padding: 6px 10px; font-size: 12px; }

.otp { display: inline-flex; gap: 6px; }
.otp .ot {
  width: 38px; height: 44px;
  border: 1px solid var(--gray-2);
  border-radius: var(--radius);
  background: var(--bg-surface);
  font-family: var(--font-mono); font-size: 18px;
  text-align: center;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--ink);
}
.otp .ot.on { border-color: var(--ink); }

/* ===================== CARDS ===================== */
.card {
  background: var(--bg-surface);
  border: 1px solid var(--line-1);
  border-radius: var(--radius);
  padding: 14px;
  display: flex; flex-direction: column; gap: 10px;
  position: relative;
}
.card.elev { box-shadow: var(--shadow-1); }
.card.brushed { background: var(--metal-brushed); border-color: var(--gray-3); box-shadow: var(--bevel-chrome); }
.card.flat { background: var(--paper-2); border-color: var(--line-hairline); }
.card.ink { background: var(--card-ink-bg); color: var(--card-ink-fg); border-color: var(--card-ink-border); }

/* ===================== TABLE ===================== */
.tbl { display: flex; flex-direction: column; background: var(--bg-surface); border: 1px solid var(--line-1); }
.tbl .tr {
  display: flex; align-items: center; gap: 12px;
  padding: 9px 14px;
  border-bottom: 0.5px solid var(--line-hairline);
  font-size: 12px;
}
.tbl .tr:last-child { border-bottom: none; }
.tbl .tr.hd {
  background: var(--paper-2);
  font-family: var(--font-mono); font-size: 9.5px;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--note);
  border-bottom: 1px solid var(--gray-2);
}
.tbl .tr.cy { background: var(--color-cyan-wash); box-shadow: inset 2px 0 0 var(--color-cyan); }
.tbl .tr:hover:not(.hd) { background: var(--paper-2); cursor: default; }
.tbl .td { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tbl .td.right { text-align: right; }
.tbl .td.mono { font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.06em; }

/* ---------- .rv-table — the single consistent table component (Phase 7.1.0) ----------
   Semantic <table> + table-layout: fixed gives deterministic, identical columns across
   every page. Replaces both the flexbox .tbl pseudo-table and the per-page inline-styled
   <table> markup that was overriding it (the squished-left bug). Callers set column widths
   on the header <th>. .tbl above stays as a dormant alias until all callers migrate. */
.rv-table {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
  background: var(--bg-surface);          /* #FFFFFF */
  border: 1px solid var(--line-1);        /* #D7D6CE */
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}
.rv-table th, .rv-table td {
  padding: 9px 14px;
  text-align: left;
  vertical-align: middle;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  border-bottom: 0.5px solid var(--line-hairline); /* #E6E5DD */
}
.rv-table thead th {
  background: var(--paper-2);             /* #F4F4EE */
  font-family: var(--font-mono);
  font-size: 9.5px; font-weight: 500;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--note);                     /* #6B6E73 */
  border-bottom: 1px solid var(--gray-2); /* #C7C6BE */
}
.rv-table tbody tr:last-child td { border-bottom: none; }
.rv-table tbody tr:hover { background: var(--paper-2); }
.rv-table tbody tr.is-current { background: var(--color-cyan-wash); box-shadow: inset 2px 0 0 var(--color-cyan); }
.rv-table tbody tr.is-current:hover { background: var(--color-cyan-wash); }
/* Phase 8.4.1 — opt-in row hover: a cyan inset accent + light wash. Unlike a
   lift (transform + outset shadow), this stays inside the row's box, so it
   never clips and works inside a scrollable table. Panels use `.panel-anim`
   for the lift; row-based lists use this. Add `.rv-table--hover`. */
.rv-table--hover tbody tr { transition: background var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out); }
.rv-table--hover tbody tr:hover { background: var(--paper-2); box-shadow: inset 3px 0 0 var(--color-cyan); }
.rv-table--hover tbody tr.is-current:hover { box-shadow: inset 2px 0 0 var(--color-cyan); }
.rv-table .num, .rv-table .actions { text-align: right; }
.rv-table .actions { white-space: nowrap; }
.rv-table .actions .btn + .btn { margin-left: 6px; }
.rv-table .mono { font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.06em; color: var(--note); }
.rv-table td.wrap { white-space: normal; }

/* Phase 14.5.0 — horizontal-scroll wrapper for wide data-dense tables. A <table>
   cannot scroll itself; wrapping it lets a wide table scroll within its own
   column on narrow viewports instead of pushing the page wider than the screen
   (which broke the 14.2.x mobile shell). `overflow-x: auto` is a no-op when the
   table fits, so no @media is needed — it also covers a wide table inside a
   narrow desktop panel. Thin REVERE hover-scrollbar (mirrors `.cra-scroll`); the
   element is a keyboard-focusable scroll region (markup adds tabindex="0") with
   the standard cyan focus ring so it's reachable without a pointer. */
.rv-table-scroll {
  max-width: 100%;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
  scrollbar-color: transparent transparent;
}
.rv-table-scroll:hover { scrollbar-color: var(--gray-3) transparent; }
.rv-table-scroll::-webkit-scrollbar { width: 7px; height: 7px; }
.rv-table-scroll::-webkit-scrollbar-track { background: transparent; }
.rv-table-scroll::-webkit-scrollbar-thumb { background: transparent; border-radius: 2px; transition: background var(--dur-fast) var(--ease-out); }
.rv-table-scroll:hover::-webkit-scrollbar-thumb { background: var(--gray-3); }
.rv-table-scroll::-webkit-scrollbar-thumb:hover { background: var(--note); }
.rv-table-scroll:focus-visible { outline: none; box-shadow: var(--glow-cyan-ring); }
/* The base table is width:100% + table-layout:fixed, so on its own it fills the
   wrapper and cells cram/wrap instead of overflowing — the scroll never engages.
   Below the mobile breakpoint, let a wrapped table size to its content so it
   overflows the wrapper and scrolls horizontally. `> table` (not `.rv-table`)
   so it also covers the CRA `.cra-pl-table`. Desktop keeps the fixed full-width
   layout. */
@media (max-width: 900px) {
  .rv-table-scroll > table { table-layout: auto; min-width: max-content; }
}

/* Phase 12.6.9 — vertical-scroll wrapper for a long table whose header must stay
   put. The sibling of `.rv-table-scroll` above: that one keeps a WIDE table
   inside its column, this one keeps a LONG table's header on screen.

   The two rules below look like decoration and are not. Both encode one lesson,
   learned separately on three surfaces: a visual edge must belong to the element
   that STAYS, never to the element that scrolls.

   1. The frame lives on this box, not on `.rv-table`. A border on the table
      scrolls with the table — its top edge climbs out of view the moment the
      list moves and its bottom edge sits below the fold, so the table reads as
      three loose sides. Bordering the scroll viewport keeps all four edges on
      screen at every scroll position.

   2. The pinned header's bottom rule is an inset box-shadow, NOT the
      `border-bottom` that `.rv-table thead th` already declares. `.rv-table` is
      `border-collapse: collapse`, and a collapsed border is painted as part of
      the TABLE's border grid rather than by the cell that declared it — so it
      does not travel with a `position: sticky` cell. It scrolls away and takes
      the header/body boundary with it, which is what lets the row underneath
      show through the strip the rule should have occupied. A box-shadow is
      painted by the cell itself, so it goes where the cell goes.

   `position: sticky` is set on `thead th`, not on `thead`, because it does not
   apply to a table section in Chrome/Safari — only to its cells. The header's
   opaque `--paper-2` background comes from `.rv-table thead th`, so rows pass
   behind it rather than through it.

   Prior art this generalizes: the contact-requests list (25.6), which reached
   both halves independently, and `.cra-issues-wrap` (8.13.0), which reached the
   first. Reported on the CRA dashboard's contract table as #702. */
.rv-table-sticky {
  overflow: auto;
  min-height: 0;
  border: 1px solid var(--line-1);
  border-radius: var(--radius);
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
  scrollbar-color: transparent transparent;
}
.rv-table-sticky .rv-table { border: 0; }
.rv-table-sticky thead th {
  position: sticky; top: 0; z-index: 2;
  box-shadow: inset 0 -1px 0 var(--gray-2);
}
/* Thin REVERE hover-scrollbar + cyan focus ring, matching `.rv-table-scroll`.
   Add tabindex="0" in the markup where the scroll region should be keyboard
   reachable; the focus ring below is what makes that focus visible. */
.rv-table-sticky:hover { scrollbar-color: var(--gray-3) transparent; }
.rv-table-sticky::-webkit-scrollbar { width: 7px; height: 7px; }
.rv-table-sticky::-webkit-scrollbar-track { background: transparent; }
.rv-table-sticky::-webkit-scrollbar-thumb { background: transparent; border-radius: 2px; transition: background var(--dur-fast) var(--ease-out); }
.rv-table-sticky:hover::-webkit-scrollbar-thumb { background: var(--gray-3); }
.rv-table-sticky::-webkit-scrollbar-thumb:hover { background: var(--note); }
.rv-table-sticky:focus-visible { outline: none; box-shadow: var(--glow-cyan-ring); }
/* collapsible detail rows (audit log, Phase 7.7.0) */
.rv-table tr.rv-row-toggle { cursor: pointer; }
.rv-table tr.rv-row-toggle td:first-child::before {
  content: '\203A'; /* › */
  display: inline-block; margin-right: 8px;
  color: var(--gray-3);
  transition: transform var(--dur-fast) var(--ease-out);
}
.rv-table tr.rv-row-toggle.is-open td:first-child::before { transform: rotate(90deg); }
.rv-table tr.rv-row-detail > td {
  background: var(--paper);               /* #FBFBF8 */
  padding: 12px 14px 14px 34px;
  font-family: var(--font-mono); font-size: 10px;
  color: var(--note); white-space: normal;
}
.rv-table tr.rv-row-detail[hidden] { display: none; }
/* full-bleed inside a .card without a doubled border */
.card > .rv-table, .card > .scroll > .rv-table { border: none; }

/* .rv-link — inline text link in the cyan-dim accent (e.g. table actions) */
.rv-link {
  color: var(--color-cyan-dim);           /* dim cyan */
  font-family: var(--font-mono);
  font-size: 11px; letter-spacing: 0.04em;
  text-decoration: none;
  white-space: nowrap;
}
.rv-link:hover { text-decoration: underline; text-underline-offset: 2px; }

/* ---------- .rv-avatar — initials in a color circle (Phase 7.1.0) ----------
   Color comes from User.avatar_color (a ROYGBIV palette key resolved to --av-*);
   defaults to --ink when unset. Reused by the rail, profile, users table, audit log. */
.rv-avatar {
  --rv-av-size: 20px;
  width: var(--rv-av-size); height: var(--rv-av-size);
  border-radius: 50%;
  background: var(--ink);
  color: var(--on-ink);
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--font-mono); font-weight: 600;
  font-size: calc(var(--rv-av-size) * 0.42);
  line-height: 1; flex-shrink: 0;
  text-transform: uppercase;
  user-select: none;
}
.rv-avatar.sm { --rv-av-size: 18px; }
.rv-avatar.lg { --rv-av-size: 48px; }

/* ---------- .rv-swatch — avatar color picker (Phase 7.10.0) ----------
   A row of ROYGBIV swatches + a "none" (default ink) chip. The selected
   swatch carries a chrome ring; the none chip shows a diagonal slash. */
.rv-swatch-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.rv-swatch {
  width: 24px; height: 24px;
  border-radius: 50%;
  border: 1px solid var(--metal-edge);
  padding: 0;
  cursor: pointer;
  background: var(--ink);
  box-shadow: none;
  transition: transform var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
.rv-swatch:hover { transform: scale(1.08); }
.rv-swatch:focus-visible { outline: none; box-shadow: var(--glow-cyan-ring); }
.rv-swatch.is-selected {
  box-shadow: 0 0 0 2px var(--paper), 0 0 0 3.5px var(--ink);
}
/* "Default" chip — neutral surface with a diagonal slash to read as "no color". */
.rv-swatch--none {
  background:
    linear-gradient(135deg, transparent calc(50% - 1px), var(--gray-2) calc(50% - 1px), var(--gray-2) calc(50% + 1px), transparent calc(50% + 1px)),
    var(--paper-2);
}

/* ===================== CUSTOM SCROLLBAR ===================== */
.scroll {
  overflow: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--gray-3) transparent;
}
.scroll::-webkit-scrollbar { width: 10px; height: 10px; }
.scroll::-webkit-scrollbar-track { background: transparent; }
.scroll::-webkit-scrollbar-thumb {
  background: var(--gray-2);
  border: 2px solid transparent;
  background-clip: padding-box;
  border-radius: 0;
}
.scroll::-webkit-scrollbar-thumb:hover { background: var(--gray-3); background-clip: padding-box; border: 2px solid transparent; }
.scroll::-webkit-scrollbar-corner { background: transparent; }
/* the slim glyph version */
.scroll.tight::-webkit-scrollbar { width: 6px; height: 6px; }
.scroll.tight::-webkit-scrollbar-thumb { background: var(--gray-3); border: none; }

/* ===================== DECALS ===================== */
.decal {
  position: absolute;
  pointer-events: none;
  user-select: none;
  display: block;
  color: var(--gray-1);
  opacity: 0.7;
}
.decal svg, .decal use { display: block; }
.decal > svg { width: 100%; height: 100%; }
/* svg as decal: inline style fills with currentColor */
svg.decal { fill: currentColor; color: var(--gray-1); }
svg.decal use { fill: currentColor; }
.decal.fade-r, svg.decal.fade-r { -webkit-mask-image: linear-gradient(90deg, black 0%, black 55%, transparent 100%); mask-image: linear-gradient(90deg, black 0%, black 55%, transparent 100%); }
.decal.fade-l, svg.decal.fade-l { -webkit-mask-image: linear-gradient(-90deg, black 0%, black 55%, transparent 100%); mask-image: linear-gradient(-90deg, black 0%, black 55%, transparent 100%); }
.decal.fade-b, svg.decal.fade-b { -webkit-mask-image: linear-gradient(180deg, black 0%, black 50%, transparent 100%); mask-image: linear-gradient(180deg, black 0%, black 50%, transparent 100%); }
.decal.fade-t, svg.decal.fade-t { -webkit-mask-image: linear-gradient(0deg, black 0%, black 50%, transparent 100%); mask-image: linear-gradient(0deg, black 0%, black 50%, transparent 100%); }
.decal.fade-h, svg.decal.fade-h {
  -webkit-mask-image: linear-gradient(90deg, transparent 0%, black 25%, black 75%, transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0%, black 25%, black 75%, transparent 100%);
}

/* registration mark glyph */
.regmark {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.18em;
  color: var(--note);
  display: inline-flex; align-items: center; gap: 6px;
}

/* ===================== AUTH HERO ===================== */
.auth-hero {
  padding: 22px 28px 18px;
  border-bottom: 0.5px dashed var(--gray-2);
  background: var(--bg-surface);
  position: relative;
}
.auth-hero h2 { font-family: var(--font-display); font-weight: 600; font-size: 26px; line-height: 1.05; color: var(--ink); }
.auth-hero p { font-size: 13.5px; color: var(--fg-2); margin-top: 6px; max-width: 92%; }

/* ===================== UTILITIES ===================== */
.row { display: flex; gap: 12px; align-items: center; }
.row.sp { justify-content: space-between; }
.row.bs { align-items: flex-start; }
.row.cn { justify-content: center; }
.col { display: flex; flex-direction: column; gap: 12px; }
.grow { flex: 1; }
.shrink { flex-shrink: 0; }
.gap-0 { gap: 0; }
.gap-2 { gap: 4px; }
.gap-3 { gap: 6px; }
.gap-4 { gap: 8px; }
.gap-5 { gap: 10px; }
.gap-6 { gap: 14px; }
.gap-7 { gap: 18px; }
.gap-8 { gap: 24px; }
.pad-4 { padding: 16px 22px; }
.pad-5 { padding: 20px 26px; }
.pad-6 { padding: 26px 32px; }
.mt-a { margin-top: auto; }
.center-all { display: flex; flex-direction: column; align-items: center; justify-content: center; }
.hairline { height: 1px; background: var(--line-hairline); }
.vrule { width: 1px; background: var(--line-hairline); align-self: stretch; }
.rel { position: relative; }
.muted { opacity: 0.55; }
.flat-fill { background: var(--paper-2); }

/* Phase 5.9 — visually hidden but screen-reader-readable. Standard
   pattern used by `#rv-hub-live` and any future polite live region. */
.rv-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* Phase 14.1.0 — responsive visibility helpers, keyed on the --bp-md (900px)
   mobile threshold. Defined here as foundation; first applied by the 14.2.0
   shell work. `.rv-show-md-down` defaults hidden (desktop) and reveals on
   narrow viewports; `.rv-hide-md-down` does the reverse. */
.rv-show-md-down { display: none; }
@media (max-width: 900px) {
  .rv-hide-md-down { display: none !important; }
  .rv-show-md-down { display: revert !important; }
}

/* tag pill */
.tag {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 8px;
  border: 0.5px solid var(--gray-2);
  border-radius: var(--radius);
  font-family: var(--font-mono); font-size: 9.5px;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--ink-soft); background: var(--bg-surface);
}
.tag.cy { border-color: var(--color-cyan-edge); background: var(--color-cyan-wash); color: var(--color-cyan-dim); }
.tag.ok { border-color: rgba(21,160,107,.3); background: var(--ok-wash); color: var(--ok-deep); }
.tag.warn { border-color: rgba(216,148,26,.3); background: var(--warn-wash); color: var(--warn-deep); }
.tag.bad { border-color: rgba(221,59,75,.3); background: var(--bad-wash); color: var(--bad-deep); }
.tag.ink { border-color: var(--ink); background: var(--ink); color: var(--on-ink); }

/* Phase 17.8.1 — status icons. Two-tone icon inside a status label; the primary
   tone follows the label's `color` (so ok→green, bad→red, warn→amber, cy→cyan)
   while the secondary tone stays neutral grey. `.tag` already aligns via its gap;
   inline `.mono` status text needs alignment + a small gap. */
.tag .ic { width: 13px; height: 13px; }
.mono .ic { width: 13px; height: 13px; vertical-align: -2px; margin-right: 4px; }

/* ============= TOOL TILE — Smash Bros style ============= */
.tools-lane {
  align-items: stretch;
}
.tool-tile {
  flex: 1; min-width: 180px;
  background: var(--bg-surface);
  border: 1px solid var(--line-1);
  border-radius: var(--radius);
  padding: 14px 16px;
  display: flex; flex-direction: column; gap: 10px;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  transition: transform var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-out);
  min-height: 120px;
}
.tool-tile .tt-decal {
  position: absolute; inset: 0; pointer-events: none;
  opacity: .35;
  -webkit-mask-image: linear-gradient(135deg, transparent 0%, transparent 30%, black 100%);
  mask-image: linear-gradient(135deg, transparent 0%, transparent 30%, black 100%);
}
.tool-tile .tt-hd { display: flex; align-items: center; justify-content: space-between; position: relative; z-index: 2; }
.tool-tile .tt-title {
  font-family: var(--font-display); font-weight: 600; font-size: 22px;
  line-height: 1.1; color: var(--ink);
  position: relative; z-index: 2;
}
.tool-tile .tt-meta {
  display: flex; gap: 12px;
  font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.04em;
  color: var(--ink-soft);
  margin-top: auto;
  position: relative; z-index: 2;
}
.tool-tile .tt-meta b { color: var(--ink); }
.tool-tile.active {
  background: var(--metal-brushed);
  border-color: var(--gray-3);
  box-shadow: var(--bevel-chrome), inset 2px 0 0 var(--color-cyan), var(--shadow-1);
}
.tool-tile:hover, .tool-tile:focus-visible {
  transform: translateY(-2px) scale(1.018);
  box-shadow: var(--shadow-2);
  border-color: var(--ink-soft);
  z-index: 3;
}
.tool-tile.active:hover {
  box-shadow: var(--bevel-chrome), inset 2px 0 0 var(--color-cyan), var(--shadow-2);
}
.tool-tile.add {
  border-style: dashed;
  background: var(--paper-2);
  color: var(--note);
  align-items: center; justify-content: center;
  flex: 0 0 160px;
}
.tool-tile.add:hover {
  background: var(--color-cyan-wash);
  border-color: var(--color-cyan-edge);
}

/* ============= HUB WIDGET GRID ============= */
.hub-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 12px;
}
.hub-grid > .card { min-width: 0; }

/* ============= MARKDOWN BODY ============= */
.md-body { font-family: var(--font-body); color: var(--ink); font-size: 13.5px; line-height: 1.55; }
.md-body h4 { font-family: var(--font-display); font-weight: 600; font-size: 16px; margin-bottom: 6px; }
.md-body p { color: var(--ink-soft); margin-bottom: 8px; }
.md-body code { font-family: var(--font-mono); font-size: 11.5px; background: var(--paper-2); padding: 1px 5px; }
.md-body ul { padding-left: 16px; color: var(--ink-soft); }
.md-body ul li { margin-bottom: 3px; }
.md-body strong { color: var(--ink); font-weight: 600; }

/* ============= IN-APP SPREADSHEET ============= */
.sheet {
  border: 1px solid var(--line-1);
  background: var(--bg-surface);
  position: relative;
  min-height: 0;
}
.sheet-tbl {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  font-family: var(--font-mono);
  font-size: 11.5px;
  letter-spacing: 0.02em;
  color: var(--ink);
  table-layout: fixed;
}
.sheet-tbl th, .sheet-tbl td {
  padding: 6px 9px;
  border-right: 0.5px solid var(--line-hairline);
  border-bottom: 0.5px solid var(--line-hairline);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  background: var(--bg-surface);
  text-align: left;
}
.sheet-tbl th {
  background: var(--paper-2);
  font-weight: 500;
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--note);
  position: sticky;
  top: 0;
  z-index: 2;
}
.sheet-tbl .row-h {
  width: 36px;
  background: var(--paper-2);
  color: var(--note);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-align: center;
  position: sticky;
  left: 0;
  z-index: 1;
}
.sheet-tbl tr.flag td { background: var(--color-cyan-wash); }
.sheet-tbl tr.flag td.row-h.flag { background: var(--color-cyan-wash); color: var(--color-cyan-dim); font-weight: 600; }
.sheet-tbl tr:hover td { background: rgba(30,143,224,.05); }
.sheet-tbl tr.flag:hover td { background: rgba(30,143,224,.12); }

/* ============= REVIEW ISSUES PANEL ============= */
.issue {
  background: var(--bg-surface);
  border: 1px solid var(--line-1);
  padding: 9px 12px;
  display: flex; flex-direction: column; gap: 6px;
  cursor: pointer;
  font-size: 11px;
}
.issue:hover { background: var(--paper-2); }
.issue.active {
  background: var(--color-cyan-wash);
  border-color: var(--color-cyan-edge);
  box-shadow: inset 2px 0 0 var(--color-cyan);
}
.issue .issue-body { padding-left: 18px; }
.issue .issue-meta {
  font-family: var(--font-mono); font-size: 10.5px;
  color: var(--note);
  padding-left: 18px;
}

/* ============================================================
   v0.3 AUGMENTATION
   Addresses Aksel review feedback:
   - rail item hover animation (grow + fill L-to-R)
   - LED needs label, not just color
   - LED pulse overflow fix
   - ink panel borders for contrast
   - tool-tile applied site-wide via .panel-anim
   - decal opacity dialed down so content never overlaps
   - larger avatar sizes
   - two-tone icon containers
   ============================================================ */

/* === Two-tone icon scale inside rail === */
.rail .rail-icn {
  width: 22px; height: 22px;
  border: none;
  background: transparent;
  color: var(--ink-soft);
  padding: 0;
  position: relative;
  z-index: 2;
}
.rail .rail-icn-svg { background: transparent !important; border: none !important; }
.rail .rail-icn .ic { width: 20px; height: 20px; display: block; }
.rail .rail-item.on .rail-icn { color: var(--ink); }
/* Phase 17.8.1 — dark mode: two-tone nav icons read muddy because the outline
   (currentColor = a light grey) sat slightly LIGHTER than the #9CA0A6 secondary
   fill, collapsing the two tones. Pitch the default-state outline darker than the
   fill so the linework separates over the bright body. Hover (white) + active
   (ink) keep their own higher-specificity colors. */
html[data-theme="dark"] .rail .rail-item:not(.on) .rail-icn,
html[data-theme="dark"] .rv-tabbar .rv-tab:not(.on) .rail-icn,
html[data-theme="dark"] .rv-moresheet .rv-more-item .rail-icn { color: var(--fg-3); }

/* === Rail item: grow + dark fill L-to-R on hover === */
/* Aksel "hover animation: grow a bit, fill with dark from left to right" */
/* Phase 3.83 — translateX moved to the inner .rail-item__slide wrapper so
   the slide doesn't bleed past the rail's content area and trigger an
   overflow-x scrollbar on the rail (overflow-y: auto implicitly clamps
   overflow-x: visible to auto). */
.rail .rail-item {
  position: relative;
  overflow: hidden;
  transition: color var(--dur-base) var(--ease-out);
}
.rail .rail-item::before {
  content: '';
  position: absolute; inset: 0;
  /* Phase 17.8.2 — finish-specific hover sweep. `--rail-hover-fill` is themed per
     finish in finishes.css; the brushed default (a dark gradient with fine vertical
     striations) lives in :root. The darkness model is unchanged — text/icon turn
     white on hover — only the texture varies by finish. */
  background: var(--rail-hover-fill);
  transform: translateX(-101%);
  transition: transform var(--dur-slow) var(--ease-in-out);
  z-index: 0;
  pointer-events: none;
}
.rail .rail-item > * { position: relative; z-index: 1; }
.rail .rail-item__slide {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 1;
  min-width: 0;
  transition: transform var(--dur-base) var(--ease-out);
}
.rail:not(.wide):not(:hover) .rail-item__slide { justify-content: center; }
.rail .rail-item:hover .rail-item__slide { transform: translateX(2px); }
.rail .rail-item:not(.on):hover { color: white; }
.rail .rail-item:not(.on):hover .rail-icn { color: white; }
.rail .rail-item:not(.on):hover .rail-label { color: white; }
.rail .rail-item:not(.on):hover .rail-badge { color: var(--color-cyan-soft); }
.rail .rail-item:not(.on):hover::before { transform: translateX(0); }
.rail .rail-item.on::before { display: none; }

/* === Panel hover animation (Aksel: apply to all clickable panels) === */
.panel-anim,
.tool-tile,
.tool-card,
.issue,
.tbl .tr.clickable {
  transition: transform var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-out);
}
.panel-anim:hover { transform: translateY(-2px) scale(1.015); box-shadow: var(--shadow-2); border-color: var(--ink-soft); }

/* tool-tile pulse animation needs to be CONTAINED so it doesn't get clipped */
.tool-tile { overflow: visible; }
.tool-tile .tt-decal { overflow: hidden; }

/* === LED + label component === */
/* Aksel: "color indicator useless. Anything that uses color (green/blue/red)
   should have a meaningful representation." → pair every LED with a word */
.led-pair {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}
.led-pair .led { flex-shrink: 0; }
.led-pair .led-label {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.led-pair.cy .led-label { color: var(--color-cyan-dim); }
.led-pair.ok .led-label { color: var(--ok-deep); }
.led-pair.warn .led-label { color: var(--warn-deep); }
.led-pair.bad .led-label { color: var(--bad-deep); }
/* Phase 8.26.8 — terminal "reported" reads as quiet settled grey (status-neutral),
   distinct from the active colored states. */
.led-pair.idle .led-label { color: var(--idle); }   /* --idle #8B939B */

/* === LED pulse: keep within bounds === */
/* Aksel: "animation effect is being cut off by the boundary" */
.led.pulse {
  position: relative;
}
.led.pulse::after {
  content: '';
  position: absolute;
  inset: -3px;        /* tighter than -4 so it doesn't bleed too far */
  border-radius: 50%;
  border: 1px solid currentColor;
  opacity: .4;
  animation: pulseRingTight 1.8s var(--ease-out) infinite;
}
@keyframes pulseRingTight {
  0%   { transform: scale(.6); opacity: .55; }
  100% { transform: scale(1.4); opacity: 0; }
}
/* Containers holding pulsing LEDs need extra room */
.led-host {
  padding: 4px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* === Card / ink panel border for high contrast === */
/* Aksel: "highly contrasted stuff like this will need borders" */
.card.ink {
  border: 1px solid var(--card-ink-border);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.06), 0 6px 18px rgba(15,20,25,.18);
}
.card.ink-soft {
  background: var(--card-ink-soft-bg);
  color: var(--card-ink-soft-fg);
  border: 1px solid rgba(255,255,255,.08);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.05);
}
.card.ink-soft .mono { color: var(--card-ink-soft-mono); }
.card.ink-soft .mono.cy { color: var(--color-cyan-soft); }

/* === Decal opacity reduced so content never overlaps === */
.decal { opacity: .35; }
.decal[style*="opacity"] { /* honor inline opacity overrides */ }
/* Soft watermark variant — for big decals behind content */
svg.decal.watermark { opacity: .12; }

/* === Hero decal — big, dark, fills empty whitespace (per Aksel example) === */
.decal.hero {
  color: var(--ink);
  opacity: .92;
}
.decal.hero.light { color: var(--gray-2); opacity: .45; }
.decal.hero.faded { color: var(--ink); opacity: .14; }
.decal.dark { color: var(--ink); opacity: .85; }
.decal.dark-stripe {
  color: rgba(255,255,255,.85);
  opacity: 1;
  mix-blend-mode: screen;
}
/* Faded background decal — sits behind content as wallpaper, never competes */
.decal.bg {
  color: var(--ink);
  opacity: .055;
}
.decal.bg-soft { color: var(--ink); opacity: .035; }

/* === Avatar sizes === */
/* Phase 3.83 — shrink to keep the avatar close to icon width (14px). Paired with
   reduced .rail.wide .rail-me gap so the name aligns with rail-item labels. */
.rail .rail-me-av {
  width: 20px; height: 20px;
  font-size: 8.5px;
}
.rail.wide .rail-me-av { width: 20px; height: 20px; font-size: 8.5px; }
.av-md { width: 28px; height: 28px; font-size: 10px; }
.av-lg { width: 36px; height: 36px; font-size: 12px; }
.av-xl { width: 48px; height: 48px; font-size: 14px; }

/* === Rail-me banner: use full width when wide === */
/* Aksel: "this banner is nice, use the full space" */
.rail.wide .rail-me {
  background: var(--ink);
  color: white;
  padding: 10px 14px;
  gap: 4px;
  border-top: 1px solid var(--metal-edge);
}
/* Phase 17.8.4 — hover shift for the ink banner (--ink-2 sits one step off
   --ink in both themes, so the shift reads in light and dark). */
.rail.wide .rail-me:hover { background: var(--ink-2); }
.rail.wide .rail-me .rail-me-av {
  background: var(--metal-brushed);
  color: var(--ink);
  border: 1px solid rgba(255,255,255,.2);
}
.rail.wide .rail-me .rail-me-name { color: white; }
.rail.wide .rail-me .rail-me-name small { color: var(--color-cyan-soft); letter-spacing: 0.14em; }

/* === sb-cmd two-tone === */
.sb-cmd {
  display: inline-flex !important;
  align-items: center;
  gap: 4px;
  padding: 2px 6px 2px 4px !important;
}
.sb-cmd .ic { color: white; }
.sb-cmd-letter {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  color: white;
}

/* === Footer-anchored notice (Aksel: keep notices at bottom) === */
.notice-bar {
  position: sticky;
  bottom: 0;
  margin-top: auto;
  padding: 8px 22px;
  background: var(--paper-2);
  border-top: 0.5px solid var(--line-1);
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  color: var(--note);
  display: flex;
  justify-content: space-between;
  align-items: center;
}

/* === Big-banner suspended (Aksel: "looks bad" replacement) === */
.suspended-banner {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 18px;
  background: linear-gradient(180deg, rgba(221,59,75,.06), rgba(221,59,75,.10));   /* --status-error */
  border: 1px solid rgba(221,59,75,.22);
  border-left: 3px solid var(--bad);
  color: var(--ink);
}
.suspended-banner .ic { width: 22px; height: 22px; color: var(--bad); }
.suspended-banner .sb-title { font-family: var(--font-display); font-weight: 600; font-size: 14px; }
.suspended-banner .sb-meta { font-family: var(--font-mono); font-size: 10.5px; color: var(--ink-soft); }

/* ===================== ALERTS (Phase 17.8.3) =====================
   Inline message block — leading two-tone status icon + title + body, a left
   accent and a faint tone wash. Four tones reuse the status tokens + icons so
   alerts read as one family with badges and the suspended banner. */
.rv-alert {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 12px 14px;
  border: 1px solid var(--line-1);
  border-left: 3px solid var(--ink-soft);
  border-radius: var(--radius);
  background: var(--paper-2);
  color: var(--ink);
}
.rv-alert > .ic { width: 18px; height: 18px; flex-shrink: 0; margin-top: 1px; color: var(--ink-soft); }
.rv-alert__body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.rv-alert__title { font-family: var(--font-display); font-weight: 600; font-size: 13px; line-height: 1.3; }
.rv-alert__text { font-size: 13px; color: var(--fg-2); line-height: 1.45; }
.rv-alert.info { border-left-color: var(--color-cyan); background: var(--color-cyan-wash); }
.rv-alert.info > .ic { color: var(--color-cyan-dim); }
.rv-alert.ok { border-left-color: var(--ok); background: var(--ok-wash); }
.rv-alert.ok > .ic { color: var(--ok-deep); }
.rv-alert.warn { border-left-color: var(--warn); background: var(--warn-wash); }
.rv-alert.warn > .ic { color: var(--warn-deep); }
.rv-alert.bad { border-left-color: var(--bad); background: var(--bad-wash); }
.rv-alert.bad > .ic { color: var(--bad-deep); }

/* ===================== DROPDOWN MENU (Phase 17.8.3) =====================
   General-purpose menu on native <details>/<summary> (no JS, accessible). Same
   visual language as .rv-filter-menu but for actions: items, a destructive
   variant, a heading and a separator. */
.rv-menu { position: relative; display: inline-block; }
.rv-menu > summary {
  list-style: none;
  display: inline-flex; align-items: center; gap: 7px;
  cursor: pointer;
}
.rv-menu > summary::-webkit-details-marker { display: none; }
.rv-menu__chev { font-size: 9px; color: var(--note); transition: transform var(--dur-fast) var(--ease-out); }
.rv-menu[open] > summary .rv-menu__chev { transform: rotate(180deg); }
.rv-menu__panel {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 120;
  min-width: 200px;
  width: max-content;
  max-width: 320px;
  max-height: 340px;
  overflow-y: auto;
  background: var(--bg-surface);
  border: 1px solid var(--line-1);
  border-radius: var(--radius);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.10);
  padding: 5px;
  display: flex; flex-direction: column; gap: 1px;
}
.rv-menu__panel--right { left: auto; right: 0; }
.rv-menu__heading {
  font-family: var(--font-mono); font-size: 9px; font-weight: 500;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--note);
  padding: 6px 10px 5px; margin-bottom: 2px;
  border-bottom: 0.5px solid var(--line-hairline);
  flex-shrink: 0;
}
.rv-menu__item {
  appearance: none; -webkit-appearance: none;
  background: transparent; border: 0; width: 100%;
  display: flex; align-items: center; gap: 9px;
  padding: 7px 10px; border-radius: 2px;
  font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.04em; line-height: 1.45;
  color: var(--ink-soft); text-align: left; text-decoration: none; cursor: pointer;
  flex-shrink: 0;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.rv-menu__item .ic { width: 15px; height: 15px; flex-shrink: 0; }
.rv-menu__item:hover, .rv-menu__item:focus-visible { background: var(--paper-2); color: var(--ink); outline: none; }
.rv-menu__item--danger { color: var(--bad-deep); }
.rv-menu__item--danger:hover, .rv-menu__item--danger:focus-visible { background: var(--bad-wash); color: var(--bad-deep); }
.rv-menu__sep { height: 0.5px; background: var(--line-hairline); margin: 4px 0; border: 0; }

/* ===================== IN-FLOW DISCLOSURE (Phase 25.8.2) =====================
   `.rv-menu`'s in-flow sibling: a block that minimizes IN the layout rather than
   opening OVER it, so the height it gives up is handed to whatever shares its
   flex column. Native <details>/<summary> — the toggle, the keyboard handling,
   and the ARIA all come from the element, and this file only styles it. Same
   `list-style: none` + hidden `::-webkit-details-marker` + rotating `__chev`
   mechanics the two popover menus already use, so the three read as one family.

   Built for the contact form, where the question block is 224px of the panel and
   the message box underneath it is the thing that wants the room. Any surface
   with a long optional block above a growable one wants this.

   No cyan. A disclosure is chrome, not the page's action; the 2% budget belongs
   to whatever the surface's single CTA is. */
.rv-collapse > summary {
  list-style: none;
  display: flex; align-items: center; gap: 7px;
  cursor: pointer;
  /* The label is small mono caps and would otherwise present a hairline-thin
     click target; the padding is the hit area, and the negative margin keeps it
     optically flush with the fields above and below. */
  padding: 2px 0; margin: -2px 0;
}
.rv-collapse > summary::-webkit-details-marker { display: none; }
.rv-collapse > summary:focus-visible { outline: none; box-shadow: var(--glow-cyan-ring); border-radius: 3px; }
.rv-collapse__chev {
  font-size: 9px; color: var(--note);
  transition: transform var(--dur-fast) var(--ease-out);
}
.rv-collapse[open] > summary .rv-collapse__chev { transform: rotate(180deg); }
/* The answer echo. Shown ONLY while closed: a minimized block that hides what it
   was answered with reads as data loss, and the same text beside an open block
   is just a duplicate of the checked row. `margin-left: auto` parks it on the
   right so the summary row reads question → chevron → answer regardless of how
   long either string is. */
.rv-collapse__answer {
  margin-left: auto;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--fg-2);
  font-family: var(--font-body); font-size: 12px;
  letter-spacing: 0; text-transform: none;
}
.rv-collapse[open] > summary .rv-collapse__answer { display: none; }
/* The disclosed body. `min-height: 0` because a <details>'s content is regularly
   a flex or scroll child and its automatic minimum would otherwise beat whatever
   the surface sized it to (the 25.8.0 lesson, one register up). */
.rv-collapse__body { min-height: 0; }

/* ===================== SYSTEM DIALOGS (Phase 17.9.0) =====================
   Styled drop-from-top replacements for native confirm/alert/prompt. Toasts
   reuse the .rv-alert tone language; confirm/prompt reuse the modal panel. */
.rv-toaster {
  position: fixed;
  top: 14px; right: 14px;
  z-index: 1200;
  display: flex; flex-direction: column; gap: 10px;
  width: min(360px, calc(100vw - 28px));
  pointer-events: none;
}
.rv-toast {
  pointer-events: auto;
  box-shadow: var(--shadow-2);
  opacity: 0; transform: translateY(-14px);
  transition: opacity 0.16s var(--ease-out), transform 0.24s var(--ease-out);
}
.rv-toast.is-in { opacity: 1; transform: translateY(0); }
.rv-toast.is-out { opacity: 0; transform: translateY(-14px); }
.rv-toast__close {
  appearance: none; -webkit-appearance: none;
  background: transparent; border: 0; cursor: pointer;
  margin-left: auto; align-self: flex-start;
  font-family: var(--font-body); font-size: 16px; line-height: 1;
  color: var(--note); padding: 0 0 0 6px;
}
.rv-toast__close:hover { color: var(--ink); }

/* Phase 12.24.1 — the drop-from-top offset lives on the OVERLAY, sized against
   the real window (`--rv-fill-h`, 18.0.0), never as a `vh` on the panel: under
   the shell's CSS zoom a `vh` is inflated 1.1–1.4×, and the panel carries no
   envelope of its own — it relies on `.rv-modal__panel { max-height: 100% }`
   (12.24.0), which resolves against this box's CONTENT height, so the offset
   here plus the 24px bottom gutter is exactly what bounds a tall checklist.
   (A max-height on `.rv-confirm__panel` would lose the cascade to
   `.rv-modal__panel`, which sits later at equal specificity.) */
.rv-confirm {
  position: fixed; inset: 0; z-index: 1300;
  display: flex; align-items: flex-start; justify-content: center;
  padding: calc(var(--rv-fill-h, 100vh) * 0.12) 24px 24px;
}
.rv-confirm__backdrop { position: absolute; inset: 0; background: rgba(20, 20, 18, 0.45); opacity: 0; transition: opacity 0.16s var(--ease-out); }
.rv-confirm.is-in .rv-confirm__backdrop { opacity: 1; }
.rv-confirm__panel {
  position: relative;
  width: 92%; max-width: 440px;
  opacity: 0; transform: translateY(-24px);
  transition: opacity 0.16s var(--ease-out), transform 0.24s var(--ease-out);
}
.rv-confirm.is-in .rv-confirm__panel { opacity: 1; transform: translateY(0); }
.rv-confirm.is-out .rv-confirm__panel { opacity: 0; transform: translateY(-24px); }
.rv-confirm__title { margin: 0 0 8px; }
.rv-confirm__body { font-size: 13px; color: var(--fg-2); margin: 0 0 16px; line-height: 1.5; }
/* ---- the submission panel (Phase 12.16.5) --------------------------------
   Moved here from `walker.css` verbatim. It began as the guided review's
   send-step popup, and Phase 12.16.5 made it a portal component so the
   no-sales confirmation opens the SAME panel — that one fires from the
   dashboard and the contract page, where the walker's stylesheet is never
   loaded, which is why its destinations had been rendering through the
   checklist's one-line item list with no draft at all. */
/* Phase 12.12.0 — the submission popup. Built on the shared `.rv-modal` /
   `.rv-modal__panel` primitive (portal.css), so only the two-column interior is
   defined here. Left = how to submit; right = the drafted email, one copyable
   field per block. No new tokens: the field wells reuse the same
   --line-hairline / --paper-2 pairing as the contract config dialog's palette. */
/* A tall, fixed-shape panel: the operator reads the instructions and the draft
   side by side, and a long message must not squeeze the actions off-screen. The
   panel itself never scrolls — it is a flex column whose middle row (the two
   columns) absorbs the height, and the long-form blocks inside scroll instead.
   The WIDTH is shared with the contract page's reporting-instructions dialog
   (`dialog.cd-rcfg` in _cra/_styles.html) so the two surfaces the operator moves
   between line up — keep that in step if either changes. The HEIGHTS diverged
   deliberately: this panel holds two prose columns and wants the room, while
   that dialog was running tall only because its token palette dictated the
   height (now scrolled in its own box). */
/* Phase 12.24.0 (#863) — no `max-height` or `overflow` here on purpose. Both
   had been dead since 12.16.5 moved this block from walker.css (which loaded
   AFTER portal.css and won) to a spot ABOVE `.rv-modal__panel`, whose own
   `max-height` / `overflow-y` beat these at equal specificity. The shared
   rule owns the envelope now and fits the window under zoom (see the note on
   `.rv-modal`); a converted copy left here would be a second envelope waiting
   to win the cascade the next time a rule moves. The reserved height is a
   percentage of the fixed overlay for the same reason `vh` left. */
.rv-submit {
  max-width: 980px; width: min(980px, 94vw);
  min-height: min(620px, 84%);
  display: flex; flex-direction: column;
}
/* The unconfigured state: no two-column body at all, just an explanation of what
   the operator still has to do and where an admin records it. The panel gives up
   its reserved two-column height here — holding 620px around two paragraphs reads
   as a loading failure rather than a deliberate empty state. */
.rv-submit--unset { min-height: 0; }
.rv-submit__unset { display: flex; flex-direction: column; gap: 10px; margin-top: 16px; }

.rv-submit__head { display: flex; flex-direction: column; gap: 4px; }
.rv-submit__title { margin: 0; font-size: 15px; letter-spacing: 0.01em; color: var(--ink); }
.rv-submit__sub { margin: 0; font-size: 11.5px; line-height: 1.45; color: var(--note); }
/* Phase 12.14.4 — the standing advisory: check the generated report, and nobody
   but the operator is going to send it. Deliberately NOT a `--warn-wash` banner.
   It renders on EVERY visit to this step, configured or not, and a permanent
   amber is how you teach an operator to stop seeing amber — which would cost us
   the alarm colours on the day something is actually wrong. Weight instead comes
   from a hairline rule and ink one step darker than the subtitle above it, so it
   separates from that grey without spending hue (or any of the 2% cyan budget).
   Shape follows `.rv-confirm__body` — the no-sales checklist's equivalent line —
   so Minor 12.16's convergence of the two panels is a rename, not a redesign. */
/* Phase 12.16.4 — this rule moved to portal.css as `.rv-gate-note`, shared with
   the checklist's standing note: one treatment, two panels, so they cannot drift
   apart. The declarations are unchanged — the convergence really was a rename,
   as 12.14.4 designed it to be. */
/* Phase 12.14.2 — one scrolling column: the contract-level instructions, then
   the destination cards in order. The 12.12.0 two-column grid ("How to submit" /
   "The email") is gone — a portal contract has no email half to leave empty. */
.rv-submit__body {
  display: flex; flex-direction: column; gap: 16px;
  margin-top: 16px;
  flex: 1 1 auto; min-height: 0; overflow-y: auto;   /* absorb the panel's spare height */
  scrollbar-width: thin; scrollbar-color: var(--gray-3) transparent;
}
@media (max-width: 720px) {
  /* Narrow: the panel gives up its fixed height and scrolls as one. */
  .rv-submit { min-height: 0; overflow-y: auto; }
  .rv-submit__value--body, .rv-submit__prose { max-height: none; }
}
/* The destination cards. Each is a paper card with a mono method mark + label
   head, the link (or address) and the steps. `--email` / `--portal` variants
   exist for a future tint; today they share one look so the list reads as one
   sequence. Phase 12.24.0 (#863) — the draft blocks and the handoff row moved
   OUT of the card into the What to send section's `.rv-submit__draft`, which
   is the same paper card (grouped selectors, not a copy) headed by the
   destination the draft belongs to. */
.rv-submit__dests { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }
.rv-submit__dest, .rv-submit__draft {
  display: flex; flex-direction: column; gap: 8px;
  padding: 10px 12px;
  border: 1px solid var(--line-1); border-radius: 3px;
  background: var(--paper);
}
.rv-submit__desthead, .rv-submit__drafthead { display: flex; align-items: baseline; gap: 10px; }
/* The draft head parks its address on the right so the row reads
   ordinal · label … address, the same shape as a section summary. The mono
   face without `.mono`'s uppercase — that would mangle the address. */
.rv-submit__drafthead { flex-wrap: wrap; }
.rv-submit__draftaddr { margin-left: auto; font-size: 11.5px; }
.rv-submit__method { font-size: 9px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--note); }
.rv-submit__destlabel { font-size: 13px; color: var(--ink); }
.rv-submit__steps { display: flex; flex-direction: column; gap: 4px; }
.rv-submit__destacts { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 10px; }
/* An inline link inside authored prose (autolinked URL, the configure link). */
.rv-submit__inline { color: var(--color-cyan); text-decoration: none; word-break: break-all; }
.rv-submit__inline:hover { text-decoration: underline; }
.rv-submit__col { display: flex; flex-direction: column; gap: 8px; min-width: 0; min-height: 0; flex: 0 0 auto; }
.rv-submit__collabel {
  font-size: 9px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--note);
}
/* Phase 12.24.0 (#863) — the three collapsible sections (How to submit /
   Where it goes / What to send) behind the always-visible values block. Each
   is the portal's `.rv-collapse` disclosure (25.8.2) wearing the panel's own
   row: the mono column label one step up in size and ink so it reads as a
   heading rather than a field caption, the chevron, and the closed-only echo
   parked right. A hairline separates sections; the values block above them
   is not a section and keeps its own label, so the first section after it
   still draws its rule. The summary's padding is the hit area (the 25.8.2
   negative-margin trick is for a field caption flush with inputs, not for a
   section head). No cyan: a disclosure is chrome, and the panel's single
   accent is its primary action. */
.rv-submit__section {
  flex: 0 0 auto; min-height: 0;
  border-top: 1px solid var(--line-hairline); padding-top: 10px;
}
.rv-submit__section:first-child { border-top: 0; padding-top: 0; }
.rv-submit__section > summary { padding: 4px 0; margin: 0; gap: 8px; }
/* The label never wraps or yields: the echo is the flexible part of the row
   (it ellipsizes), and a two-line "HOW TO / SUBMIT" read as a broken head. */
.rv-submit__section > summary .rv-submit__collabel { font-size: 10px; color: var(--ink-2); white-space: nowrap; flex: 0 0 auto; }
.rv-submit__section > summary .rv-collapse__chev { flex: 0 0 auto; }
.rv-submit__sectionbody { display: flex; flex-direction: column; gap: 8px; margin-top: 8px; }
.rv-submit__prose {
  margin: 0; font-size: 12.5px; line-height: 1.55; color: var(--ink-2);
  white-space: pre-wrap;   /* authored guidance — keep its line breaks */
  overflow-y: auto; min-height: 0;
  scrollbar-width: thin; scrollbar-color: var(--gray-3) transparent;
}
.rv-submit__empty { margin: 0; font-size: 12px; line-height: 1.5; color: var(--fg-3); }
.rv-submit__field {
  display: flex; flex-direction: column; gap: 4px;
  padding: 8px 10px;
  border: 1px solid var(--line-hairline); border-radius: 3px;
  background: var(--paper-2);
}
.rv-submit__fieldhead { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.rv-submit__fieldlabel { font-size: 9px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--note); }
.rv-submit__copy {
  font-family: var(--font-mono); font-size: 9.5px; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--fg-2);
  background: none; border: none; padding: 0; cursor: pointer;
  transition: color var(--dur-base) var(--ease-out);
}
.rv-submit__copy:hover { color: var(--color-cyan); }
/* The mono FACE without the `.mono` HUD treatment — that class uppercases, which
   mangles an address. Same reasoning for .rv-walker-submit__addr on the card. */
.rv-submit__addr {
  font-family: var(--font-mono); font-size: 12.5px; letter-spacing: 0.02em;
  color: var(--color-cyan); text-decoration: none; word-break: break-all;
}
.rv-submit__addr:hover { text-decoration: underline; }
.rv-submit__value { margin: 0; font-size: 12px; line-height: 1.5; color: var(--ink); }
/* Phase 8.185.1 (#762) — the report values column. A figure is read off and
   retyped into a vendor's form, so it gets tabular numerals and a size above the
   panel's body text; the note under it is a hint, so it takes the 11.5px/--note
   role the 12.16.5 text convention set. No new color: the value is --ink like
   every other field value, and the Copy control stays chrome. */
/* No width/basis rule here on purpose: `.rv-submit__body` is a COLUMN flex, so a
   `flex-basis` would set the section's HEIGHT and leave a dead gap under the
   figures (it did — caught in the browser). The section stacks and sizes itself
   like `--how` does. */
.rv-submit__value--figure {
  font-size: 17px; line-height: 1.35; letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}
.rv-submit__valnote {
  margin: 4px 0 0; font-size: 11.5px; line-height: 1.5; color: var(--note);
}
/* The message block is the one that grows — it takes whatever height the panel
   has left after the address and subject, so a long draft gets real room and
   scrolls inside itself instead of pushing the actions row away. */
.rv-submit__field--body { flex: 1 1 auto; min-height: 0; }
.rv-submit__value--body {
  color: var(--ink-2);
  white-space: pre-wrap;   /* the draft is authored text — keep its line breaks */
  flex: 1 1 auto; min-height: 140px; overflow-y: auto;
  scrollbar-width: thin; scrollbar-color: var(--gray-3) transparent;
}
.rv-submit__prose::-webkit-scrollbar,
.rv-submit__value--body::-webkit-scrollbar { width: 8px; }
.rv-submit__prose::-webkit-scrollbar-track,
.rv-submit__value--body::-webkit-scrollbar-track { background: transparent; }
.rv-submit__prose::-webkit-scrollbar-thumb,
.rv-submit__value--body::-webkit-scrollbar-thumb { background: var(--gray-3); border-radius: 4px; }
.rv-submit__acts {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 10px;
  margin-top: 18px; padding-top: 12px; border-top: 1px solid var(--line-hairline);
  flex: 0 0 auto;   /* pinned — a long message scrolls, these never move */
}

/* Phase 12.16.4 — the two confirmations' standing note, one rule for both. The
   submit panel's `.rv-submit__notice` was deliberately shaped after
   `.rv-confirm__body` in 12.14.4 so this convergence would be a rename rather
   than a redesign; it is. Weight comes from a hairline rule and ink one step
   darker than the subtitle above it — never from hue, which would spend the
   2% cyan budget or teach an operator to stop seeing amber. */
.rv-gate-note {
  margin: 2px 0 0; padding-left: 8px;
  border-left: 2px solid var(--line-hairline);
  font-size: 12px; line-height: 1.5; color: var(--ink-2);
}
/* Phase 8.202.1 (#756) — a gate NOTICE: a fact shown at the moment of
   confirming that is not itself confirmed (credits landing in a later report).
   The gate note's shape with the warn ink and a warn hairline, so it reads as
   "mind this" beside the note's "how this works" — no wash, no badge: it is one
   sentence the operator reads, not an action. */
.rv-gate-notice {
  margin: 2px 0 0; padding-left: 8px;
  border-left: 2px solid var(--warn-deep);
  font-size: 12px; line-height: 1.5; color: var(--warn-deep);
}
.rv-confirm__input { width: 100%; margin-bottom: 4px; }

/* Phase 12.14.3 — rvChecklist: a confirmation that waits for every line. The
   message is shown to copy (the portal never sends); destinations listed; the
   primary button enables only once all boxes are ticked. */
.rv-confirm--checklist .rv-confirm__panel { width: min(680px, 92vw); }
.rv-checklist__head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin: 4px 0 4px; }
.rv-checklist__label { font-size: 9px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--note); }
.rv-checklist__copy {
  font-family: var(--font-mono); font-size: 9.5px; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--fg-2); background: none; border: none; padding: 0; cursor: pointer;
}
.rv-checklist__copy:hover { color: var(--color-cyan); }
.rv-checklist__message {
  margin: 0 0 12px; padding: 10px 12px; font: inherit; font-size: 12.5px; line-height: 1.5;
  white-space: pre-wrap; word-break: break-word; color: var(--ink-2);
  border: 1px solid var(--line-hairline); border-radius: 3px; background: var(--paper-2);
  max-height: calc(var(--rv-fill-h, 100vh) * 0.38); overflow-y: auto;   /* 12.24.1 — not `vh`: zoom inflates it */
}
.rv-checklist__items { list-style: none; margin: 0 0 12px; padding: 0; display: flex; flex-direction: column; gap: 4px; }
/* Phase 12.16.0 — `break-all` broke EVERY word at the character, not just the
   long URL it was added for: a destination row rendered "PORT / AL" and
   "FooSolutions partn / er portal". `anywhere` breaks inside a word only when
   the word cannot fit, so the URL still wraps and prose still breaks at spaces
   (and, unlike `break-word`, it shrinks the flex item's min-content width, which
   is what keeps the row inside the panel). The method chip is a HUD micro-label
   and never breaks. */
.rv-checklist__item { display: flex; align-items: baseline; gap: 8px; font-size: 12.5px; overflow-wrap: anywhere; }
.rv-checklist__method { font-size: 9px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--note); white-space: nowrap; }
.rv-checklist__where { font-family: var(--font-mono); font-size: 11.5px; color: var(--color-cyan); text-decoration: none; }
.rv-checklist__where:hover { text-decoration: underline; }
.rv-checklist__lines { display: flex; flex-direction: column; gap: 8px; margin: 0 0 4px; }
.rv-checklist__line { display: flex; align-items: flex-start; gap: 8px; font-size: 13px; line-height: 1.45; color: var(--fg-1); cursor: pointer; }
.rv-checklist__line input { margin-top: 3px; }
/* Phase 12.26.0 — run facts beside the lines (the sales-order numbers). The
   portal's `.rv-collapse` disclosure wearing the checklist's paper card, folded
   to its count unless the gate opens it; values as mono chips on the canvas so
   they read as data, not links (`--ink-2`, never cyan — the panel's one accent
   is Confirm); the copy control is `.rv-checklist__copy`, already declared
   above. Height caps against `--rv-fill-h` (12.24.1: never `vh`) and scrolls,
   so a large book cannot push the acknowledgement lines out of the panel. */
.rv-checklist__facts {
  margin: 0 0 12px; padding: 8px 10px;
  border: 1px solid var(--line-hairline); border-radius: 3px; background: var(--paper-2);
}
.rv-checklist__factsum { padding: 0; margin: 0; }
.rv-checklist__factsum .rv-collapse__answer { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--note); }
.rv-checklist__factbody { display: flex; flex-direction: column; gap: 8px; margin-top: 8px; }
.rv-checklist__factacts { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.rv-checklist__factcount { font-size: 11.5px; color: var(--note); }
.rv-checklist__values {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: 6px;
  max-height: calc(var(--rv-fill-h, 100vh) * 0.25); overflow-y: auto;
}
.rv-checklist__value {
  display: inline-flex; align-items: baseline; gap: 8px;
  padding: 3px 8px;
  border: 1px solid var(--line-hairline); border-radius: 3px; background: var(--bg-canvas);
}
.rv-checklist__valuetext { font-family: var(--font-mono); font-size: 11.5px; letter-spacing: 0.02em; color: var(--ink-2); user-select: all; }
.rv-checklist__empty { margin: 0; font-size: 12px; line-height: 1.5; color: var(--fg-3); }
html.rv-dialog-open { overflow: hidden; }

/* ===================== CUSTOM SELECT (Phase 17.10.0) =====================
   Progressive enhancement of native <select.input> (rv-select.js). The native
   select is kept (form-bound) but visually hidden; a styled trigger + listbox
   panel (same language as .rv-filter-menu) drive it. */
.rv-select { position: relative; display: inline-flex; max-width: 100%; vertical-align: middle; }
.rv-select--block { display: flex; width: 100%; }
/* Phase 17.10.1 — the geometry is enforced (!important): page-level rules that
   size a select for the no-JS fallback (e.g. `.cra-cycle-select { width: 150px }`)
   share this rule's specificity and load after portal.css, so without enforcement
   they re-inflate the hidden native — an invisible absolutely-positioned box that
   overhangs the wrapper and can push a phantom scrollbar onto the page. */
.rv-select__native {
  position: absolute !important;
  width: 1px !important; height: 1px !important;
  min-width: 0 !important; min-height: 0 !important;
  padding: 0 !important; margin: -1px !important;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
  opacity: 0; pointer-events: none;
}
.rv-select__trigger {
  display: inline-flex; align-items: center; justify-content: space-between; gap: 8px;
  text-align: left; cursor: pointer;
}
.rv-select--block .rv-select__trigger { width: 100%; }
.rv-select__label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rv-select__label.is-placeholder { color: var(--note); }
.rv-select__chev { flex-shrink: 0; font-size: 9px; color: var(--note); transition: transform var(--dur-fast) var(--ease-out); }
.rv-select.is-open .rv-select__chev { transform: rotate(180deg); }
.rv-select__trigger:focus-visible { outline: none; box-shadow: var(--glow-cyan-ring); }
.rv-select.is-open .rv-select__trigger { border-color: var(--color-cyan); }
.rv-select__panel {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 130;
  min-width: 100%; width: max-content; max-width: 360px;
  max-height: 300px; overflow-y: auto;
  background: var(--bg-surface); border: 1px solid var(--line-1); border-radius: var(--radius);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.10);
  padding: 5px; display: flex; flex-direction: column; gap: 1px;
}
/* Phase 12.11.0 — themed scrollbar for the overflowing option list, mirroring the
   design-system `.scroll.tight` recipe (portal.css `.scroll` block) rather than
   leaving the native OS bar (#299). Tokens carry their own dark-mode values, so
   this reads correctly in both themes; the 6px thumb suits the compact 300px panel. */
.rv-select__panel { scrollbar-width: thin; scrollbar-color: var(--gray-3) transparent; }
.rv-select__panel::-webkit-scrollbar { width: 6px; height: 6px; }
.rv-select__panel::-webkit-scrollbar-track { background: transparent; }
.rv-select__panel::-webkit-scrollbar-thumb { background: var(--gray-3); border: none; border-radius: 0; }
.rv-select__panel::-webkit-scrollbar-thumb:hover { background: var(--gray-3); }
.rv-select__panel::-webkit-scrollbar-corner { background: transparent; }
.rv-select__option {
  flex-shrink: 0; line-height: 1.45;
  padding: 7px 10px; border-radius: 2px;
  font-family: var(--font-body); font-size: 13px;
  color: var(--ink-soft); cursor: pointer; white-space: nowrap;
}
.rv-select__option.is-active { background: var(--paper-2); color: var(--ink); }
.rv-select__option[aria-selected="true"] { color: var(--ink); box-shadow: inset 2px 0 0 var(--color-cyan); }
.rv-select__option[aria-disabled="true"] { color: var(--gray-3); cursor: default; }
.rv-select.is-disabled { opacity: 0.55; pointer-events: none; }

/* === Phase 5.1 — Customizable hub widgets === */
.rv-hub-grid [data-widget-id] {
  position: relative;
}
.rv-widget-chrome {
  display: none;
  position: absolute;
  top: 6px;
  right: 6px;
  gap: 4px;
  z-index: 10;
  align-items: center;
  background: rgba(248, 248, 246, 0.92);
  border: 0.5px solid var(--line-hairline);
  padding: 3px;
  border-radius: 2px;
}
.rv-hub-grid[data-rearrange-mode="1"] .rv-widget-chrome { display: flex; }
.rv-hub-grid[data-rearrange-mode="1"] [data-widget-id] {
  cursor: grab;
  outline: 1px dashed var(--color-cyan);
  outline-offset: -3px;
}
.rv-hub-grid[data-rearrange-mode="1"] [data-widget-id]:active { cursor: grabbing; }
.rv-widget-dragging { opacity: 0.45; }
.rv-widget-dragover { outline-color: var(--color-cyan); outline-style: solid !important; outline-offset: -3px; }
.rv-widget-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  height: 22px;
  padding: 0 6px;
  border: 0.5px solid var(--line-hairline);
  background: var(--paper);
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  color: var(--ink);
  cursor: pointer;
  text-transform: uppercase;
}
.rv-widget-btn:hover { border-color: var(--ink); }

/* === Phase 5.1 — Shared portal modal === */
/* Phase 12.24.0 (#863) — the panel is sized against the OVERLAY, never in
   `vh`. The shell scales the page with CSS `zoom` on HD/2K screens (18.0.0),
   and `zoom` inflates viewport units instead of shrinking the viewport: on a
   2K monitor at 1.4× a `max-height: 86vh` rendered 1,523px tall in a 1,265px
   window, and the send-step popup hung 129px past BOTH window edges. The
   overlay is `position: fixed; inset: 0`, so its box is the visible window in
   the zoomed element's own px at every zoom, on every page — including the
   styleguide, where the shell script (and its `--rv-fill-h`) never runs. A
   percentage of it is right by construction; the 24px padding is the gutter
   that keeps any panel off the edges. */
.rv-modal {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.rv-modal[hidden] { display: none; }
.rv-modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(20, 20, 18, 0.45);
}
.rv-modal__panel {
  position: relative;
  background: var(--paper);
  width: 92%;
  max-height: 100%;   /* of the overlay's content box — see the note above */
  overflow-y: auto;
  /* Phase 17.8.3 — give dialog content breathing room + the shared radius/border
     so it reads as a card, not edge-to-edge text. */
  padding: 22px 24px;
  border: 1px solid var(--line-1);
  border-radius: var(--radius);
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.18);
}
.rv-modal__panel--md { max-width: 540px; }   /* Phase 7.5.0 — constrained dialog */
/* Phase 17.8.3 — internal rhythm for dialog content (used by the styleguide
   example and real dialogs): title, body copy, then an actions row. */
.rv-modal__panel > :first-child { margin-top: 0; }
.rv-modal__actions { display: flex; gap: 10px; margin-top: 18px; }


/* === Phase 5.2 — Docs tree nav === */
.rv-docs-nav { display: flex; flex-direction: column; gap: 12px; }
.rv-docs-nav__group { display: flex; flex-direction: column; gap: 4px; padding-top: 8px; border-top: 0.5px solid var(--line-hairline); }
.rv-docs-nav__group:first-child { padding-top: 0; border-top: 0; }
.rv-docs-nav__header {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.18em;
  color: var(--ink);
  text-transform: uppercase;
  font-weight: 600;
  margin-bottom: 2px;
}
.rv-docs-nav__list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.rv-docs-nav__item {
  display: flex;
  align-items: baseline;
  gap: 6px;
  white-space: nowrap;
}
.rv-docs-nav__tree {
  color: var(--note);
  font-family: var(--font-mono);
  font-size: 11px;
  user-select: none;
  flex-shrink: 0;
}
.rv-docs-nav__link {
  color: var(--note);
  text-decoration: none;
  font-size: 12px;
  line-height: 1.6;
  overflow: hidden;
  text-overflow: ellipsis;
}
.rv-docs-nav__link:hover { color: var(--ink); }
.rv-docs-nav__link.is-active { color: var(--ink); font-weight: 500; }
.rv-docs-nav__empty {
  color: var(--gray-3);
  font-family: var(--font-mono);
  font-size: 10px;
}

/* === Phase 15.5.0 — reusable doc component: a markdown document rendered with a
   sticky tree nav (.rv-docs-nav, above) + scrollspy. Shared by /help (full shell)
   and /legal (stripped shell). Promoted out of help/index.html's scoped <style>. === */
.rv-docs-layout {
  padding: 26px 32px;
  display: grid;
  grid-template-columns: 220px 1fr;
  gap: 32px;
  align-items: start;
}
.rv-docs-layout .rv-docs-nav {
  position: sticky;
  top: 16px;
  align-self: start;
  max-height: calc(100vh - 32px);
  overflow-y: auto;
}
.rv-docs-main { max-width: 760px; min-width: 0; }   /* readability cap inside a full-width layout */
.rv-docs-lead { color: var(--fg-2); margin: 0 0 8px; max-width: 60ch; }
.rv-docs-section { scroll-margin-top: 16px; padding-top: 8px; }
.rv-docs-section + .rv-docs-section {
  margin-top: 28px;
  padding-top: 28px;
  border-top: 0.5px solid var(--line-hairline);
}
.rv-docs-section__eyebrow {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--note);
  margin: 0 0 6px;
}
/* Rendered markdown body. */
.rv-docs-body h1, .rv-docs-body h2, .rv-docs-body h3 { font-family: var(--font-display); color: var(--ink); }
.rv-docs-body h1 { font-size: 24px; font-weight: 600; margin: 0 0 8px; }
.rv-docs-body h2 { font-size: 18px; font-weight: 600; margin: 20px 0 8px; }
.rv-docs-body h3 { font-size: 15px; font-weight: 600; margin: 14px 0 6px; }
.rv-docs-body p { color: var(--fg-2); line-height: 1.6; margin: 0 0 12px; }
.rv-docs-body ul, .rv-docs-body ol { color: var(--fg-2); line-height: 1.6; padding-left: 20px; }
.rv-docs-body li { margin-bottom: 4px; }
.rv-docs-body blockquote { margin: 0 0 14px; padding: 8px 14px; border-left: 2px solid var(--gray-2); color: var(--note); }
.rv-docs-body blockquote p { margin: 0; color: var(--note); }
.rv-docs-body code { font-family: var(--font-mono); font-size: 12px; background: var(--paper-2); padding: 1px 5px; }
.rv-docs-body pre { background: var(--paper-2); padding: 12px; overflow-x: auto; font-family: var(--font-mono); font-size: 12px; }
.rv-docs-body pre code { background: transparent; padding: 0; }
.rv-docs-body a { color: var(--color-cyan-dim); }
.rv-docs-body table { border-collapse: collapse; font-size: 13px; margin: 12px 0; }
.rv-docs-body th, .rv-docs-body td { padding: 6px 10px; border-bottom: 0.5px solid var(--line-hairline); text-align: left; }
.rv-docs-body th { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--note); }

/* === Phase 5.2 — Admin settings hub === */
.rv-admin-shell {
  display: grid;
  grid-template-columns: 120px 1fr;   /* Phase 7.5.0 — narrower admin rail */
  gap: 32px;
  align-items: flex-start;
  padding: 26px 32px;
}
.rv-admin-main {
  display: flex;
  flex-direction: column;
  gap: 18px;
  min-width: 0;
}
.rv-admin-nav { display: flex; flex-direction: column; gap: 4px; }
.rv-admin-nav__header {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.18em;
  color: var(--ink);
  text-transform: uppercase;
  font-weight: 600;
  margin-bottom: 6px;
  padding-bottom: 6px;
  border-bottom: 0.5px solid var(--line-hairline);
}
.rv-admin-nav__link {
  display: block;
  padding: 5px 0;
  color: var(--note);
  text-decoration: none;
  font-family: var(--font-mono);
  font-size: 12px;
}
.rv-admin-nav__link:hover { color: var(--ink); }
.rv-admin-nav__link.is-active { color: var(--ink); font-weight: 500; }

/* Phase 14.7.0 — reusable 2-up form grid that collapses to one column on small
   screens (replaces inline `grid-template-columns: 1fr 1fr` that can't respond). */
.rv-form-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
@media (max-width: 640px) {   /* --bp-sm */
  .rv-form-grid-2 { grid-template-columns: 1fr; }
}

/* Phase 25.6.1 — the contact page's form + requests-panel pair. A page-scoped
   layout (only /contact composes it), kept here rather than inline because the
   collapse needs a media query. The two panels split the page evenly, stretch
   to equal height (grid default), and the layout grows to the bottom of the
   viewport (`flex: 1` inside the shell's flex-column main). The panel doesn't
   resize on tool selection because the MESSAGE field absorbs the difference —
   it grows into whatever space the follow-up questions aren't using. Below
   --bp-md the requests panel drops under the form (the form is the reason
   most visitors came). */
/* Phase 25.8.0 — the layout gained a third panel, and it stacks INSIDE the
   right column rather than taking a full-width row of its own.
   A full-width row beneath the two columns was tried first and rejected on the
   running page: the form is tall, so it pushed the requests table off the
   bottom of the viewport and cost 25.6.1 the property it was built for — the
   table being visible the moment the page opens. The commitment card is short
   and fixed-height, so stacking it above the table keeps both in view and the
   form's height still sets the page. */
/* The row stays `auto`, deliberately. Pinning it to `minmax(0, 1fr)` was tried
   and reverted: the form column has a hard content minimum (~950px of fields
   that cannot compress), so a pinned row starves it and the overflow spills
   through the shell's `overflow: visible` chain — scrolling the page *worse*
   than before. Letting the form set the row and making the right column fit
   inside it is what actually keeps the page still. */
/* Phase 25.8.0 — `min-height: 0` on the page wrapper is what lets the viewport
   cap in `contact.html` actually reach the panels: a flex item's automatic
   minimum is its content, so without this the wrapper refuses to shrink and the
   cap clips instead of compressing. */
.rv-contact-page { min-height: 0; }

/* Weighted two-pane, the CRA dashboard's convention (`.cd-grid`, Phase 18.5.6):
   a wide data side beside a narrow input side, rather than the 50/50 this page
   started with. The form is a fixed set of fields that does not benefit from
   extra width; the table has five columns including two timestamps that were
   ellipsising mid-date at half the page. `minmax(380px, …)` is the form's floor
   — below it the impact sentences start wrapping to three lines. */
.rv-contact-layout {
  display: grid;
  grid-template-columns: minmax(380px, 1fr) minmax(0, 1.9fr);
  gap: 24px; flex: 1; min-height: 0;
}
/* A grid item defaults to `min-height: auto`, which means "never shrink below my
   content" — so without this the tallest content in either column sets the row
   height and the whole page scrolls. This page is a fixed, viewport-filling
   surface (25.6.1's shape), so the columns must be allowed to shrink and the
   overflow handled inside the one region built to scroll: the table. */
/* Phase 25.8.1 — TRUE OF THE REQUESTS COLUMN ONLY. `contact.html` now floors the
   form column back to `min-content` inside its route-scoped `@media` block,
   because that column has no region built to scroll: its form is incompressible
   below 696px, so shrinking it did not compress anything, it painted the submit
   button outside the card. Do not "restore symmetry" by removing that floor —
   the asymmetry is the fix. The `min-height: 0` here still does its original job
   for `.rv-contact-col`, which does have a scroll region and must keep shrinking
   into it. */
.rv-contact-layout > * { min-height: 0; }
.rv-contact-col {
  display: flex; flex-direction: column; gap: 24px; min-height: 0;
}
/* Two thirds table, one third commitments. Proportional rather than the fixed
   336px this phase first shipped: the requirement that produced that height was
   "the panel must not resize as the data changes", and a share of the column
   satisfies it just as well — the split is a function of the viewport, never of
   how many requests came in. The table leads because it is what a customer
   opens this page to read; the record sits under it. */
/* An explicit two-thirds / one-third of the column rather than `flex: 2` and
   `flex: 1`. Grow factors divide the FREE space, so either card's content
   minimum pulls the result off the ratio — measured 1.81:1 rather than 2:1.
   Sizing both from the column height (less the 24px gap) is exact and says the
   intent outright. */
.rv-contact-col > .rv-contact-requests {
  flex: 0 0 calc((100% - 24px) * 2 / 3); min-height: 0;
}
/* The table's own scroll region. `overflow-x` was already needed for the narrow
   case; `overflow-y` is what absorbs a long request list instead of growing the
   document. */
/* The frame lives on the SCROLL BOX, not on the table. `.rv-table`'s own border
   scrolls with its content, so the top edge disappeared the moment the list was
   scrolled and the bottom edge sat below the fold — the table read as three
   loose sides. Bordering the viewport instead keeps all four edges on screen at
   every scroll position. */
.rv-contact-requests__scroll {
  overflow: auto; min-height: 0; flex: 1;
  border: 1px solid var(--line-1);
  border-radius: var(--radius);
}
.rv-contact-requests__scroll .rv-table { border: 0; }
/* Anchored header: the column names have to survive scrolling 50 rows, or the
   reader loses which date is "submitted" and which is "reply by". `thead th`
   rather than `thead`, because `position: sticky` does not apply to a table
   section in Chrome/Safari — only to its cells. The header already carries an
   opaque `--paper-2` background from `.rv-table thead th`, so rows pass behind
   it rather than through it. */
.rv-contact-requests__scroll thead th { position: sticky; top: 0; z-index: 1; }
/* The header's own bottom rule travels with it; without this the boundary
   between the pinned header and the rows sliding under it disappears. */
.rv-contact-requests__scroll thead th { box-shadow: inset 0 -1px 0 var(--gray-2); }
@media (max-width: 900px) {   /* --bp-md */
  /* Stacked, the page is genuinely taller than the viewport and scrolling it is
     correct — so every shrink-to-fit rule above is released, including the row
     pin, which would otherwise crush three stacked panels into one screen. */
  .rv-contact-layout { grid-template-columns: 1fr; }
  .rv-contact-layout > * { min-height: auto; }
  .rv-contact-col > .rv-contact-requests { flex: 0 0 auto; }
  .rv-contact-requests__scroll { flex: 0 0 auto; }
}

/* Phase 25.8.0 — the §4 commitment record, in counts.
   Ink only — no cyan, and deliberately no status colours either. The figure was
   first tinted by outcome (`--bad-deep` on a miss, `--ok-deep` on a clean
   window) and that was wrong twice over: `--bad-deep` is the ERROR token, so a
   90-day record with one late reply read as something broken right now, and
   colouring the number editorialises a fact the sentence beneath already states
   in words. Same restraint 25.6.1 settled on for the reply-owed column, which
   renders a time and never a verdict. */
/* No `align-self: start` here. It was correct while this card sat in a grid row
   (it kept the short card from stretching to the tall form's height), and it is
   wrong inside `.rv-contact-col`: in a column flex container the cross axis is
   horizontal, so it shrank the card to its content width and left it visibly
   narrower than the table beneath. The column's default `stretch` is what makes
   the two panels share an edge; the card is already content-height because a
   flex item does not grow unless told to. */
/* Compact by requirement, not by taste. The first cut stacked acknowledgement
   over resolution and stood 445px tall, which made the right column taller than
   the form and pushed the whole page into a scrollbar — and this page is a
   fixed, viewport-filling surface (25.6.1). Two figures side by side with their
   qualifications on one shared footnote line comes in around 150px, which keeps
   the form the tallest thing on the page and the page still. */
/* Sized as a share of the column (one third), NOT by its content. The content
   varies with the data — an empty window is three lines, a busy one runs to a
   three-line footnote — and a panel that resized as the figures changed would
   move the table every time a request landed. Anything past the third scrolls
   inside the card. A quiet window therefore leaves whitespace, deliberately: a
   still panel is worth more than a tight one. */
/* `min-height: 0` is load-bearing, not tidying: a flex item's automatic minimum
   size is its content, and that wins over an explicit `height`, so without it
   the card silently grows past the fixed height exactly when the data is busy —
   the one case the fixed height exists for. */
.rv-commit { gap: 10px; flex: 0 0 calc((100% - 24px) / 3); min-height: 0; }
.rv-commit__body {
  display: flex; flex-direction: column; gap: 10px;
  overflow: auto; min-height: 0; flex: 1;
}
.rv-commit__pair {
  display: grid; grid-template-columns: 1fr 1fr; gap: 16px;
}
.rv-commit__group { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.rv-commit__figure {
  font-family: var(--font-display);
  font-size: 20px;
  line-height: 1.15;
  color: var(--fg-1);
  font-variant-numeric: tabular-nums;
}
.rv-commit__caption { font-size: 12.5px; color: var(--fg-2); }
.rv-commit__note { font-size: 12px; color: var(--note); }
.rv-commit__rule { height: 1px; background: var(--gray-2); border: 0; margin: 1px 0; }
.rv-commit__empty { font-size: 13px; color: var(--fg-2); }

/* Phase 25.6.1 — portal file-attach dropzone, ported (compact) from the CRA
   request panel's tool-local `.cra-dropzone` so /contact can mirror the
   attach capability; the CRA copy stays tool-local until a dedicated
   unification pass. */
.rv-dropzone {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  padding: 11px 14px; text-align: center; cursor: pointer;
  border: 1.5px dashed var(--gray-2); border-radius: var(--radius);
  background: var(--paper-2);
  font-size: 12px; color: var(--fg-2);
  transition: border-color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.rv-dropzone:hover, .rv-dropzone:focus-visible { border-color: var(--color-cyan); background: var(--bg-surface); outline: none; }
.rv-dropzone.is-drag { border-color: var(--color-cyan); background: var(--color-cyan-wash); }
.rv-dropzone__file { font-family: var(--font-mono); font-size: 11px; color: var(--fg-1); overflow-wrap: anywhere; }
.rv-dropzone__file[hidden] { display: none; }

/* Phase 14.7.0 — admin settings shell reflow: below --bp-md the 120px + content
   grid stacks (the sub-nav becomes a full-width strip above the content) and the
   page padding tightens below --bp-sm. Desktop (≥900px) is unchanged. */
@media (max-width: 900px) {   /* --bp-md */
  .rv-admin-shell { grid-template-columns: 1fr; gap: 16px; padding: 20px 16px; }
  .rv-admin-nav { flex-direction: row; flex-wrap: wrap; align-items: baseline; gap: 6px 14px; }
  .rv-admin-nav__header { width: 100%; margin-bottom: 2px; }
  .rv-admin-nav__link { padding: 2px 0; }
}
@media (max-width: 640px) {   /* --bp-sm */
  .rv-admin-shell { padding: 16px 12px; }
}

/* === Phase 5.2 — Audit log sticky filter strip === */
/* === Phase 7.7.0 — audit log toolbar + collapsible day groups === */
/* Phase 7.7.2 — the audit header's date-range export form was retired (the page
   now offers a client-side "Export CSV" in the toolbar, mirroring Jobs), so the
   `.rv-audit-head` / `.rv-audit-export` layout rules were removed. */

/* Phase 7.7.2 — the audit log's filter + collapse controls now share the Jobs
   page's generic `.rv-logtable-toolbar` (search · count · filter menus ·
   collapse/expand-all); the bespoke `.rv-audit-controls` / `.rv-audit-collapse`
   rows are retired. The day-group/table look comes from the shared `.rv-logtable`
   block below, so the parallel `.rv-audit-table`/`.rv-audit-group`/`.rv-audit-event`
   rules are gone too — only the export-form layout + the actor subline stay
   audit-specific. */

/* filter menu — a <details> popover, clickable with no free-text input */
.rv-filter-menu { position: relative; }
.rv-filter-menu > summary {
  list-style: none;
  display: inline-flex;
  align-items: center;
  gap: 7px;
}
.rv-filter-menu > summary::-webkit-details-marker { display: none; }
.rv-filter-menu > summary.is-set {
  border-color: var(--color-cyan);
  color: var(--ink);
}
.rv-filter-menu__icon { width: 15px; height: 15px; opacity: 0.75; }
.rv-filter-menu__chev { font-size: 9px; color: var(--note); }
.rv-filter-menu[open] > summary .rv-filter-menu__chev { transform: rotate(180deg); }
.rv-filter-menu__panel {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 120;
  min-width: 220px;
  width: max-content;          /* grow to the widest item so labels never clip */
  max-width: 380px;
  max-height: 340px;
  overflow-y: auto;
  background: var(--bg-surface);          /* #FFFFFF */
  border: 1px solid var(--line-1);        /* #D7D6CE */
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.10);
  padding: 5px;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.rv-filter-menu__heading {
  font-family: var(--font-mono);
  font-size: 9px; font-weight: 500;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--note);                     /* #6B6E73 */
  padding: 6px 10px 5px;
  margin-bottom: 2px;
  border-bottom: 0.5px solid var(--line-hairline); /* #E6E5DD */
  flex-shrink: 0;
}
.rv-filter-menu__item {
  /* works for both <a> and <button> items — reset native button chrome so a
     <button> renders identically to an <a> (no grey box / border). */
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  border: 0;
  width: 100%;
  display: block;
  padding: 7px 10px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  text-align: left;
  cursor: pointer;
  color: var(--fg-2);
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  border-radius: 2px;
  /* Phase 17.9.0 — the panel is a flex column with max-height + scroll; without
     this, a long option list lets flex-shrink compress each row below its text
     height, so the characters overlap and become unreadable. */
  flex-shrink: 0;
  line-height: 1.45;
}
.rv-filter-menu__item:hover { background: var(--paper-2); color: var(--ink); }
.rv-filter-menu__item.is-active {
  background: var(--color-cyan-wash);
  color: var(--ink);
  box-shadow: inset 2px 0 0 var(--color-cyan);
}

/* Phase 7.7.2 — day-group header + caret + collapse now come from the shared
   `.rv-logtable` block (the audit table carries `.rv-logtable` since this phase).
   The former `.rv-audit-table tr.rv-audit-day*` / `.rv-audit-group*` /
   `.rv-audit-event` rules were removed as redundant. */
/* actor job-title subline */
.rv-audit-subline {
  display: block;
  font-family: var(--font-mono);
  font-size: 9px; letter-spacing: 0.08em;
  color: var(--note);                      /* #6B6E73 */
  margin-top: 1px;
}

/* === Phase 5.12.1 — .rv-logtable: collapsible day-grouped log table ===
   A reusable extension of .rv-table for chronological logs (the Jobs page,
   first consumer): one <tbody.rv-logtable__group> per day, each opened by a
   clickable <tr.rv-logtable__day> header row. The collapse rules below are
   inert until JS toggles .is-collapsed on a group (Jobs: Phase 5.12.3). This
   supersedes the bespoke .rv-audit-* day-group classes; migrating audit.html
   onto it is a deferred cleanup. */
/* Only rows that navigate (Jobs page carries data-href) get the pointer; the
   audit log's rows have no detail page and stay default-cursor (Phase 7.7.2). */
.rv-logtable__row[data-href] { cursor: pointer; }
.rv-logtable__row .rv-logtable__view { text-decoration: none; }
.rv-logtable__row:hover .rv-logtable__view { color: var(--color-cyan-dim); }

.rv-logtable tr.rv-logtable__day { cursor: pointer; user-select: none; }
.rv-logtable tr.rv-logtable__day td {
  background: var(--paper-2);              /* #F4F4EE */
  font-family: var(--font-mono);
  font-size: 9.5px; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--note);                      /* #6B6E73 */
  padding: 7px 14px;
  border-bottom: 1px solid var(--gray-2);  /* #C7C6BE */
}
.rv-logtable tr.rv-logtable__day:hover td { background: var(--gray-1); }
.rv-logtable__caret {
  display: inline-block; margin-right: 10px;
  color: var(--gray-3);
  transition: transform var(--dur-fast) var(--ease-out);
}
.rv-logtable__group.is-collapsed .rv-logtable__caret { transform: rotate(0deg); }
.rv-logtable__group:not(.is-collapsed) .rv-logtable__caret { transform: rotate(90deg); }
.rv-logtable__count {
  margin-left: 10px; color: var(--gray-3);
  letter-spacing: 0.1em;
}
/* collapsed groups hide their data rows but keep the day header visible */
.rv-logtable__group.is-collapsed tr.rv-logtable__row { display: none; }

/* Status-as-text colors. `.rv-table .mono { color: var(--note) }` (specificity
   0,2,0, declared later in the file) otherwise clobbers the plain `.mono.ok`
   etc. rules; these 0,3,0 selectors restore the on-theme status color inside
   the table. Mirrors the status text tokens used elsewhere. */
.rv-logtable .mono.ok   { color: var(--ok-deep); }    /* --status-success-deep */
.rv-logtable .mono.warn { color: var(--warn-deep); }  /* --status-warning-deep */
.rv-logtable .mono.bad  { color: var(--bad-deep); }   /* --status-error-deep */
.rv-logtable .mono.cy   { color: var(--color-cyan-dim); }

/* === Phase 5.12.3 — .rv-logtable toolbar (search · filters · collapse · export)
   A thin, generic control bar above an .rv-logtable. Adds layout only; the
   controls themselves reuse .rv-filter-menu*, .btn.ghost.sm, and .input.sm.
   Search grows on the left; the rest sit to the right and wrap together. */
.rv-logtable-toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.rv-logtable-toolbar__search { flex: 1 1 240px; max-width: 360px; }
.rv-logtable-toolbar__controls {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-left: auto;          /* push the menus + buttons to the right edge */
}
.rv-logtable-toolbar__count {
  font-family: var(--font-mono);
  font-size: 10px; letter-spacing: 0.1em;
  color: var(--note);                       /* #6B6E73 */
  white-space: nowrap;
}

/* === Phase 5.3 — Account left rail === */
/* Phase 14.7.0 — the 180px width lived inline on the <aside>; moved here so a
   media query can widen it to a full-width strip on mobile. */
.rv-account-nav { display: flex; flex-direction: column; gap: 4px; width: 180px; flex-shrink: 0; }
.rv-account-nav__header {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.18em;
  color: var(--ink);
  text-transform: uppercase;
  font-weight: 600;
  margin-bottom: 6px;
  padding-bottom: 6px;
  border-bottom: 0.5px solid var(--line-hairline);
}
.rv-account-nav__link {
  display: block;
  padding: 5px 0;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--note);
  text-decoration: none;
}
.rv-account-nav__link:hover { color: var(--ink); }
.rv-account-nav__link.is-active { color: var(--ink); font-weight: 500; }

/* Phase 14.7.0 — account settings shell. Each account page wrapped nav+content
   in an inline flex row (which can't carry a media query); this is that row as a
   class. Below --bp-md the 180px nav becomes a full-width horizontal strip above
   the content; padding tightens below --bp-sm. Desktop (≥900px) is unchanged. */
.rv-acct-shell { padding: 26px 32px; display: flex; gap: 32px; align-items: flex-start; }
.rv-acct-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 18px; }
@media (max-width: 900px) {   /* --bp-md */
  .rv-acct-shell { flex-direction: column; gap: 18px; padding: 20px 16px; }
  .rv-account-nav { width: 100%; flex-direction: row; flex-wrap: wrap; align-items: baseline; gap: 6px 14px; }
  .rv-account-nav__header { width: 100%; margin-bottom: 2px; }
  .rv-account-nav__link { padding: 2px 0; }
}
@media (max-width: 640px) {   /* --bp-sm */
  .rv-acct-shell { padding: 16px 12px; }
}

/* === Phase 5.3 — LED pulse containment === */
/* The pulse ring extends 3px outside the LED's bounding box. Without a
   small inset margin on the wrapping container, the ring used to clip
   against the card edge on the Security page. Adding 4px padding to the
   led-pair keeps the ring inside the parent box on every callsite. */
.led-pair { padding: 4px 0; }

/* === Phase 5.4 — Hub customization polish === */

/* Add-widget placeholder card. Layered on top of the shared `.card`
   recipe so the edges, padding, and border-radius match the panel
   widgets in the grid. The element ships with the `hidden` attribute;
   `toggleEditMode()` removes it on entry and reapplies on exit. The CSS
   rule below is a safety net for the same gating. */
.rv-hub-add-slot {
  /* Phase 5.11.1 — grid placement moved to the more-specific
     `.rv-hub-grid .rv-hub-add-slot` rule below (full-width tray at the
     bottom). This rule retains the visual treatment only. */
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  gap: 12px;
  /* Slightly muted background + dashed border distinguishes the slot
     from a real widget without diverging from the card recipe. */
  background: var(--paper-2);
  border-style: dashed;
  border-color: var(--color-cyan, var(--line-1));
  color: var(--note);
  cursor: pointer;
  font: inherit;
  transition: border-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.rv-hub-add-slot[hidden] { display: none; }
.rv-hub-grid:not([data-rearrange-mode="1"]) .rv-hub-add-slot { display: none; }
.rv-hub-add-slot:hover {
  border-color: var(--ink);
  color: var(--ink);
  background: var(--paper);
}
.rv-hub-add-slot__plus {
  font-family: var(--font-display);
  font-size: 32px;
  line-height: 1;
  color: var(--ink);
}
.rv-hub-add-slot__label {
  font-family: var(--font-display);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
}

/* Phase 5.4 — chrome tightens up. The drag-handle dots are gone; the
   dashed outline + grab cursor are sufficient affordances. */
.rv-hub-grid[data-rearrange-mode="1"] [data-widget-removable="false"] [data-rv-widget-action="remove"] { display: none; }

/* Phase 5.4.2 — drop-edge indicator. During drag we tag the target
   widget with `data-drop-edge="top"|"bottom"|"left"|"right"` and CSS
   paints a cyan bar on the corresponding edge so the user can see
   exactly where the dragged widget will land.

   Phase 5.10 — names match the visual direction. Previously
   `before`/`after` (Phase 5.4.2) for top/bottom; renamed to
   `top`/`bottom` for symmetry with the left/right cousins added in
   Phase 5.8.2. */
.rv-hub-grid[data-rearrange-mode="1"] [data-widget-id] { position: relative; }
/* Horizontal bars: top + bottom */
.rv-hub-grid[data-rearrange-mode="1"] [data-drop-edge="top"]::before,
.rv-hub-grid[data-rearrange-mode="1"] [data-drop-edge="bottom"]::after {
  content: '';
  position: absolute;
  left: -4px;
  right: -4px;
  height: 4px;
  background: var(--color-cyan);
  border-radius: 2px;
  z-index: 20;
  box-shadow: 0 0 8px rgba(30, 143, 224, 0.45);
}
.rv-hub-grid[data-rearrange-mode="1"] [data-drop-edge="top"]::before { top: -8px; }
.rv-hub-grid[data-rearrange-mode="1"] [data-drop-edge="bottom"]::after { bottom: -8px; }
/* Vertical bars: left + right. Mirror the top/bottom recipe so the
   four edges read as one consistent indicator family. */
.rv-hub-grid[data-rearrange-mode="1"] [data-drop-edge="left"]::before,
.rv-hub-grid[data-rearrange-mode="1"] [data-drop-edge="right"]::after {
  content: '';
  position: absolute;
  top: -4px;
  bottom: -4px;
  width: 4px;
  background: var(--color-cyan);
  border-radius: 2px;
  z-index: 20;
  box-shadow: 0 0 8px rgba(30, 143, 224, 0.45);
}
.rv-hub-grid[data-rearrange-mode="1"] [data-drop-edge="left"]::before { left: -8px; }
.rv-hub-grid[data-rearrange-mode="1"] [data-drop-edge="right"]::after { right: -8px; }

/* Phase 5.4.2 — clicks on the widget body don't navigate in edit mode.
   Belt-and-braces with the capture-phase preventDefault in the JS. */
.rv-hub-grid[data-rearrange-mode="1"] [data-widget-id] { cursor: grab; }
.rv-hub-grid[data-rearrange-mode="1"] [data-widget-id]:active { cursor: grabbing; }
/* Chrome buttons stay clickable with the normal pointer cursor. */
.rv-widget-chrome,
.rv-widget-btn { cursor: pointer; }

/* === Phase 5.10 — S-unit grid for the widget dashboard === */
/* Square S-unit cells. The packing algorithm (server + client) computes
   each widget's (col, row) on a `COLS × ∞` grid; the resolved position
   ships as inline `grid-column: <col> / span <cspan>` style on the
   widget root (see widget_skin.py::_root_attrs). Widget sizes map to
   square footprints: S = 1×1, M = 2×2, L = 4×4.

   `--rv-s-unit` is set by JS via ResizeObserver to keep cells square
   even as the container resizes; the CSS default (110px) is a sane
   fallback that matches the typical hub width of ~1330px / 12 cols. */
.rv-hub-grid {
  --rv-cols: 12;
  --rv-s-unit: 110px;
  --rv-gap: 14px;
  display: grid;
  grid-template-columns: repeat(var(--rv-cols), var(--rv-s-unit));
  grid-auto-rows: var(--rv-s-unit);
  gap: var(--rv-gap);
  justify-content: start;
}
.rv-hub-grid [data-widget-id] {
  height: 100%;
  overflow: hidden;
}
/* Phase 5.11.1 — the add-slot is a full-width tray below every
   widget in edit mode. `grid-column: 1 / -1` spans every column;
   placing the slot last in DOM (already true via the template) makes
   it naturally fall onto its own row below the packed widgets. */
.rv-hub-grid .rv-hub-add-slot {
  grid-column: 1 / -1;
  grid-row: auto;
  min-height: calc(var(--rv-s-unit) * 1.2);
  width: 100%;
}

/* Scrollable inner content. Lives inside a widget that uses `display:
   flex; flex-direction: column`. The header stays pinned; this region
   absorbs overflow. */
.rv-widget-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}
.rv-widget-scroll::-webkit-scrollbar { width: 6px; }
.rv-widget-scroll::-webkit-scrollbar-thumb {
  background: var(--line-1);
  border-radius: 3px;
}

/* Settings widget shortcut list. */
.rv-settings-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.rv-settings-list a {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 8px 10px;
  text-decoration: none;
  color: var(--ink);
  background: var(--paper-2);
  font-family: var(--font-mono);
  font-size: calc(11px * var(--rv-text-scale, 1));
  letter-spacing: 0.04em;
  transition: background var(--dur-instant) var(--ease-out);
}
.rv-settings-list a:hover { background: var(--paper); border-left: 2px solid var(--color-cyan); padding-left: 8px; }
.rv-settings-list a .rv-settings-arrow { color: var(--note); font-size: calc(12px * var(--rv-text-scale, 1)); }

/* Swim-lane empty-slot hint — quieter than the old "FUTURE TOOLS"
   placeholder; just signals capacity. */
.rv-tools-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 0;
  height: 100%;
  border: 1px dashed var(--line-1);
  color: var(--note);
  font-family: var(--font-mono);
  font-size: calc(9.5px * var(--rv-text-scale, 1));
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-align: center;
  padding: 14px;
}

/* === Phase 5.5.1 — Fixup: ensure `hidden` attribute beats explicit
   `display` from utility classes like `.btn`. `[hidden]` is supposed
   to be UA-equivalent to `display: none`, but `.btn { display: inline-flex }`
   wins on specificity. */
[hidden] { display: none !important; }

/* === Phase 8.7.0 — honor prefers-reduced-motion. Collapse animation +
   transition to instant and stop the infinite "running" LED pulse. Only motion
   is removed; state changes still happen, layout is untouched. Global so the
   whole portal respects the preference, not just the CRA dashboard. === */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .led.pulse, .led.pulse::after { animation: none !important; }
}

/* === Phase 15.2.0 / 15.2.1 — error / event page: centered content, hero heading,
   legible-quiet two-tone mark. The heading is the page's one focal element; the
   mark is a soft supporting watermark; the cyan CTA is the only accent. Shared
   element rules live here so the five error templates carry classes only. */
.rv-event { max-width: 560px; margin: 0 auto; padding: 32px 0; text-align: center; }
.rv-event h1 {                /* hero — top of the scale, 36px display tracking */
  margin: 6px 0 16px;
  letter-spacing: -0.015em;
  text-wrap: balance;
}
.rv-event p { color: var(--fg-2); line-height: 1.6; margin: 0 auto 24px; }
.rv-event .rv-event-actions { display: flex; gap: 12px; justify-content: center; }
/* 403_tenant_suspended bespoke bits, de-inlined: the reason banner spacing and the
   left-aligned "What's preserved" list (inline-block so it centers as a block while
   its bullets read normally). */
.rv-event .suspended-banner { margin: 0 auto 22px; text-align: left; }
.rv-event-aside { display: inline-block; text-align: left; margin: 0 0 26px; }
.rv-event-aside ul {
  margin: 0; padding-left: 16px; color: var(--fg-2);
  line-height: 1.7; font-size: 13px;
}
/* Legible-but-quiet two-tone watermark. The two tones now separate — a perceptible
   gray-2 outline over a lighter gray-1 lens (both override the sprite's inline
   fill="#9CA0A6") — so the glyph actually reads, while staying a soft grey far below
   the near-black heading. Supporting size, not dominating; carries its own outer
   viewBox (set on the <svg>) so the <use>-referenced symbol scales to this box. */
.rv-event-mark {
  display: block;
  width: clamp(140px, 26vh, 260px);
  height: clamp(140px, 26vh, 260px);
  margin: 0 auto 10px;
  color: var(--gray-2);                            /* outline — #C7C6BE, perceptible */
}
.rv-event-mark .ic-fill { fill: var(--gray-1); }   /* lens — #E2E1DA, lighter, distinct */
/* Phase 15.4.0 — auth/entry pages reuse the centered .rv-event treatment; their
   forms keep left-aligned internals for readability (centered form fields hurt
   scannability). */
.rv-event form { text-align: left; }

/* ============================================================================
   Hub — fixed landing (Phase 18.1.0)
   ----------------------------------------------------------------------------
   The per-user customizable widget grid was retired from the frontend. The hub
   is now a static, hand-composed landing: a greeting and an even band of four
   `.card` panels (tools · recent jobs · notifications · team). Centered and
   width-capped; the band flows from a 4-across row down to 2-up and 1-up on
   narrower viewports via `auto-fit`. Panels share the CRA dashboard's chrome:
   the same `.card` radius/border/background, `.panel-anim` hover (lift +
   shadow), and the underlined `.rv-section-label` header (matching the
   dashboard's "Month-end sales report" panel), so the hub and dashboard read
   as one system. */

.rv-hub {
  /* Phase 18.1.0 — fill the working area (main is a flex column) so the 2×2
     band can size its rows to the viewport and the whole hub fits one screen
     without page scroll. */
  width: 100%;
  max-width: 1400px;
  margin-inline: auto;
  flex: 1 1 auto;
  min-height: 0;
  padding: 22px 32px 20px;
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.rv-hub__head { display: flex; flex-direction: column; }
.rv-hub__sub {
  margin: 6px 0 0;
  color: var(--fg-2);
  font-size: 13px;
}

.rv-hub-band {
  /* Phase 18.1.0 — a 2×2 grid of wide panels. The two rows share the leftover
     vertical space (`1fr 1fr`) and the band flex-grows to fill the hub, so the
     four panels always fit one screen. Collapses to a single scrollable column
     on narrow viewports. */
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  /* Phase 18.1.3 — `minmax(0, 1fr)` (not bare `1fr`, i.e. `minmax(auto, 1fr)`)
     so tall panel content can't inflate a row past its share and push the band
     — and the page — past the viewport. This is what makes the per-panel clip
     below actually engage instead of the page growing. */
  grid-template-rows: minmax(0, 1fr) minmax(0, 1fr);
  gap: 20px;
  align-items: stretch;
}
/* The per-panel mobile tap icon (Phase 18.1.1) is desktop-hidden; below 900px
   it replaces the panel body (see the media query). */
.rv-hub-panel__go { display: none; }

@media (max-width: 900px) {
  /* Phase 18.1.1 — compact mobile hub. Keep the 2×2 grid filling one screen
     (no page scroll); each panel collapses to its title plus one big tap icon
     (the rail glyph) that opens the corresponding page. The rich bodies —
     carousel, job/notification lists, roster, "more" links — are hidden.
     Selectors are `.rv-hub-band`-scoped so they outrank the panel base rules,
     which appear later in the file (media queries add no specificity). */
  .rv-hub { flex: 1 1 auto; min-height: 0; }
  .rv-hub-band {
    grid-template-columns: repeat(2, 1fr);
    grid-template-rows: minmax(0, 1fr) minmax(0, 1fr);
  }
  .rv-hub-band .rv-hub-panel { min-height: 0; }
  .rv-hub-band .rv-hub-panel__body,
  .rv-hub-band .rv-hub-panel__more { display: none; }
  .rv-hub-band .rv-hub-panel__go {
    display: flex;
    flex: 1 1 auto;
    min-height: 0;
    align-items: center;
    justify-content: center;
    text-decoration: none;
    /* Match the rail/tab-bar two-tone icon coloring exactly — no hover/active
       tint (touch can't hover). Light mode: --fg-2 outline reads darker than
       the #9CA0A6 fill. */
    color: var(--fg-2);
  }
  /* Dark mode: pitch the outline below the fill so the two tones separate
     rather than collapsing to flat grey — the Phase 17.8.1 rail/tab treatment. */
  html[data-theme="dark"] .rv-hub-band .rv-hub-panel__go { color: var(--fg-3); }
  .rv-hub-band .rv-hub-panel__go--static { pointer-events: none; }
  .rv-hub-band .rv-hub-panel__icon {
    width: clamp(30px, 12vw, 52px);
    height: clamp(30px, 12vw, 52px);
  }
}

/* Each section is a `.card`; the panel fills its grid cell and lays out header
   (top) → body (fills) → "more" link (bottom). `min-height: 0` lets it shrink
   to its row so the grid can fit the screen; overflow is contained per body. */
.rv-hub-panel {
  min-height: 0;
  padding: 18px 20px;
  gap: 0;
  overflow: hidden;
}
.rv-hub-panel__label {
  /* Keep the default `.rv-section-label` hairline underline — matches the CRA
     dashboard's "Month-end sales report" panel header (which uses the plain
     label with its underline). Only the bottom margin is tuned for the panel. */
  margin-bottom: 12px;
}
.rv-hub-panel__body {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.rv-hub-panel__more {
  margin-top: 16px;
  /* Phase 18.1.3 — pin the escape-hatch link so a short panel cell clips the
     list body (above), never this "more" link. */
  flex-shrink: 0;
  align-self: flex-start;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--fg-2);
  text-decoration: none;
  transition: color var(--dur-base) var(--ease-out);
}
.rv-hub-panel__more:hover { color: var(--color-cyan); }

/* Phase 18.1.3 — "+N more" indicator shown when the list is clipped by the
   viewport cap. Injected + populated by portal.js (initHubOverflow) as a
   sibling below the panel body; hidden when nothing is cut off. Pinned
   (flex-shrink:0) so it never gets clipped itself. */
.rv-hub-panel__count {
  flex-shrink: 0;
  margin-top: 8px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-3);
}
.rv-hub-panel__count[hidden] { display: none; }

.rv-hub-empty {
  margin: auto 0;
  color: var(--fg-3);
  font-size: 13px;
  text-align: center;
}

/* Your tools — a carousel. The focal tool sits centered at full size (its name
   shows in full, like the /tools page); the previous/next tools peek at reduced
   scale on either side. Arrows spin the stage to a neighbor. A single tool
   disables the arrows (set server-side + the JS no-ops). */
.rv-hub-carousel {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  align-items: center;
  gap: 6px;
}
.rv-hub-carousel__stage {
  position: relative;
  flex: 1 1 auto;
  align-self: stretch;
  min-width: 0;
  overflow: hidden;
  /* Size container so the slide can scale to the available panel body via
     `cqmin` — the fix for the tile overflowing the panel at high browser
     zoom (zoom shrinks the CSS viewport, so a fixed 220px tile no longer
     fits the shortened panel). Slides are absolutely positioned (out of
     flow), so size containment on the stage is safe. */
  container-type: size;
  container-name: rv-carousel-stage;
}
.rv-hub-carousel__slide {
  position: absolute;
  top: 50%;
  left: 50%;
  /* Square that fits the smaller of the stage's dimensions (height at high
     zoom, width on a narrow panel), capped at the canonical 220px. The tile
     inside is 100% of this, so it shrinks with the stage instead of spilling
     past the panel border. */
  width: min(220px, 96cqmin);
  transform: translate(-50%, -50%) scale(0.6);
  opacity: 0;
  pointer-events: none;
  z-index: 0;
  transition:
    transform var(--dur-deliberate, 360ms) var(--ease-out),
    opacity var(--dur-deliberate, 360ms) var(--ease-out);
}
.rv-hub-carousel__slide.is-center {
  transform: translate(-50%, -50%) scale(1);
  opacity: 1;
  z-index: 3;
  pointer-events: auto;
}
.rv-hub-carousel__slide.is-prev {
  transform: translate(calc(-50% - 78%), -50%) scale(0.72);
  opacity: 0.4;
  z-index: 1;
  pointer-events: auto;
}
.rv-hub-carousel__slide.is-next {
  transform: translate(calc(-50% + 78%), -50%) scale(0.72);
  opacity: 0.4;
  z-index: 1;
  pointer-events: auto;
}
/* Hovering a peeking tool clears its transparency and lifts it fully in front
   of the center tile (z-index above the focal slide) so nothing occludes it —
   the tile reads as completely solid, keeping only its recessed scale. */
.rv-hub-carousel__slide.is-prev:hover,
.rv-hub-carousel__slide.is-next:hover {
  opacity: 1;
  z-index: 4;
}
/* The tile fills its 220px slide. It's narrower than the /tools grid card,
   so scale the title/description type down (via --rv-text-scale, which the
   tile's font sizes all read) and tighten padding so the large title, the
   description, and the Open affordance all fit without clipping. */
.rv-hub-carousel__slide .rv-tool-tile {
  width: 100%;
  max-width: none;
  max-height: none;
  padding: 16px;
  --rv-text-scale: 0.82;
}
/* The hub carousel is the tile's "full mode" surface — always show the blurb.
   At 100% the height-capped panel shrinks the slide below the 180px container
   threshold that drops the description on the narrow /tools grid; this override
   (higher specificity than the container-query rule) keeps full mode here while
   the /tools grid keeps its responsive drop. */
.rv-hub-carousel__slide .rv-tool-tile__blurb {
  display: -webkit-box;
}
.rv-hub-carousel__arrow {
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0.5px solid var(--line-1);
  border-radius: 50%;
  background: var(--bg-surface);
  color: var(--fg-1);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  transition:
    color var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out);
}
.rv-hub-carousel__arrow:hover:not(:disabled) {
  color: var(--color-cyan);
  border-color: var(--color-cyan);
}
.rv-hub-carousel__arrow:disabled { opacity: 0.3; cursor: default; }

/* Recent jobs */
.rv-hub-jobs {
  list-style: none;
  margin: 0;
  padding: 0;
  flex: 1 1 auto;
  min-height: 0;
  /* Phase 18.1.3 — hard-cut the list to its cell (no in-panel scrollbar); the
     grid's `minmax(0,1fr)` rows + the shell's `min-height:0` chain make this
     clip instead of growing the page. A soft ~14px bottom fade signals the
     list continues past what fits; the panel's "more" link reaches the rest. */
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 14px), transparent);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 14px), transparent);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.rv-hub-job {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  padding: 8px 8px;
  border-radius: var(--radius);
  text-decoration: none;
  color: var(--fg-1);
  transition: background var(--dur-base) var(--ease-out);
}
.rv-hub-job:hover { background: var(--gray-1); }
.rv-hub-job__title {
  font-size: 13px;
  font-weight: 600;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rv-hub-job__meta {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}
.rv-hub-job__status { font-size: 11px; }
.rv-hub-job__when { font-size: 11px; color: var(--fg-3); }

/* Notifications */
.rv-hub-notifs {
  list-style: none;
  margin: 0;
  padding: 0;
  flex: 1 1 auto;
  min-height: 0;
  /* Phase 18.1.3 — hard-cut the list to its cell (no in-panel scrollbar); the
     grid's `minmax(0,1fr)` rows + the shell's `min-height:0` chain make this
     clip instead of growing the page. A soft ~14px bottom fade signals the
     list continues past what fits; the panel's "more" link reaches the rest. */
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 14px), transparent);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 14px), transparent);
  display: flex;
  flex-direction: column;
  gap: 14px;
}
/* Notifications — title on the left, source chip on the right, using the wider
   panel's horizontal room. No row rules (kept clean; rows separate on gap). */
.rv-hub-notif {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
}
.rv-hub-notif__title {
  font-size: 13px;
  font-weight: 500;
  color: var(--fg-1);
  line-height: 1.35;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rv-hub-notif__meta {
  /* Phase 18.1.3 — source chip + relative timestamp, right-aligned as a group
     (mirrors Recent jobs' meta cluster). */
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-shrink: 0;
}
.rv-hub-notif__source {
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-3);
}
.rv-hub-notif__when {
  font-size: 11px;
  color: var(--fg-3);
}

/* Your team — roster-first (Phase 18.1.1): member rows (avatar · name ·
   title · role) that scroll inside the panel like Recent jobs; the
   seats/invites/MFA numbers demote to a quiet admin-only mono strip under
   the list instead of headlining as KPI blocks. */
.rv-hub-roster {
  list-style: none;
  margin: 0;
  padding: 0;
  flex: 1 1 auto;
  min-height: 0;
  /* Phase 18.1.3 — hard-cut the list to its cell (no in-panel scrollbar); the
     grid's `minmax(0,1fr)` rows + the shell's `min-height:0` chain make this
     clip instead of growing the page. A soft ~14px bottom fade signals the
     list continues past what fits; the panel's "more" link reaches the rest. */
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 14px), transparent);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 14px), transparent);
  /* Phase 18.1.3 — member rows are short, so flow them into responsive columns
     to use the panel's horizontal space: as many columns as fit at ~220px each,
     packed from the top (row-major, so the role order reads left-to-right then
     down) so the vertical clip + fade + "+N more" still apply — each cell is
     still one list item the overflow pass can count. */
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  grid-auto-rows: min-content;
  align-content: start;
  gap: 2px 18px;
}
.rv-hub-member {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 8px;
  border-radius: var(--radius);
  /* Grid items default to min-width:auto — without this a long member name
     pushes the row past its roster column and overlaps the neighbor. */
  min-width: 0;
}
.rv-hub-member .rv-avatar {
  flex-shrink: 0;
}
.rv-hub-member__name {
  /* Shrinkable (unlike title/role) so long names ellipsize inside the cell. */
  flex: 0 1 auto;
  min-width: 0;
  font-size: 13px;
  font-weight: 600;
  color: var(--fg-1);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.rv-hub-member__title {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 12px;
  color: var(--fg-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.rv-hub-member__role {
  flex-shrink: 0;
  margin-left: auto;
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-2);
}
.rv-hub-team__strip {
  flex-shrink: 0;
  margin-top: 8px;
  padding-top: 8px;
  border-top: 0.5px solid var(--line-1);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--fg-3);
}
