
:root {
  --bg: #0b0d10;
  --surface: #11151a;
  /* Nested surfaces need a perceptible, still-restrained step from
     --surface on dim displays (#1562 D17). */
  --surface-2: #1b222b;
  --border: #27303a;
  /* Control boundary (#1395 WS7). --border is a decorative hairline
     (~1.4:1 against --surface) — fine for card edges and row
     separators, far below the 3:1 WCAG 1.4.11 non-text floor where
     the border IS the control's boundary. Inputs, secondary/danger
     buttons, the hamburger, and outline/off pills ride this
     stronger token instead (3.1:1 vs --surface, 3.3:1 vs --bg). */
  --border-strong: #5a6770;
  /* Semantic accent split (#1395 WS8, extending the #982 P&L split):
     --accent now means *interaction only* (links, primary buttons,
     focus). Success states read --success (aliases the accent hue
     today, so pills/dots keep visual parity while gaining their own
     lever), and the active-navigation marker reads --nav-active,
     which is deliberately NOT green: the sidebar's current-page
     marker competed with every CTA on the page for the one meaning
     the accent is reserved for (#627 "one green, one meaning"). */
  --success: #3ee07f;
  --nav-active: #e8eaed;
  --text: #e8eaed;
  --text-dim: #9ba1a6;
  --accent: #3ee07f;
  --accent-dim: #1f6b3f;
  /* Same accent green at ~18% alpha. Used as a soft fill on
     "recommended" / "active" badges where the solid accent would
     out-shout the surrounding type. Sits between the subtle
     surface tint and the solid accent callout. Introduced for the
     hero card on /dashboard/connect (issue #297) — was an inline
     literal there. */
  --accent-bg: rgba(62, 224, 127, 0.18);
  /* Same accent green at ~5% alpha — a barely-there tint for
     "featured surface" treatments where even --accent-bg would
     compete with the surrounding type. Used by .tier.featured to
     lift the focal pricing card off the page without a gradient
     (the system is otherwise flat by convention — issue #526). */
  --accent-wash: rgba(62, 224, 127, 0.05);
  /* A half-step up from --accent-wash (~9% alpha), still well under
     the solid --accent-bg badge fill. The barely-there 5% wash was
     effectively invisible on the #0b0d10 surface (issue #819), so the
     "featured" pricing card carried no perceptible lift — and the
     differentiation, being hue-only, was undetectable to low-vision /
     colorblind users. This raises the featured surface just enough to
     register without the gradient the system avoids (#526); the badge
     + border + shadow carry the rest of the multi-signal treatment. */
  --accent-wash-strong: rgba(62, 224, 127, 0.09);
  /* Dark text laid over a solid --accent (#3ee07f) fill — the only
     place a near-black foreground is correct, because accent green is
     a light hue and white / --text on it fails AA. Single source of
     truth for .btn and .tier-badge, which both paint accent-green
     backgrounds (#984; was inlined as the literal #062414 in both). */
  --on-accent: #062414;
  --danger: #ff6b6b;
  --danger-border: rgba(255, 107, 107, 0.4);
  --warn: #ffb84d;
  --info: #6ec5ff;
  --info-bg: rgba(110, 197, 255, 0.12);
  /* Status-tint fills (#983). The .pill.* / .install-step.done status
     badges paint a status color at ~12% alpha over the dark surface —
     the same fill recipe --accent-bg (18%) / --accent-wash (5%)
     established for the accent. These fold the hand-written rgba()
     duplicates onto one definition each: --accent-bg-soft is the 12%
     accent fill (a half-step below the 18% --accent-bg badge);
     --danger-bg / --warn-bg are the 12% danger / warn fills. Alphas
     are the exact literals folded, so the badges are pixel-identical. */
  --accent-bg-soft: rgba(62, 224, 127, 0.12);
  --danger-bg: rgba(255, 107, 107, 0.12);
  --warn-bg: rgba(255, 184, 77, 0.12);
  /* Semantic P&L tokens (#982). A profit/loss number's *meaning* must
     be separable from the brand --accent (CTA / "go" / linked-positive)
     and from --danger (destructive: sign-out / unlink) — the green that
     means "this number is good" should not be the same token as the
     green that means "this control is interactive," or the journaling
     success green. They alias the same hues today, so existing P&L
     color is unchanged (visual parity); but a future shift to the brand
     green or the danger red won't silently repaint every gain/loss
     value, and re-tuning the P&L palette won't touch a button. These
     back .pnl-pos / .pnl-neg only. Never point a brand-green interactive
     control (.btn) at them, and never render one inside a P&L numeric
     cell — that's the ambiguity #982 fixes. */
  --pnl-up: #3ee07f;
  --pnl-down: #ff6b6b;
  /* Signed-in split-frame palette. Public and auth pages continue to use the
     dark semantic tokens above. ``body.layout-sidebar`` remaps those same
     roles to this paper-canvas set, while ``.sidebar`` locally remaps them
     back to the rail set. Keeping the component rules semantic means cards,
     controls, status, and P&L treatments need no page-by-page light skin. */
  --app-bg: #f1f3ef;
  --app-surface: #ffffff;
  --app-surface-2: #e8ece7;
  --app-border: #d4dad4;
  --app-border-strong: #657169;
  --app-text: #141a16;
  --app-text-dim: #59655e;
  --app-accent: #08714d;
  --app-accent-dim: #055a3d;
  --app-accent-bg: rgba(8, 113, 77, 0.12);
  --app-accent-wash: rgba(8, 113, 77, 0.04);
  --app-accent-wash-strong: rgba(8, 113, 77, 0.08);
  --app-accent-bg-soft: rgba(8, 113, 77, 0.09);
  --app-on-accent: #ffffff;
  --app-danger: #b42332;
  --app-danger-border: rgba(180, 35, 50, 0.35);
  --app-danger-bg: rgba(180, 35, 50, 0.09);
  --app-warn: #855700;
  --app-warn-bg: rgba(133, 87, 0, 0.11);
  --app-info: #17698e;
  --app-info-bg: rgba(23, 105, 142, 0.10);
  --app-pnl-up: #08714d;
  --app-pnl-down: #b42332;
  --rail-bg: #080a0c;
  --rail-surface: #171b1f;
  --rail-border: #252b2f;
  --rail-border-strong: #65717a;
  --rail-text: #f2f4f2;
  --rail-text-dim: #a0a8a2;
  --rail-accent: #3ee07f;
  --app-card-shadow: 0 1px 2px rgba(14, 23, 18, 0.05),
                     0 10px 30px rgba(14, 23, 18, 0.04);
  --rail-shadow: 8px 0 28px rgba(0, 0, 0, 0.14);
  --radius: 8px;
  /* Smaller corner step for inline chips — the ``$SLOT`` placeholder
     pill and the nested ``.prompt-example`` callout (#761) want a
     tighter radius than the 8px --radius used on cards / inputs. These
     two sites referenced a phantom ``var(--radius-sm, 4px)`` whose
     token was never declared in :root, so it silently resolved to the
     4px literal every time — the same class of bug #989 fixed for
     .insight-card's --radius-lg / --bg-elevated fallbacks. Promote it
     to a real token at that same 4px value; no visual change. */
  --radius-sm: 4px;
  --font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
               Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas,
               "Liberation Mono", monospace;
  --fs-micro: 0.75rem;
  /* #986 removed the 0.8125rem (13px) --fs-sm step: it packed a third
     size into the 12-14px band and was used interchangeably with its
     neighbors, earning no distinct role. Every consumer was repointed
     up to --fs-base-sm (14px) — a uniform +1px move that shrinks
     nothing (preserving the #426.1 .tile .label legibility bump). */
  --fs-base-sm: 0.875rem;
  --fs-base: 1rem;
  --fs-md: 1.125rem;
  --fs-lg: 1.25rem;
  --fs-xl: 1.5rem;
  --fs-2xl: 2rem;
  /* Mobile rescales for the two display sizes — h1 ramps down from
     --fs-xl, .card.hero h1 ramps down from --fs-2xl. Kept as
     explicit tokens (rather than rolling clamp() into --fs-xl /
     --fs-2xl) because the codebase isn't using clamp() anywhere
     else yet; the token surface stays consistent across breakpoints. */
  --fs-h1-mobile: 1.375rem;
  --fs-hero-mobile: 1.625rem;
  /* Marketing display step (#1300). The public marketing pages set
     ``body.layout-marketing``, which ramps their .card.hero h1 above
     the app scale so the landing hero reads as display type, not a
     settings-screen heading. --fs-3xl-mobile is its explicit mobile
     companion, same no-clamp() convention as the pair above. The
     dashboard / auth type scale is untouched — these two tokens are
     consumed only under the .layout-marketing scope. */
  --fs-3xl: 2.75rem;
  --fs-3xl-mobile: 2rem;
  /* Spacing scale — 4px-based, used by every gap/margin/padding
     declaration in the branded web surface. Twenty-six off-scale
     rem values had been intuited per-component before issue #523;
     this token series snaps them to a single rhythm so cards,
     rows, and tiles share consistent breathing room. Steps follow
     a 4px progression (--space-1 = 4px, --space-2 = 8px, …), with
     gaps in the numbering (skipping 7, 9-11) reserved for future
     additions. */
  --space-1: 0.25rem;  /*  4px */
  --space-2: 0.5rem;   /*  8px */
  --space-3: 0.75rem;  /* 12px */
  --space-4: 1rem;     /* 16px */
  --space-5: 1.25rem;  /* 20px */
  --space-6: 1.5rem;   /* 24px */
  --space-8: 2rem;     /* 32px */
  --space-12: 3rem;    /* 48px */
  /* Font weights — three steps cover the surface. Five distinct
     weights had been used inconsistently before issue #527 (400 /
     500 / 600 / 700 / inherited). Headings sit at --fw-bold so the
     UA default 700 doesn't slam light-on-dark display type; --fw-
     medium picks out labels and tile values that want a small step
     up from body without going semi-bold. The bare literal ``700``
     survives only on the small-caps kicker/eyebrow pattern (micro
     size, uppercase, tracked) and the tier-features ✓ icon —
     deliberate typographic exceptions documented at each site, not
     a fourth token. */
  --fw-regular: 400;
  --fw-medium: 500;
  --fw-bold: 600;
  /* Elevation shadows (#983). The system is flat by convention (#526 —
     no gradients), but three surfaces use a soft drop shadow to read as
     raised: the featured pricing card, the abbr tooltip, and the mobile
     sidebar drawer. These were three hand-written rgba(0,0,0,*)
     box-shadow strings; the tokens capture each verbatim (visual
     parity) so future elevation tuning has one home. --shadow-sm is the
     featured-card lift, --shadow-md the tooltip pop, --shadow-lg the
     drawer's horizontal cast. */
  --shadow-sm: 0 8px 24px rgba(0, 0, 0, 0.4);
  --shadow-md: 0 6px 18px rgba(0, 0, 0, 0.45);
  --shadow-lg: 2px 0 16px rgba(0, 0, 0, 0.4);
  /* Layout measures (#2573 D02, the design system's foundation slice).
     The --space-* scale above covers every gap/margin/padding
     relationship, and Stylelint enforces it. It does not cover
     ``width``, ``max-width``, ``min-height`` or ``line-height``, which
     sit outside the literal ban in both frontend/stylelint.config.mjs
     and scripts/check_spacing_tokens.py. Those four properties had
     therefore drifted uncaught: 65ch appeared 8 times, 2.75rem 10
     times, and 40rem twice, with no declared authority anywhere.
     docs/frontend-design-system.md is the design decision; these
     tokens make it executable. SPA stylesheets cannot declare a custom
     property at all, so a shared token is the only available home. */

  /* Comfortable reading measure for prose, ledes, help and supporting
     notes. A genuinely tabular comparison is not prose and is not
     capped here. */
  --measure-prose: 65ch;

  /* Slatemark's minimum interaction target for single-line controls,
     buttons and inline actions. This is a product choice, not an
     accessibility floor: WCAG 2.2 AA's target-size minimum is 24 CSS
     px with specified exceptions. Do not describe this token as the
     AA minimum. Deliberately separate from --fs-3xl, which happens to
     share the 2.75rem value for marketing display type. */
  --control-target: 2.75rem;

  /* Purposeful control widths. Every one is a maximum applied as
     min(100%, maximum), so a control still fits a narrow column; none
     of them is a value constraint. A 10rem quantity field does not
     imply a numeric limit. --control-long doubles as the fallback cap
     for a control that has not been classified yet. */
  --control-numeric: 10rem;
  --control-symbol: 12rem;
  --control-compact: 20rem;
  --control-text: 32rem;
  --control-long: 40rem;

  /* Line heights. Body and controls read at --lh-body, long prose at
     --lh-prose, and headings at --lh-heading, which is the loose end
     of the 1.2 to 1.35 heading band. */
  --lh-body: 1.5;
  --lh-prose: 1.6;
  --lh-heading: 1.35;
}
* { box-sizing: border-box; }
/* Native choice controls share the interaction accent (#1562 D19).
   The browser keeps its platform-native shape and forced-colors behavior. */
input[type="checkbox"],
input[type="radio"] { accent-color: var(--accent); }
/* Themed scrollbars (design pass). The OS default scrollbar renders as
   a chunky light bar that clashes with the dark theme — most visible on
   the fixed sidebar's overflow-y track at viewport heights where the
   nav + footer exceed the fold (1080p and below), and on any long
   dashboard. Thin + dark across Firefox (scrollbar-*) and WebKit/Blink
   (::-webkit-scrollbar) so every scroll container (sidebar, main, code
   blocks, tooltips) recedes into the surface instead of punching a
   bright rectangle through the page. Track stays transparent; the thumb
   is the hairline --border at rest and lifts to --text-dim on hover so
   it reads as grabbable without ever shouting. */
html { scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--border);
  border-radius: 999px;
  /* Transparent border + padding-box clip insets the thumb so it reads
     as a slim pill with breathing room, not a full-width slab. */
  border: 2px solid transparent;
  background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--text-dim); }
/* WCAG 2.1 SC 2.4.7 — keyboard focus indicator. Chrome's default
   focus ring dims to near-invisible against the dark theme, so we
   draw our own. Scoped to :focus-visible so a mouse click into an
   input or button doesn't paint a ring. Elements that need a
   different offset (overflow-clipped containers, dense nav rows)
   override per-class below. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius);
}
.btn:focus-visible { outline-offset: 3px; }
.nav-item:focus-visible { outline-offset: -2px; }
html {
  /* Lock the rem base to 16px regardless of UA quirks. Every rem
     value below is sized against this. */
  font-size: 16px;
  min-height: 100%;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}
body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  line-height: 1.5;
  /* Antialiasing. Chosen for the dark public surface, where subpixel
     rendering thickens light-on-dark text and we want crisp, not
     chunky. This rule now also serves ``body.layout-sidebar``, the
     light signed-in canvas, where the treatment is a consistency
     choice rather than a legibility one (#2573 D02). */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  font-feature-settings: "kern", "liga";
  /* Sticky footer. Body is a column flex container that fills the
     viewport, main grows to absorb any leftover space, footer stays
     non-shrinking at the bottom. Works for both the public topbar
     layout and the signed-in sidebar layout — the sidebar is
     position:fixed and the hidden nav-toggle checkbox is
     position:absolute, so neither participates in the flex flow. */
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}
/* Signed-in application canvas. Components continue to consume the same
   semantic custom properties; this shell-level remap is the complete light
   theme boundary and prevents the public dark brand surface from drifting. */
body.layout-sidebar {
  --bg: var(--app-bg);
  --surface: var(--app-surface);
  --surface-2: var(--app-surface-2);
  --border: var(--app-border);
  --border-strong: var(--app-border-strong);
  --success: var(--app-accent);
  --nav-active: var(--app-text);
  --text: var(--app-text);
  --text-dim: var(--app-text-dim);
  --accent: var(--app-accent);
  --accent-dim: var(--app-accent-dim);
  --accent-bg: var(--app-accent-bg);
  --accent-wash: var(--app-accent-wash);
  --accent-wash-strong: var(--app-accent-wash-strong);
  --accent-bg-soft: var(--app-accent-bg-soft);
  --on-accent: var(--app-on-accent);
  --danger: var(--app-danger);
  --danger-border: var(--app-danger-border);
  --danger-bg: var(--app-danger-bg);
  --warn: var(--app-warn);
  --warn-bg: var(--app-warn-bg);
  --info: var(--app-info);
  --info-bg: var(--app-info-bg);
  --pnl-up: var(--app-pnl-up);
  --pnl-down: var(--app-pnl-down);
  color-scheme: light;
}
main { flex: 1 0 auto; }
.footer { flex-shrink: 0; }
/* Semantic emphasis follows the same three-step weight system as the
   headings and components instead of falling back to UA-specific boldness.
   Keep italic explicit too, so prose emphasis has one shared primitive even
   though the current platform stacks already provide a native italic face. */
strong, b { font-weight: var(--fw-bold); }
em, i { font-style: italic; }
/* Authored underlines use the same font-relative geometry as prose links.
   ``skip-ink`` keeps descenders legible instead of drawing through them. */
u, ins {
  text-decoration: underline;
  text-decoration-thickness: 0.08em;
  text-underline-offset: 0.16em;
  text-decoration-skip-ink: auto;
}
/* Inline links need a non-color resting affordance (#1570 / WCAG
   1.4.1). Accent green is intentionally close to the surrounding
   light body text, so color alone cannot identify a prose link.
   Navigation and whole-component links opt out at their component
   rules below; ordinary prose, help, and legal links stay underlined. */
a {
  color: var(--accent);
  text-decoration: underline;
  text-decoration-thickness: 0.08em;
  text-underline-offset: 0.16em;
  text-decoration-skip-ink: auto;
}
a:hover { text-decoration-thickness: 0.12em; }
/* Honor the ``hidden`` attribute even on elements whose own rule
   sets a ``display`` (e.g. ``.tile`` is flex, ``.hero-card-link``
   is block) — the UA ``[hidden]`` rule loses to any author display.
   Used by the client-side tool filter (#766) to show/hide cards. */
[hidden] { display: none !important; }
/* Screen-reader-only label: removed from view but kept in the
   accessibility tree (e.g. the tool-search input's label, #766). */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
code, .mono {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum", "zero";
}
/* Skip-to-content link (#1300 P2, WCAG 2.4.1). First focusable
   element in the public topbar shell; parked offscreen until a
   keyboard user tabs onto it, then pinned over the header so Tab ->
   Enter jumps past the nav to <main id="main">. */
.skip-link {
  position: absolute;
  left: -9999px;
  z-index: 100;
}
.skip-link:focus {
  left: var(--space-4);
  top: var(--space-4);
  background: var(--surface-2);
  color: var(--text);
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  text-decoration: none;
}
.shell { max-width: 56rem; margin: 0 auto; padding: 0 var(--space-6); }

/* Sidebar layout — opt-in via <body class="layout-sidebar"> on the
   frontend-owned signed-in document shell. Public pages keep the topbar.

   Mobile drawer trick: a hidden checkbox sibling of the sidebar +
   scrim drives the slide-in via :checked, so the whole thing is
   CSS-only. Order matters: the checkbox must appear in the DOM
   before .sidebar and .scrim for the `~` sibling selectors below
   to fire. */
.nav-toggle { position: absolute; left: -9999px; }
.sidebar {
  position: fixed;
  left: 0;
  top: 0;
  bottom: 0;
  width: 14rem;
  /* The navigation rail owns its dark palette locally. Descendants such as
     focus rings, the active row, account identity, and Sign out therefore
     stay coherent without rail-specific component variants. */
  --bg: var(--rail-bg);
  --surface: var(--rail-bg);
  --surface-2: var(--rail-surface);
  --border: var(--rail-border);
  --border-strong: var(--rail-border-strong);
  --nav-active: var(--rail-text);
  --text: var(--rail-text);
  --text-dim: var(--rail-text-dim);
  --accent: var(--rail-accent);
  background: var(--surface);
  border-right: 1px solid var(--border);
  box-shadow: var(--rail-shadow);
  /* env(safe-area-inset-*) keeps the brand off the iOS notch /
     status bar and the Sign out button clear of the home
     indicator. The viewport meta carries ``viewport-fit=cover``
     so the env values resolve to actual insets on iPhone; on
     Android / desktop they're 0 and the fallback padding wins. */
  padding:
    max(var(--space-4), calc(var(--space-3) + env(safe-area-inset-top)))
    var(--space-3)
    max(var(--space-4), calc(var(--space-2) + env(safe-area-inset-bottom)));
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  overflow-y: auto;
  z-index: 20;
}
.sidebar .brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2);
  font-size: var(--fs-lg);
}
.brand-mark {
  display: block;
  width: var(--space-6);
  height: var(--space-6);
  flex: 0 0 auto;
}
.side-nav { display: flex; flex-direction: column; gap: var(--space-4); flex: 1; }
.nav-group { display: flex; flex-direction: column; gap: var(--space-1); }
.job-context {
  margin-bottom: var(--space-4);
}
.context-nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  width: fit-content;
  max-width: 100%;
  padding: var(--space-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--app-card-shadow);
}
.context-nav a {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
  padding: var(--space-2) var(--space-3);
  border: 1px solid transparent;
  border-radius: var(--radius);
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
  text-decoration: none;
}
.context-nav a:hover {
  background: var(--surface-2);
  color: var(--text);
  text-decoration: none;
}
.context-nav a[aria-current] { color: var(--text); }
/* Exact pages carry the stronger filled + bold treatment; a containing
   location stays outline-only. The shape/weight change keeps page versus
   section visible without depending on color alone (#1659). */
.context-nav a[aria-current="page"] {
  border-color: var(--nav-active);
  background: var(--surface-2);
  font-weight: var(--fw-bold);
}
.context-nav a[aria-current="location"] {
  border-color: var(--border-strong);
}
.nav-item {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: var(--space-3);
  /* 44px min touch target (issue #626 a11y) — the rows were
     tightly stacked at space-2 padding. min-height + centered
     flex keeps the visual rhythm while meeting the target. */
  min-height: 2.75rem;
  padding: var(--space-2);
  border-radius: 6px;
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
  border-left: 2px solid transparent;
  text-decoration: none;
  line-height: 1.3;
}
.nav-label { flex: 1 1 auto; }
.nav-item:hover {
  background: var(--surface-2);
  color: var(--text);
  text-decoration: none;
}
.nav-item.active {
  color: var(--text);
  border-left-color: var(--nav-active);
}
.nav-item.active[aria-current="page"] {
  background: var(--surface-2);
  font-weight: var(--fw-bold);
}
.nav-item.active[aria-current="location"] {
  border-left-color: var(--border-strong);
}
.sidebar-foot {
  border-top: 1px solid var(--border);
  padding-top: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.sidebar-identity {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  padding: 0 var(--space-1);
}
.account-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--space-8);
  height: var(--space-8);
  flex: 0 0 auto;
  border: 1px solid var(--border-strong);
  border-radius: 50%;
  background: var(--surface-2);
  color: var(--text);
  font-family: var(--font-mono);
  font-size: var(--fs-base-sm);
  font-weight: var(--fw-bold);
}
.sidebar-foot .who {
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
  font-family: var(--font-mono);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sidebar-foot form { padding: 0 var(--space-1); }
.sidebar-foot .btn { width: 100%; text-align: center; padding: var(--space-2) var(--space-3); font-size: var(--fs-base-sm); }

/* Mobile top strip on sidebar pages — slim bar with hamburger +
   brand. Hidden on desktop; the sidebar carries everything there. */
.topbar.mobile-only { display: none; }
/* The drawer toggle is a `span.hamburger` wrapped in a role-free
   `label[for="nav-toggle"]`: the label is the no-JavaScript activation
   path, the span is where `role="button"` is actually valid (#2604).
   `display: contents` drops the wrapper's box so the span stays the
   topbar flex item and every `.hamburger` rule below still applies. */
.hamburger-slot { display: contents; }
.hamburger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  color: var(--text);
  background: transparent;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  cursor: pointer;
  /* Reset font metrics so the SVG icon below isn't shifted by
     an inherited line-height (system fonts vary on Mac vs.
     iOS Safari and the old &#9776; glyph centered differently
     on each). */
  line-height: 1;
}
.hamburger:hover { background: var(--surface-2); }
.hamburger svg {
  width: 1.25rem;
  height: 1.25rem;
  display: block;
  stroke: currentColor;
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
}
.scrim { display: none; }

/* 14rem on the main + foot margin-left is the sidebar offset
   (matches the .sidebar width above) — a layout constraint, not a
   spacing-rhythm value, so it stays a literal rather than riding on
   the --space-* scale. */
body.layout-sidebar main { margin-left: 14rem; padding: var(--space-12) 0 4rem; /* 4rem bottom = generous page-bottom breathing; --space-12 (3rem) would crowd the foot on long dashboards */ }
body.layout-sidebar .shell { max-width: 72rem; margin: 0 auto; padding: 0 var(--space-8); }
body.layout-sidebar > .topbar:not(.mobile-only) { display: none; }
body.layout-sidebar .footer {
  margin-left: 14rem; /* sidebar offset, see above */
  background: var(--surface);
}

/* Mobile (≤640px): sidebar becomes a slide-in drawer. */
@media (max-width: 640px) {
  body.layout-sidebar > .topbar.mobile-only,
  body.layout-sidebar > #slatemark-browser-document-root > .topbar.mobile-only {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: max(var(--space-3), calc(var(--space-1) + env(safe-area-inset-top))) var(--space-4) var(--space-3);
    flex-direction: row;
  }
  body.layout-sidebar main { margin-left: 0; padding: var(--space-6) 0 var(--space-12); }
  body.layout-sidebar .shell { padding: 0 var(--space-4); }
  body.layout-sidebar .footer { margin-left: 0; }
  body.layout-sidebar .sidebar {
    transform: translateX(-100%);
    /* visibility rides the transition so the closed drawer is
       removed from pointer AND keyboard/AT reach (WCAG 2.4.3 /
       #1395 WS7) — an off-screen transform alone leaves ~30 nav
       links in the tab order behind the page. */
    visibility: hidden;
    transition: transform 0.2s ease, visibility 0.2s;
    width: 16rem;
    box-shadow: var(--shadow-lg);
  }
  body.layout-sidebar .nav-toggle:checked ~ .sidebar {
    transform: translateX(0);
    visibility: visible;
  }
  body.layout-sidebar .scrim {
    display: block;
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.55);
    z-index: 15;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s ease;
  }
  body.layout-sidebar .nav-toggle:checked ~ .scrim {
    opacity: 1;
    pointer-events: auto;
  }
}

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-4) var(--space-6);
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}
.brand {
  font-family: var(--font-mono);
  font-weight: var(--fw-bold);
  font-size: var(--fs-md);
  letter-spacing: 0.02em;
  color: var(--text);
  text-decoration: none;
}
.topbar .brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}
.brand .dot { color: var(--accent); }
.topbar-right { display: flex; align-items: center; gap: var(--space-4); }
.topbar nav a {
  margin-left: var(--space-5);
  color: var(--text-dim);
  text-decoration: none;
}
.topbar nav a:first-child { margin-left: 0; }
.topbar nav a:hover { color: var(--text); text-decoration: none; }
/* Active-page state on the public nav (#1300 P0). The shell marks the
   current page's nav link with ``aria-current="page"`` (derived from
   the request's canonical path), and the style keys off that
   attribute so the semantic and visual state can't drift apart. Kept
   deliberately neutral — full text color plus a quiet underline; the
   accent green stays reserved for the CTA (#627 "one green, one
   meaning"). */
.topbar nav a[aria-current="page"] {
  color: var(--text);
  text-decoration: underline;
  text-decoration-color: var(--text-dim);
  text-underline-offset: 0.45em;
}
/* Compact header CTA (#1300 P0). The persistent "Get started" is the
   only conversion element that follows the visitor down the page, so
   it renders as a real filled .btn at every width — sized down from
   the body .btn so it sits inside the header type, not on top of it.
   "Sign in" stays a text link; one filled control per header. */
.btn.nav-cta {
  padding: var(--space-1) var(--space-3);
  font-size: var(--fs-base-sm);
  white-space: nowrap;
}
.public-menu > summary { list-style: none; }
.public-menu > summary::-webkit-details-marker { display: none; }
/* Wide layouts expose the one navigation instance even while its native
   disclosure is closed. Hide the summary only when the browser can expose
   its internal content box; older engines retain a usable native menu. The
   sidebar hamburger (inside .topbar.mobile-only) keeps its own rules. */
@media (min-width: 701px) {
  @supports selector(::details-content) {
    .topbar.collapsible .hamburger { display: none; }
    .public-menu::details-content {
      display: contents;
      content-visibility: visible;
    }
  }
}
main { padding: var(--space-8) 0 4rem; /* 4rem bottom = generous page-bottom breathing on the public layout; --space-12 (3rem) would crowd the foot under long pages */ }
/* Headings: explicit 600 so we don't inherit the UA's 700 — too
   heavy for light-on-dark display text. Slight negative tracking
   on h1 tightens the larger size optically. */
h1 {
  font-size: var(--fs-xl);
  font-weight: var(--fw-bold);
  letter-spacing: -0.01em;
  line-height: 1.25;
  margin: 0 0 0.5rem;
}
body.layout-sidebar main h1 {
  font-size: var(--fs-2xl);
  line-height: 1.15;
  letter-spacing: -0.015em;
}
@media (max-width: 640px) {
  body.layout-sidebar main h1 { font-size: var(--fs-hero-mobile); }
}
/* Hero size step — opt-in via ``.card.hero`` on a landing-style
   card. The default h1 sits at --fs-xl (24px) so it doesn't drown
   sign-in / sign-up / plan card heads; the hero step kicks the
   landing h1 up to the documented 2rem (32px) display size so the
   kicker → h1 → lede stack reads as display scale rather than
   content-section scale. Mobile drops back to ~26px so the line
   doesn't wrap awkwardly under a narrow viewport. */
.card.hero h1 {
  font-size: var(--fs-2xl);
  line-height: 1.15;
  letter-spacing: -0.015em;
}
@media (max-width: 640px) {
  .card.hero h1 { font-size: var(--fs-hero-mobile); }
}
/* Marketing display register (#1300). The public marketing pages
   (/, /features, /pricing, /examples, /first-workflow) opt in via
   ``body.layout-marketing`` set by ``page(marketing=True)``. Two
   moves, both scoped so the dashboard / auth surfaces keep the app
   scale: the hero h1 steps up to the --fs-3xl display size (the
   32px app-hero size read as a settings screen on a landing page),
   and marketing h2 steps 20px -> 24px so section heads clear the
   18px tile h3s by a legible margin instead of 2px. Element-level
   selectors on purpose — the heading tags themselves stay
   attribute-free (several copy pins assert the literal ``<h1>`` /
   ``<h2>...`` strings). */
body.layout-marketing .card.hero h1 { font-size: var(--fs-3xl); }
body.layout-marketing h2 { font-size: var(--fs-xl); }
@media (max-width: 640px) {
  body.layout-marketing .card.hero h1 { font-size: var(--fs-3xl-mobile); }
  body.layout-marketing h2 { font-size: var(--fs-lg); }
}
h2 {
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  line-height: 1.3;
  margin: 0 0 0.75rem;
  color: var(--text);
}
p.kicker {
  /* Dim, not accent (design-critique pass): the eyebrow kicker used to
     paint --accent green, putting a second green attention magnet right
     above the hero's green CTA so first fixation split between the two.
     This completes the "one green, one meaning" accent discipline the
     rest of the marketing surface already follows: green is reserved for
     the CTA button and the proof column (#627), and for the analyst turn
     label + CTA on /examples (#817). The kicker now reads as the same quiet
     small-caps eyebrow used by dashboard section heads, letting the
     <h1> and the CTA own the hero.
     --text-dim on the dark surface is ~7:1, well past AA at this size. */
  color: var(--text-dim);
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 700;
  margin: 0 0 0.5rem;
}
p.lede {
  color: var(--text-dim);
  font-size: var(--fs-md);
  line-height: 1.55;
  margin: 0 0 var(--space-8);
}
/* Opt-in measure cap (issue #760) — a dashboard ``.lede`` runs the full
   60rem shell width by default, which is too wide for comfortable
   reading. Pages where the lede is body copy (not a hero line) add
   ``measure`` to cap it near the ~65ch readable line length, matching
   the ``.dim-blurb`` (60ch) treatment. Opt-in for dashboard surfaces;
   the marketing pages carry a blanket ~70ch cap below (#1300 P2 —
   uncapped ledes ran 85-90ch across the 56rem shell). */
p.lede.measure { max-width: 65ch; }
/* Marketing prose measure (#1300 P2). Scoped to the public marketing
   shell: ledes and the /examples transcript prose cap near 70ch so
   paragraphs stop running the full 56rem card width. Ledes that opt
   into the tighter 65ch .measure are excluded here (the :not()) so
   the opt-in keeps winning — without it this later, higher-specificity
   rule would silently widen them to 70ch. */
body.layout-marketing p.lede:not(.measure),
body.layout-marketing .demo-source,
body.layout-marketing .demo-phase-lede,
body.layout-marketing .demo-reply { max-width: 70ch; }
/* "How this works" expander — demotes pipeline/drift mechanics under
   a plain-English lede (e.g. /dashboard/rules) so the value promise
   reads first and the internals are one click away. Tighten the gap
   to the lede above it so the two read as one unit. */
p.lede:has(+ details.how-it-works) { margin-bottom: var(--space-3); }
details.how-it-works {
  margin: 0 0 var(--space-8);
  font-size: var(--fs-base-sm);
  color: var(--text-dim);
}
details.how-it-works > summary {
  cursor: pointer;
  list-style: none;
  color: var(--text-dim);
  font-weight: var(--fw-medium);
}
details.how-it-works > summary::-webkit-details-marker { display: none; }
details.how-it-works > summary::before {
  content: "▸" / "";
  display: inline-block;
  flex: 0 0 auto;
  margin-right: var(--space-2);
  transition: transform 0.15s ease;
}
details.how-it-works[open] > summary::before { transform: rotate(90deg); }
details.how-it-works > summary:hover { color: var(--accent); }
details.how-it-works > p {
  margin: var(--space-2) 0 0;
  line-height: 1.55;
}
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-5) var(--space-6);
  margin-bottom: var(--space-4);
}
body.layout-sidebar .card,
body.layout-sidebar .tile {
  box-shadow: var(--app-card-shadow);
}
body.layout-sidebar .card { padding: var(--space-6) var(--space-8); }
@media (max-width: 640px) {
  body.layout-sidebar .card { padding: var(--space-5); }
}
.card.danger { border-color: var(--danger-border); }
/* Section-rhythm floor (#1308). Top-level page sections self-space
   via a bottom margin on their own class (.card above;
   .feature-pillar, .tier-grid, .demo-transcript below), but a
   section class that never joined the system rendered flush against
   its neighbor: /demo's bare `section.tile-grid` sat 0px above the
   setup-help card. This floor guarantees the standard gap between
   adjacent top-level sections. :where() keeps it at zero
   specificity, so any class that declares its own margin-top
   (.mt-*) still wins outright, and sibling margin
   collapsing makes the floor a no-op wherever the preceding section
   already carries a bottom margin of --space-4 or more (every
   existing page). Pinned by tests/web/test_section_rhythm.py; a new
   top-level section class must also join RHYTHM_AUDITED_CLASSES
   there. */
:where(.shell > section + section) { margin-top: var(--space-4); }
.tile-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr));
  gap: var(--space-4);
}
/* /features capability grid (issue #816). The shared auto-fit
   .tile-grid collapses to a single ~90ch-wide column between
   ~670–840px, where its 20rem floor can no longer fit two tracks and
   body lines stretch past a comfortable measure. The marketing page
   opts into an explicit two-column grid that holds until true mobile,
   so the reading measure never blows out at mid-width. The
   two-class selector outranks the base .tile-grid rule regardless of
   source order. */
.tile-grid.feature-grid {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (max-width: 560px) {
  .tile-grid.feature-grid { grid-template-columns: 1fr; }
}
.tile {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-5) var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
/* Flex gap owns generic tile rhythm. Zero specificity lets a component
   with an intentional paragraph margin override this without a fight. */
:where(.tile > p) { margin: 0; }
.tile .row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2) var(--space-4);
  /* Wrap so a long pill drops below its label rather than
     squeezing the label into a two-line stub when the row
     can't hold both on one line. */
  flex-wrap: wrap;
}
.tile .row > .label { white-space: nowrap; }
/* Long unbreakable code tokens (SLATEMARK_* env names, row ids) are
   the classic 320px page-overflow source (#1395 WS7): let them break
   anywhere rather than widen the page. */
code { overflow-wrap: anywhere; }
.tile .label {
  color: var(--text-dim);
  /* #426.1 upsized this uppercase + tracked label off --fs-micro for
     legibility; that bump survives the #986 type-scale collapse by
     landing on --fs-base-sm (14px) rather than the removed 13px
     --fs-sm — it stays above micro and is never shrunk. */
  font-size: var(--fs-base-sm);
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
/* Focal accent-left rule, the single point of truth for the
   "this is the focal one" framing on tile-grid heroes
   (``.tile.hero``, issue #422).
   ``.tier.featured`` intentionally uses a different treatment
   (full accent border + ``--accent-wash`` bg, see below) —
   it sits in a grid of peers on /pricing and carries heavier
   emphasis than a single focal card. */
.tile.hero {
  border-left: 3px solid var(--accent);
}
/* Tile-grid hero (issue #626): the accent-left rule alone read as
   equal-weight against eight siblings. Lift it with a faint accent
   wash, a full accent-dim hairline, and a soft ring so the eye lands
   on the single promoted "next step" tile before the others. */
.tile.hero {
  background: var(--accent-wash);
  border-color: var(--accent-dim);
  border-left-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent-dim);
}
/* Marketing feature pillars (/features, issue #816). The capability
   tiles are grouped under the three named pillars the lede promises
   (analyst skill / journal + rules / data surface) plus a foundations
   group, so the page structure matches its claim instead of reading
   as a flat 11-up dump. The pillar <h2> + tile <h3> pairing also
   gives screen-reader heading-nav and SEO one stop per feature. */
.feature-pillar { margin-bottom: var(--space-8); }
.feature-pillar > h2 { margin-bottom: var(--space-2); }
.feature-pillar > .pillar-lede {
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
  line-height: 1.55;
  margin: 0 0 var(--space-4);
  max-width: 65ch;
}
/* Marketing-tile heading — the scannable title on a /features tile.
   Deliberately NOT .tile-title (that's a mono identifier for the
   tools catalog): full --text color, sentence case, and a step above
   the 16px body so the title is the most prominent element on the
   tile, not the dimmest (issue #816 inverted-hierarchy fix). Promoted
   from a dim <span class="label"> to a real <h3> so heading
   navigation and SEO get one stop per feature. */
.tile > h3.tile-heading {
  font-size: var(--fs-md);
  font-weight: var(--fw-bold);
  line-height: 1.3;
  color: var(--text);
  margin: 0;
}
/* Linked move-tile titles (#1298): the six-move tiles deep-link into
   the /examples transcripts, but a heading is no place to spend the
   accent (one green, one meaning). Keep the title in --text and carry
   the link affordance as a quiet underline that sharpens on hover. */
.tile > h3.tile-heading a {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--border);
  text-underline-offset: 3px;
}
.tile > h3.tile-heading a:hover { text-decoration-color: var(--text-dim); }
.tile .value {
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum", "zero";
}
.tile .actions { margin-top: auto; padding-top: var(--space-2); display: flex; gap: var(--space-2); flex-wrap: wrap; align-items: center; }
.muted-note {
  color: var(--text-dim);
  /* Roman, not italic (issue #626): long italic runs hurt
     readability and these are the lowest-contrast text on the
     tile face. Contrast of --text-dim on --surface is ~7:1, well
     past the 4.5:1 AA floor. */
  font-size: var(--fs-base-sm);
}
.tile .muted-note { padding: var(--space-3) 0; }
/* Setup-stepper "skill install" sub-card. Groups the per-AI-client
   download picker, the install-path hint, and the attestation
   checkbox into one visually-cohesive block on step 2's done state.
   Without the frame the three pieces read as three competing
   affordances on a flat surface; with it they read as one
   "install your skill" workflow. */
.card > .muted-note:first-of-type {
  margin-top: 0;
  margin-bottom: var(--space-4);
  max-width: 48rem;
}
.section-stack > * + * { margin-top: var(--space-6); }
/* Returning-user recent-record panel on Home / the dashboard (#1578).
   Rides .card for its section rhythm; these rules only shape the
   inner list and rows. ``Open <symbol>`` is an explicit compact action,
   not prose, so it uses weight + hover feedback instead of carrying the
   global resting underline into every ticker row. */
.activity-recent {
  list-style: none;
  margin: 0 0 var(--space-3);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.activity-recent-link {
  display: inline-block;
  font-weight: var(--fw-medium);
  text-decoration: none;
}
.activity-recent-link:hover {
  text-decoration: underline;
  text-decoration-thickness: 0.1em;
}
.activity-attention {
  margin: 0 0 var(--space-2);
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.activity-scorecard { margin: 0; }
.connection-row { margin: 0; }
/* The card's vertical rhythm rides the .tile flex `gap` (--space-2), not
   the UA <p> margins (which never collapse in a flex column and stacked
   on the gap) nor the shared `.tile .muted-note` padding — both zeroed
   here so related content stays tight. Group boundaries add an explicit
   token margin on top of the base gap so unrelated groups breathe, and
   the biggest void inside the card no longer exceeds the page's own
   section rhythm (#1375). */
.connection-row p { margin: 0; }
.connection-row .muted-note { padding: 0; }
.connection-row .account-list-head {
  margin-top: var(--space-2);  /* + the .tile gap ~= a group boundary */
  padding: 0;
}
.connection-row .account-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--border);
}
/* Account rows read as a border-separated table, name + institution
   inline ("Roth IRA · Fidelity") rather than
   flung to opposite card edges with no separator (#1375). */
.connection-row .account-row {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  flex-wrap: wrap;
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--border);
}
.connection-row .account-row:last-child { border-bottom: none; }
.connection-row .account-broker { color: var(--text-dim); }
.connection-row .account-broker::before {
  content: "·";
  padding-right: var(--space-2);
}
/* Rename: a real disclosure affordance, not inert dim text. The prior
   `display: inline-block` suppressed the UA marker, leaving "Rename"
   looking static; draw our own caret and
   underline on hover, grouped just under the action row (#1375). */
.connection-row .rename { margin-top: 0; }
.connection-row .rename > summary {
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  width: max-content;
  padding: var(--space-1) 0;
  list-style: none;
}
.connection-row .rename > summary::-webkit-details-marker { display: none; }
.connection-row .rename > summary::before {
  content: "";
  width: 0.4rem;
  height: 0.4rem;
  border-right: 2px solid var(--text-dim);
  border-bottom: 2px solid var(--text-dim);
  transform: rotate(-45deg);
  transition: transform 0.15s ease;
}
.connection-row .rename[open] > summary::before {
  transform: rotate(45deg);
}
.connection-row .rename > summary:hover {
  color: var(--text);
  text-decoration: underline;
}
.connection-row .rename > summary:hover::before { border-color: var(--accent); }
.coming-soon { margin-top: var(--space-4); }
.credentials {
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--border);
  margin-top: var(--space-1);
}
p.callout {
  margin: 0 0 var(--space-4) 0;
  padding: var(--space-3) var(--space-4);
  background: var(--surface-2);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius);
  font-size: var(--fs-base-sm);
  color: var(--text);
}
p.callout code {
  font-family: var(--font-mono);
  /* em-relative shrink (not a token): monospace inline <code> renders
     visually larger than the surrounding sans, so it scales relative to
     its parent rather than to a rem-based size. */
  font-size: 0.88em;
  color: var(--text-dim);
}
.tool-list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1);
  margin: var(--space-3) 0 var(--space-1) 0;
}
/* Shared input skin (#985, extended #1206). One base, the .input class,
   owns the surface + border + radius + padding + text color + accent
   focus-border that each form system used to redeclare identically,
   including the coming-soon waitlist field (.notify-me-form input).
   Per-system deltas (width / max-width / font-family / font-size / flex
   / resize) stay on the individual rules below. The selector list folds
   in each system's existing matched set (descendant
   input/select/textarea) alongside .input, so no element needs
   class="input" added and specificity is unchanged, while a future form
   can just use class="input". The keyboard :focus-visible ring lives at
   the top of this file and is untouched; the focus selectors below only
   restate the accent border + non-keyboard outline-reset. A single
   ::placeholder treatment (dim + italic) is hoisted alongside the skin
   (#1206) so placeholder tone no longer varies page to page; a system's
   own font-family on the placeholder stays local. The systems whose
   last consumer retired were removed from these lists (#2591 D20).
   color-scheme: dark
   is part of the skin (#1207): it themes the native control chrome (the
   <select> arrow, the date-picker glyph) that the UA otherwise draws
   dark-on-dark against these controls' dark background. */
.input,
.form-stack input[type="text"],
.form-stack input[type="datetime-local"],
.form-stack input[type="number"],
.form-stack input[type="email"],
.form-stack input[type="password"],
.form-stack input[type="url"],
.form-stack select,
.form-stack textarea,
.reconcile-tag-form select,
.reconcile-tag-form input[type="text"],
.reconcile-tag-form textarea,
.chat-form textarea,
.notify-me-form input[type="email"] {
  background: var(--bg);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  padding: var(--space-2) var(--space-3);
  color: var(--text);
  color-scheme: dark;
}
/* The shared skin defaults to dark native chrome for public/auth surfaces.
   Signed-in controls inherit the paper palette and explicitly request the
   matching light select arrows and date-picker glyphs. */
body.layout-sidebar main :is(input, select, textarea) { color-scheme: light; }
.input:focus,
.form-stack input:focus,
.form-stack select:focus,
.form-stack textarea:focus,
.reconcile-tag-form select:focus,
.reconcile-tag-form input[type="text"]:focus,
.reconcile-tag-form textarea:focus,
.notify-me-form input[type="email"]:focus,
.chat-form textarea:focus { border-color: var(--accent); }
.input:focus:not(:focus-visible),
.form-stack input:focus:not(:focus-visible),
.form-stack select:focus:not(:focus-visible),
.form-stack textarea:focus:not(:focus-visible),
.reconcile-tag-form select:focus:not(:focus-visible),
.reconcile-tag-form input[type="text"]:focus:not(:focus-visible),
.reconcile-tag-form textarea:focus:not(:focus-visible),
.notify-me-form input[type="email"]:focus:not(:focus-visible),
.chat-form textarea:focus:not(:focus-visible) { outline: none; }
/* Shared placeholder treatment (#1206). One dim, italic tone for every
   skinned control, so placeholder text reads the same on every form
   (.form-stack, .reconcile-tag-form, .notify-me-form, and .chat-form
   fell back to the browser default). Only color + slant are shared:
   font-family stays per system, so the mono controls keep a mono
   placeholder. Selects carry no placeholder and are absent from the
   list. */
.input::placeholder,
.form-stack input::placeholder,
.form-stack textarea::placeholder,
.reconcile-tag-form input[type="text"]::placeholder,
.reconcile-tag-form textarea::placeholder,
.notify-me-form input[type="email"]::placeholder,
.chat-form textarea::placeholder {
  color: var(--text-dim);
  font-style: italic;
}
/* Inline validation error state (#977). A control that failed
   validation gets a danger border; its companion ``.field-error`` note
   (rendered after the control, wired via ``aria-describedby``) carries
   the message. One selector list spans the ``.form-stack`` skin of the
   labelled-field system (#1211) and the sign-in form. The
   element-qualified selectors tie the base skin's specificity and sit
   later in source, so the danger border wins even when the field is
   focused. */
.form-stack input[aria-invalid="true"],
.form-stack select[aria-invalid="true"],
.form-stack textarea[aria-invalid="true"],
form.signin input[aria-invalid="true"] { border-color: var(--danger); }
.field-error {
  display: block;
  color: var(--danger);
  font-size: var(--fs-base-sm);
  margin-top: var(--space-1);
  line-height: 1.4;
}
.btn {
  display: inline-block;
  /* Grid items stretch across their track by default even when their
     display is inline-block. Buttons should hug their label unless a
     component deliberately owns a full-width CTA (for example the
     sidebar and narrow-screen action stacks), so make that default
     shared across every grid-backed action row. */
  justify-self: start;
  max-width: 100%;
  padding: var(--space-2) var(--space-4);
  background: var(--accent);
  color: var(--on-accent);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  font-weight: var(--fw-bold);
  font-size: var(--fs-base);
  cursor: pointer;
  text-decoration: none;
  font-family: inherit;
}
.btn:hover { background: var(--accent-dim); color: var(--text); border-color: var(--accent-dim); text-decoration: none; }
body.layout-sidebar main .btn:not(.secondary):not(.danger):hover {
  color: var(--on-accent);
}
.btn.small {
  padding: var(--space-1) var(--space-3);
  font-size: var(--fs-base-sm);
}
.hero-cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3) var(--space-4);
  margin: 0 0 var(--space-4) 0;
}
.btn.secondary {
  background: transparent;
  color: var(--text);
  border: 1px solid var(--border-strong);
}
.btn.secondary:hover { background: var(--surface-2); border-color: var(--text-dim); color: var(--text); }
.btn.danger {
  background: transparent;
  color: var(--danger);
  border: 1px solid var(--border-strong);
}
.btn.danger:hover { background: rgba(255, 107, 107, 0.08); border-color: var(--danger); }
.btn[aria-disabled="true"],
.btn[aria-disabled="true"]:hover {
  background: var(--surface-2);
  color: var(--text-dim);
  border-color: var(--border);
  cursor: not-allowed;
  opacity: 0.6;
  /* Belt + suspenders (issue #426). The anchor-as-button render
     pattern uses ``<a class="btn" aria-disabled="true" tabindex="-1">``
     with no ``href`` so it can't be clicked or focused. ``pointer-
     events: none`` is a final guard: if a future caller adds an
     ``href`` to "fix" linting, the disabled visual still doesn't
     navigate. */
  pointer-events: none;
}
.pill {
  display: inline-block;
  padding: var(--space-1) var(--space-2);
  border-radius: 999px;
  font-size: var(--fs-micro);
  font-weight: var(--fw-bold);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.01em;
  /* Long pill text (e.g. "1 active · 1 revoked", "Swing /
     individual-stock trader") used to wrap inside the pill on
     narrow viewports, which made the pill look like a two-line
     bubble and pushed the sibling label onto two lines too.
     Keep pill content atomic; the surrounding ``.tile .row``
     flex-wraps it onto its own line when the row can't fit
     both items. */
  white-space: nowrap;
}
.pill.ok { background: var(--accent-bg-soft); color: var(--success); }
.pill.warn { background: var(--warn-bg); color: var(--warn); }
.pill.dim { background: var(--surface-2); color: var(--text-dim); }
/* Meaning-specific, non-status pills (#1569). Trade side, plan identity,
   and a product recommendation are labels, not success or warning
   states, so they deliberately carry no ok/warn glyph. */
.pill.side {
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--border-strong);
  padding: calc(var(--space-1) - 1px) calc(var(--space-2) - 1px);
}
.pill.plan { background: var(--surface-2); color: var(--text-dim); }
.pill.recommended { background: var(--info-bg); color: var(--info); }
/* ``.pill.danger`` (issue #635) — muted-danger status tone so a
   terminal/revoked state reads as *status*, distinct from the
   neutral grey ``.pill.dim`` used for provenance tags (e.g.
   "Auto-connected") that sit beside it. Same 0.12-alpha-fill +
   token-color recipe as ``.ok`` / ``.warn``, which clears WCAG AA on
   both frames: the tokens resolve to --danger over --surface on the
   public pages and to --app-danger over --app-surface on the
   signed-in canvas, where these pills overwhelmingly render
   (#2573 D02). */
.pill.danger { background: var(--danger-bg); color: var(--danger); }
/* ``.pill.info`` (issue #419) — half-state tone for "started, not
   done" rows. Distinct from ``.pill.warn`` ("needs your attention")
   and ``.pill.ok`` ("done"), so a user scanning the setup stepper
   can tell PINNED apart from PENDING at a glance. Cool blue keeps
   the system at three colors plus dim and clears WCAG AA on both
   frames, resolving to --info over --surface on the public pages and
   to --app-info over --app-surface on the signed-in canvas, where
   these pills overwhelmingly render (#2573 D02). */
.pill.info { background: var(--info-bg); color: var(--info); }
/* ``.pill.outline`` (issue #432) — credential row "Not set" status,
   distinct from ``.pill.dim`` ("Disabled" — stored but paused). The
   solid border + no fill reads as "no key here" so a user scanning
   the credentials page can tell stored-paused from nothing-stored
   apart without reading the masked value. */
.pill.outline {
  background: transparent;
  color: var(--text-dim);
  border: 1px solid var(--border-strong);
  /* solid border eats ~2px vs filled background; trim padding to
     keep pill height visually consistent with .ok/.warn/.dim. */
  padding: calc(var(--space-1) - 1px) calc(var(--space-2) - 1px);
}
form.signin { display: flex; flex-direction: column; gap: var(--space-3); max-width: 22rem; }
.signup-assurance {
  max-width: 34rem;
  margin: var(--space-5) 0 0;
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
  line-height: 1.5;
}
.signup-assurance li + li { margin-top: var(--space-2); }
.signup-assurance strong {
  color: var(--text);
  font-weight: var(--fw-medium);
}
/* The sign-in email field is intentionally not a shared-skin member:
   the #985 pass hoisted only byte-identical declarations, and this field
   runs a larger 12/12 (var(--space-3)) padding for a roomier standalone
   form, so it redeclares the skin inline. Input paddings across the
   surface after #1206: signin 12/12, shared skin 8/12, the step-2 client
   picker and chat both 4/8. */
form.signin input[type="email"] {
  background: var(--bg);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  padding: var(--space-3);
  color: var(--text);
  font-size: var(--fs-base);
  font-family: inherit;
}
form.signin input[type="email"]:focus {
  border-color: var(--accent);
}
form.signin input[type="email"][aria-invalid="true"]:focus {
  border-color: var(--danger);
}
form.signin input[type="email"]:focus:not(:focus-visible) {
  outline: none;
}
/* Resend affordance on the check-your-email page (#981). The button
   group lays the inbox deep-link and the resend button in one wrapping
   row; the status line below carries the live cooldown countdown. */
form.signin.resend { gap: var(--space-2); }
.resend-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: center;
}
.resend-status {
  margin: var(--space-1) 0 0;
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
  min-height: 1.25em;
}
.banner {
  border-left: 3px solid var(--accent);
  background: var(--surface-2);
  padding: var(--space-3) var(--space-4);
  border-radius: 0 var(--radius) var(--radius) 0;
  margin-bottom: var(--space-6);
  color: var(--text-dim);
}
.banner.warn { border-left-color: var(--warn); }
.banner.danger { border-left-color: var(--danger); }
/* A form directly inside a banner (the past-due "Update payment"
   button, #177) sits under the message with a small gap. Inline-block
   rather than inline: a vertical margin needs a block-level box to take
   effect. */
.banner > form {
  display: inline-block;
  margin-top: var(--space-2);
}
.banner-progress {
  display: block;
  margin-top: var(--space-2);
  height: 0.35rem;
  background: var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}
.banner-progress > span {
  display: block;
  height: 100%;
  background: var(--warn);
  border-radius: inherit;
}
.banner.warn .banner-progress > span { background: var(--warn); }
.footer {
  border-top: 1px solid var(--border);
  margin-top: 4rem; /* 4rem footer top margin = clear separation from page content; --space-12 (3rem) would crowd the disclaimer band */
  padding: var(--space-5) 0;
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
}
.footer .shell { display: flex; justify-content: space-between; align-items: center; gap: var(--space-4); }
.footer-nav { display: flex; flex-wrap: wrap; gap: var(--space-4); }
.footer-nav a { color: var(--text-dim); text-decoration: none; }
.footer-nav a:hover { color: var(--text); text-decoration: none; }
/* The fineprint deliberately does NOT use ``.shell``. The sidebar
   layout's ``body.layout-sidebar .shell { margin: 0 auto; padding: 0 var(--space-8) }``
   has higher specificity than this rule, so reusing ``.shell`` here
   collapses the fineprint's margin-top and padding-top to zero on
   dashboard pages — the disclaimer ends up jammed against the row
   above with no separator. Give the fineprint its own sizing and
   centering so the spacing survives. */
.footer-fineprint {
  display: block;
  max-width: 56rem;
  margin: var(--space-4) auto 0;
  padding: var(--space-4) var(--space-6) 0;
  border-top: 1px solid var(--border);
  font-size: var(--fs-micro);
  line-height: 1.5;
  color: var(--text-dim);
}
body.layout-sidebar .footer-fineprint {
  max-width: 72rem;
  padding-left: var(--space-8);
  padding-right: var(--space-8);
}
.disclosure-list { margin: var(--space-6) 0; padding: var(--space-4) var(--space-4) var(--space-4) var(--space-8); background: var(--surface-2); border-left: 3px solid var(--border); border-radius: var(--radius); font-size: var(--fs-base-sm); line-height: 1.55; color: var(--text-dim); }
.disclosure-list li { margin: var(--space-1) 0; }
.disclosure-list strong { color: var(--text); font-weight: var(--fw-bold); }
.hero-disclaimer {
  margin: 0;
  font-size: var(--fs-base-sm);
  line-height: 1.5;
  color: var(--text-dim);
}
.hero-disclaimer a { color: var(--text-dim); text-decoration: underline; }
.hero-disclaimer a:hover { color: var(--text); }
/* Tertiary text-link CTA (e.g. paused-signup "Sign in") sitting in the
   .hero-cta flex row beside the .btn pair (#627). Reads as a link, not
   a button, so the single primary stays unambiguous. Padded so the tap
   target clears 44px on touch even though it isn't a .btn. */
.hero-cta-text {
  color: var(--text-dim);
  font-weight: var(--fw-medium);
  text-decoration: underline;
  padding: var(--space-2) 0;
}
.hero-cta-text:hover { color: var(--text); }
/* Hero proof teaser (#818 P1) — lifts one cite chip above the fold so a
   product whose pitch is "sourced, timestamped numbers" shows a sourced
   number in the hero, not only in the third card down the page. Reuses
   the .cite typographic mark from the after-vignette; line-height runs
   loose so the inline chips don't crowd. */
.hero-proof {
  margin: 0 0 var(--space-5);
  font-size: var(--fs-base);
  line-height: 1.7;
  color: var(--text-dim);
}
/* Launch-wedge audience line (#855) — names the swing trader as the
   first audience right under the lede, so a cold visitor knows who the
   product is built for before the proof chip. Same quiet treatment as
   .hero-clients: secondary color, the audience noun promoted to
   --text. */
.hero-audience {
  margin: 0 0 var(--space-4);
  font-size: var(--fs-base-sm);
  line-height: 1.5;
  color: var(--text-dim);
}
.hero-audience strong { color: var(--text); font-weight: var(--fw-medium); }
/* BYO-AI-client line (#818 P2) — states the bring-your-own-client model
   next to the hero CTA so a cold visitor knows Slatemark plugs into the
   AI client they already use, not a chatbot we host. */
.hero-clients {
  margin: 0 0 var(--space-4);
  font-size: var(--fs-base-sm);
  line-height: 1.5;
  color: var(--text-dim);
}
.hero-clients strong { color: var(--text); font-weight: var(--fw-medium); }
.kv {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--space-2) var(--space-4);
  font-size: var(--fs-base-sm);
  font-variant-numeric: tabular-nums;
}
.kv dt { color: var(--text-dim); }
.kv dd { margin: 0; }
/* FAQ list — question/answer pairs. Desktop: two columns with a
   fixed-width question gutter so the answers wrap on a consistent
   left edge. Mobile: questions stack above answers (see the 640px
   breakpoint) since long questions otherwise crush the answer
   column on a phone. */
.faq { display: grid; grid-template-columns: minmax(0, 16rem) minmax(0, 1fr); gap: var(--space-4) var(--space-6); font-size: var(--fs-base); align-items: baseline; }
.faq dt { color: var(--text); font-weight: var(--fw-bold); line-height: 1.4; }
.faq dd { margin: 0; color: var(--text-dim); line-height: 1.6; }
/* Pricing keeps lower-frequency FAQ answers in one native disclosure.
   .disclosure-summary uses flex layout, which can hide the browser marker,
   so restore an explicit chevron and rotate it with the native open state. */
.pricing-faq-more > summary { list-style: none; }
.pricing-faq-more > summary::-webkit-details-marker { display: none; }
.pricing-faq-more > summary::before {
  content: "▸";
  display: inline-block;
  margin-right: var(--space-2);
  transition: transform 0.15s ease;
}
.pricing-faq-more[open] > summary::before { transform: rotate(90deg); }
.center { text-align: center; }

/* L4 — design-system audit cleanup. The utilities and three
   component classes below replace the bulk of the inline
   ``style=""`` declarations that had accumulated across the
   branded web modules before the H1/H2/M4 token series landed
   (issues #522 / #523 / #527). Per the class-naming guidance at
   the top of this file, a utility earns its keep when it's used
   in two+ unrelated components AND is purely presentational. */
.mt-2 { margin-top: var(--space-2); }
.mt-3 { margin-top: var(--space-3); }
.mt-4 { margin-top: var(--space-4); }
.mt-5 { margin-top: var(--space-5); }
.mt-6 { margin-top: var(--space-6); }
.ml-2 { margin-left: var(--space-2); }

/* Labelled-field stack — the form-stack skin of the unified
   labelled-field system (#1211), used by the rules, skills,
   and connected-apps forms. ``.form-stack`` sets the
   between-field rhythm; each field is a ``.field-row`` that stacks
   label, help, control, and error in that one order. An explicit
   ``<label for>`` names the control, and the help text sits *outside*
   the label wired via ``aria-describedby`` so a control's accessible
   name is its label text only, never the help paragraph the old
   wrapping-``<label>`` markup folded into the name. */
.form-stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}
/* One field row: label, help, control, error stacked with a tight
   gap; the form-stack gap above carries the larger between-field
   rhythm. */
.field-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.form-stack .field-label {
  display: block;
  font-weight: var(--fw-medium);
}
.form-stack input[type="text"],
.form-stack input[type="datetime-local"],
.form-stack input[type="number"],
.form-stack input[type="email"],
.form-stack input[type="password"],
.form-stack input[type="url"],
.form-stack select {
  width: 100%;
  max-width: 24rem;
  font-family: inherit;
  font-size: var(--fs-base);
}
.form-stack textarea {
  width: 100%;
  font-family: var(--font-mono);
  font-size: var(--fs-base-sm);
  line-height: 1.5;
  resize: vertical;
}
.form-stack > .btn { align-self: flex-start; }

/* Broker link + Account-Data consent interstitials (#1208). The
   SnapTrade link, consent, and result pages use the clean neutral React
   shell, with no distracting nav on a trust-sensitive consent step. They
   inherit ``.card`` / ``.btn`` /
   ``.form-stack`` / ``.banner`` / ``.muted-note``; these two rules are
   the only markup unique to them: the consent bullet list and the
   affirmative-consent checkbox row. */
.consent-points { margin: 0 0 var(--space-4); padding-left: var(--space-5); }
.consent-points li { margin: var(--space-1) 0; line-height: 1.5; }
.consent-agree {
  display: flex;
  gap: var(--space-2);
  align-items: flex-start;
  margin: var(--space-4) 0;
  line-height: 1.5;
}
.consent-agree input[type="checkbox"] { flex: none; margin: var(--space-1) 0 0; }

/* Wide data table — journal positions / entries lists, the
   connected-apps OAuth clients list, the skills catalog, the
   onboarding install reference table. Replaces the
   ``width:100%;border-collapse:collapse`` inline on the <table>,
   the header-row tracking inline, and the per-cell
   ``padding:var(--space-3) var(--space-2)`` declarations.
   ``.col-actions`` is the opt-in right-align modifier for action
   columns (Edit, Open, Disconnect); most cells stay left-aligned.
   Wrap the table in ``.scroll-x`` for horizontal-scroll fallback
   on narrow viewports. */
.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-base);
}
.data-table thead tr {
  text-align: left;
  color: var(--text-dim);
  /* #986: uppercase tracked table header — repointed from the removed
     13px --fs-sm up to --fs-base-sm (14px); stays below the 16px body
     it heads. */
  font-size: var(--fs-base-sm);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  border-bottom: 1px solid var(--border);
}
.data-table th { padding: var(--space-2); font-weight: var(--fw-medium); }
.data-table td { padding: var(--space-3) var(--space-2); vertical-align: top; }
.data-table th:first-child,
.data-table td:first-child { padding-left: 0; }
.data-table tbody tr { border-bottom: 1px solid var(--border); }
.data-table tbody tr:last-child { border-bottom: none; }

/* Strategy Scorecard (/dashboard/scorecard) — page-specific styling
   for the date/account filter row, the sortable + numeric ``.data-table``
   modifiers, the sample-size badges (P0-7), the "Favorite vs Best"
   insight card (P0-6), and the free-tier teaser (P0-9). These extend
   the shared ``.data-table`` base above; they live here rather than
   inline in ``web/scorecard.py`` so all page CSS stays in this one
   string (see module docstring). */
.data-table th.sortable a {
  color: inherit;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  text-decoration: none;
}
.data-table th.sortable a:hover { color: var(--accent); text-decoration: none; }
.data-table td.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.data-table th.num { text-align: right; }
/* P&L cells read from the semantic --pnl-* tokens, not the brand
   --accent / --danger, so gain/loss *meaning* is decoupled from CTA
   and destructive intent (#982). */
.data-table td .pnl-pos { color: var(--pnl-up); }
.data-table td .pnl-neg { color: var(--pnl-down); }
.win-ci { display: block; color: var(--text-dim); font-size: var(--fs-micro); }
/* Facet rollup (#828): the default scorecard view, one expandable
   disclosure per PRIMARY facet. The summary carries the facet name and
   its distribution headline; expanding reveals the per-leaf table. */
.facet-rollup {
  border-top: 1px solid var(--border);
  padding: var(--space-2) 0;
}
.facet-rollup:first-of-type { border-top: none; }
.facet-rollup > summary {
  cursor: pointer;
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  list-style: none;
}
.facet-rollup > summary::-webkit-details-marker { display: none; }
.facet-rollup > summary::before {
  content: "▸";
  color: var(--text-dim);
  transition: transform 0.15s ease;
}
.facet-rollup[open] > summary::before { content: "▾"; }
.facet-rollup .facet-name { font-weight: 600; }
.facet-rollup .facet-dist { color: var(--text-dim); font-size: 0.9em; }
.facet-rollup .data-table { margin-top: var(--space-2); }
/* Per-trade capture foundation (#731 / #1409). The journal symbol page's
   capture forms use these controls; the reconcile inbox moved to the
   shared field layer (#2585 D14). */
.reconcile-tag-form {
  display: flex;
  align-items: flex-end;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.reconcile-tag-form label {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: var(--fs-base-sm);
  color: var(--text-dim);
}
/* Reconcile tag form controls (trades-needing-attention inbox, #731):
   the shared .input skin (#985) supplies the surface / border / focus;
   these are just the reconcile-only deltas (select min-width, font
   sizing). The skin resolves its semantic tokens against whichever
   frame the page sets, and this form renders only on
   /dashboard/scorecard/reconcile, a signed-in route that forces
   ``color-scheme: light`` (#2573 D02). */
.reconcile-tag-form select,
.reconcile-tag-form input[type="text"],
.reconcile-tag-form textarea {
  font-family: inherit;
  font-size: var(--fs-base);
}
.reconcile-tag-form select { min-width: 12rem; }
/* The why box on a merged attention row (#1412). Tag-first means the
   facet picker leads and this follows, so it takes the full remaining
   width and wraps to its own line on a narrow viewport rather than
   squeezing the picker. Surface / border / focus come from the shared
   .input skin above; these are the reconcile-only deltas. */
.reconcile-ask { flex: 1 1 22rem; }
.reconcile-ask textarea {
  width: 100%;
  resize: vertical;
}
/* The reconcile inbox is a progressive review queue: every trade is a
   compact disclosure row and only the first unresolved item opens on
   render. Its field and action composition lives with the route in the
   SPA stylesheet (#2585 D14). */
.reconcile-task-list {
  border-top: 1px solid var(--border);
  margin-top: var(--space-4);
}
.reconcile-task {
  border-bottom: 1px solid var(--border);
}
.reconcile-task:last-child { border-bottom: none; }
.reconcile-task-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 3rem;
  padding: var(--space-3) var(--space-6) var(--space-3) 0;
  cursor: pointer;
  list-style: none;
  position: relative;
}
.reconcile-task-head::-webkit-details-marker { display: none; }
.reconcile-task-head::after {
  content: "";
  position: absolute;
  right: var(--space-1);
  top: 50%;
  width: var(--space-2);
  height: var(--space-2);
  margin-top: calc(-1 * var(--space-2));
  border-right: 2px solid var(--text-dim);
  border-bottom: 2px solid var(--text-dim);
  transform: rotate(-45deg);
  transition: transform 0.15s ease;
}
.reconcile-task[open] > .reconcile-task-head::after {
  margin-top: calc(-1 * var(--space-1));
  transform: rotate(45deg);
}
.reconcile-task-head:hover::after { border-color: var(--accent); }
.reconcile-task-trade { min-width: 0; }
.reconcile-task-needs {
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-left: auto;
}
.reconcile-task-needs-label {
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
}
@media (max-width: 640px) {
  .reconcile-task-head {
    align-items: flex-start;
    flex-direction: column;
  }
  .reconcile-task-needs {
    justify-content: flex-start;
    margin-left: 0;
  }
  .reconcile-dismiss {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }
}
.capture-panel {
  box-sizing: border-box;
  width: 100cqw;
  padding: var(--space-4);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface-2);
}
.capture-form {
  display: grid;
  grid-template-columns:
    minmax(14rem, 18rem) minmax(min(20rem, 100%), 1fr);
  align-items: start;
  gap: var(--space-3);
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}
.capture-form legend {
  grid-column: 1 / -1;
  width: 100%;
  margin-bottom: var(--space-3);
  padding: 0 0 var(--space-2);
  border-bottom: 1px solid var(--border);
  color: var(--text);
  font-size: var(--fs-base-sm);
  font-weight: var(--fw-medium);
}
.capture-form label { width: 100%; min-width: 0; }
.capture-form.capture-form-reason-only label { grid-column: 1 / -1; }
.capture-form select,
.capture-form textarea {
  width: 100%;
  min-width: 0;
  max-width: 100%;
}
.capture-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-3);
  margin-top: var(--space-3);
}
.helper-prompt-disclosure > summary {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 44px;
  cursor: pointer;
  list-style: none;
}
.helper-prompt-disclosure > summary::-webkit-details-marker { display: none; }
.helper-prompt-disclosure > summary::after {
  content: "▸";
  flex: 0 0 auto;
  margin-left: auto;
  color: var(--text-dim);
  transition: transform .15s ease;
}
.helper-prompt-disclosure[open] > summary::after { transform: rotate(90deg); }
.helper-prompt-summary-title {
  color: var(--text);
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
}
.helper-prompt-summary-hint {
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
}
.helper-prompt-body {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border);
}
.helper-prompt-body .muted-note { margin: 0; }
.helper-prompt-actions {
  display: flex;
  justify-content: flex-end;
  margin-top: var(--space-3);
}
.helper-prompt-preview {
  display: block;
  margin-top: var(--space-3);
  padding: var(--space-3);
  border-radius: var(--radius);
  background: var(--surface-2);
  white-space: normal;
  overflow-wrap: break-word;
}
.capture-batch-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border);
}
.capture-batch-footer .muted-note { margin: 0; }
@media (max-width: 640px) {
  .capture-panel { padding: var(--space-3); }
  .capture-form { grid-template-columns: minmax(0, 1fr); }
  .capture-batch-footer {
    align-items: stretch;
    flex-direction: column;
  }
  .helper-prompt-summary-hint { display: none; }
  .helper-prompt-actions .btn { width: 100%; }
  .capture-batch-footer .btn { min-height: 44px; }
}
/* The prompt heading the thesis cell, sitting above its own form row.
   Reads as a label, not a link: the affordance is the form below. */
.capture-prompt {
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
}
/* "No read on this one" (#1410). A quiet text-link affordance, not a
   button: dismissing is the secondary path out of the row and must not
   compete with Save. Matches .reconcile-note-toggle > summary. */
.reconcile-dismiss {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  font-family: inherit;
  font-size: var(--fs-base-sm);
  color: var(--text-dim);
}
.reconcile-dismiss:hover { color: var(--accent); }
/* Close-note disclosure inside each untagged row (#731): the optional
   close note is hidden behind a quiet text-link summary so the default
   row reads as one tidy line; the field reveals on demand. */
.reconcile-note-toggle > summary {
  cursor: pointer;
  list-style: none;
  font-size: var(--fs-base-sm);
  color: var(--text-dim);
}
.reconcile-note-toggle > summary::-webkit-details-marker { display: none; }
.reconcile-note-toggle > summary:hover { color: var(--accent); }
.reconcile-note-toggle[open] > summary { margin-bottom: var(--space-2); }
.reconcile-note-toggle > *:not(summary) { margin-top: var(--space-2); }
/* Linked-trades history disclosure (#600): the whole "Linked trades"
   section collapses by default so the inbox leads with the actionable
   worklists. The summary carries the section heading and reads like the
   card's other <h2> headings, with a disclosure affordance. Sits in a
   .card. The unlinked-closes disclosure (#1406) shares the shape: an
   informational section that must not compete with the action queues. */
.reconcile-linked-disclosure > summary,
.reconcile-unlinked-disclosure > summary {
  cursor: pointer;
  list-style: none;
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  line-height: 1.3;
  color: var(--text);
  padding-right: var(--space-5);
  position: relative;
}
.reconcile-linked-disclosure > summary::-webkit-details-marker,
.reconcile-unlinked-disclosure > summary::-webkit-details-marker { display: none; }
.reconcile-linked-disclosure > summary::after,
.reconcile-unlinked-disclosure > summary::after {
  content: "";
  position: absolute;
  right: 0.25rem;
  top: 50%;
  width: 0.5rem;
  height: 0.5rem;
  margin-top: -0.35rem;
  border-right: 2px solid var(--text-dim);
  border-bottom: 2px solid var(--text-dim);
  transform: rotate(-45deg);
  transition: transform 0.15s ease;
}
.reconcile-linked-disclosure[open] > summary::after,
.reconcile-unlinked-disclosure[open] > summary::after {
  margin-top: -0.15rem;
  transform: rotate(45deg);
}
.reconcile-linked-disclosure > summary:hover::after,
.reconcile-unlinked-disclosure > summary:hover::after { border-color: var(--accent); }
.reconcile-linked-disclosure[open] > summary,
.reconcile-unlinked-disclosure[open] > summary { margin-bottom: var(--space-4); }
/* Shared Journal page identity and symbol links, also used by the React
   route bodies. Keep these base styles alongside the shared shell. */
.journal-page-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4) var(--space-6);
  margin-bottom: var(--space-4);
}
.journal-page-header .lede { margin-bottom: 0; }
.journal-symbol-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  text-decoration: none;
}
@media (max-width: 640px) {
  .journal-page-header {
    flex-direction: column;
    align-items: stretch;
  }
  .journal-symbol-link { min-height: 2.75rem; }
}
/* Filter disclosure (#625): filters sit below the headline numbers and
   stay collapsed until reached for. Open by default when a non-default
   filter is active so the applied scope stays visible. */
.scorecard-filters-disclosure { margin-bottom: var(--space-4); }
.insight-card {
  border: 1px solid var(--accent);
  /* Inset/recessed callout: the insight card sits a step *darker* than
     a normal .card (--bg #0b0d10 vs .card's --surface #11151a) so it
     reads as pulled-out, accent-bordered. References the defined tokens
     directly — the prior var(--radius-lg, …) / var(--bg-elevated, …)
     fallbacks pointed at tokens never declared in :root and silently
     resolved to --radius / --bg anyway (#989). No visual change. */
  border-radius: var(--radius);
  background: var(--bg);
  padding: var(--space-5) var(--space-5);
}
/* Page-level scorecard disclaimer (#388 / #840): a muted past-performance
   + not-advice note under the real card, free or Pro. */
.scorecard-disclaimer {
  margin-top: var(--space-4);
  font-size: var(--fs-micro);
}

/* Read-only code / JSON snapshot block. Pre + monospace +
   scrollable surface for verbatim text the analyst recorded
   (notes appended to a journal entry; intent-params / rule_refs
   captured at fill time; rendered skill Markdown). The
   ``.snapshot-note`` helper renders the explanatory caption
   above a ``.code-block`` (e.g. "Snapshot at record time"). */
.code-block {
  background: var(--surface-2);
  padding: var(--space-3);
  border-radius: var(--radius);
  max-height: 24rem;
  overflow: auto;
  white-space: pre-wrap;
  font-size: var(--fs-base-sm);
  font-family: var(--font-mono);
  line-height: 1.5;
  margin: 0;
}
.snapshot-note {
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
  margin: 0 0 0.5rem 0;
}
.record-snapshot {
  margin-top: var(--space-3);
}
.record-snapshot > summary {
  cursor: pointer;
  list-style: none;
  font-weight: var(--fw-medium);
  color: var(--text-dim);
}
.record-snapshot > summary::-webkit-details-marker { display: none; }
.record-snapshot > summary::before {
  content: "▸";
  display: inline-block;
  margin-right: var(--space-2);
  transition: transform 0.15s ease;
}
.record-snapshot[open] > summary::before { transform: rotate(90deg); }
.record-snapshot > summary:hover { color: var(--accent); }
.record-snapshot > *:not(summary) { margin-top: var(--space-2); }
/* Disclosure-summary styling for the recurring "open the
   advanced editor" pattern used by connected-apps, credentials,
   setup, and onboarding. ``-aside`` is the parenthetical hint
   after the summary text. */
.disclosure-summary {
  display: flex;
  align-items: center;
  min-height: 44px;
  cursor: pointer;
  font-size: var(--fs-base);
  font-weight: var(--fw-bold);
  color: var(--text);
}
.disclosure-summary-aside {
  font-weight: var(--fw-regular);
  margin-left: var(--space-2);
  font-size: var(--fs-base-sm);
}
/* "Label + copyable value + copy button" row pattern used by the
   credentials-shown-once success page in connected-apps. The
   value span flexes to 18rem so the copy button sits beside it
   on desktop and wraps below it on phones. */
.copy-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  word-break: break-all;
}
.copy-row > .value { flex: 1 1 18rem; }
/* Last-resort copy target. The shared handler reveals this read-only
   textarea only after both clipboard writes fail and a copy button has
   no visible .copy-row value to select. It spans its container so long
   commands and multi-line snippets remain readable and selectable. */
.copy-fallback-value {
  flex: 1 1 100%;
  width: 100%;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-base);
  line-height: 1.5;
  resize: vertical;
}
/* A copy-row carrying a sentence rather than an identifier (#1462:
   the first question the setup funnel and the connect walkthroughs
   hand over). ``break-all`` above is right for a URL or a CLI command
   and wrong for prose, which it snaps mid-word ("how does the chart
   read int / o it today?"). */
.copy-row.prose { word-break: normal; overflow-wrap: break-word; }
.copy-row.tinted {
  background: var(--surface-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius);
}
/* Unstyled list inside a ``<dl><dd>`` — kills the default
   margin / list-style so a short bullet list of redirect URLs
   sits flush against the term. */
.flush-list { margin: 0; padding-left: var(--space-4); }

/* Sit a paragraph or heading flush — the wrapping ``.copy-row``
   already supplies its own spacing, so the inner element should
   not add a default block margin. */
.flush { margin: 0; }
/* Tinted code snippet pre — onboarding's "paste this into your
   AI client's config" block. Same surface tint as
   ``.endpoint-display`` but pre-wraps preserved verbatim
   (whitespace:pre) and a horizontal scrollbar for long lines. */
.snippet-block {
  background: var(--surface-2);
  padding: var(--space-3);
  border-radius: var(--radius);
  overflow-x: auto;
  margin: var(--space-3) 0 0;
  white-space: pre;
}
.install-hint { font-size: var(--fs-base); }
/* Coming-soon waitlist form on the per-client onboarding page —
   email input flexes to 18rem, button sits beside or wraps below
   on a phone. */
.notify-me-form {
  margin-top: var(--space-4);
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
  align-items: center;
}
.notify-me-form input[type="email"] {
  /* Skin member (#1206): surface, border, radius, padding, focus accent,
     and placeholder come from the shared skin above; only the flex sizing
     is local. Previously redeclared the skin with a drifted --surface-2
     fill and no focus rule. */
  flex: 1 1 18rem;
}

.disclosure-summary.plain {
  cursor: pointer;
  font-size: inherit;
  color: inherit;
  font-weight: var(--fw-bold);
}
/* The paid first-workflow renderer is scoped under .setup-page. The Free
   first-value journey reuses the generic setup-stepper classes and keeps its
   separate compact milestone treatment. */
.setup-page { min-width: 0; }

/* Detail-page reference key: the raw kebab-case slug shown mono
   directly under the humanized <h1>, mirroring the catalog row so a
   power user can still cite the id (#1349). */
.rule-slug {
  margin: var(--space-1) 0 var(--space-3) 0;
  font-size: var(--fs-base-sm);
  color: var(--text-dim);
}

/* ===========================================================
   Persona controls (Profile; POST /dashboard/persona)
   and magic-first-question panel
   =========================================================== */
.persona-card { cursor: pointer; padding: var(--space-4); }
.persona-card input[type="radio"] {
  margin-top: var(--space-1);
  flex-shrink: 0;
}
/* Magic-first-question list — one card per prompt, each with a
   bold label, the prompt text, and a Copy button. */
.magic-prompt {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-3);
}
/* An example sits inside a flattened ``.prompt-pattern`` move block.
   A full border + radius would compete with the intent disclosure's outer
   boundary, so examples remain flat callouts with a left rule. */
.prompt-pattern {
  margin: 0;
  padding: var(--space-4) 0;
}
.prompt-pattern + .prompt-pattern {
  border-top: 1px solid var(--border);
}

/* ===========================================================
   Landing-interstitial (per-AI-client "not wired up yet" page)
   =========================================================== */
.interstitial-aside {
  margin-top: var(--space-2);
  font-size: var(--fs-base-sm);
}
.interstitial-aside-base {
  margin-top: var(--space-2);
  font-size: var(--fs-base);
}

/* ===========================================================
   OAuth consent screen (/oauth/authorize)
   =========================================================== */
.oauth-client-logo {
  width: 48px;
  height: 48px;
  border-radius: 8px;
  background: var(--surface-2);
  object-fit: contain;
}
.oauth-client-header {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}
.oauth-client-subhead { font-size: var(--fs-base-sm); }
.oauth-scopes-heading {
  font-size: var(--fs-base);
  margin-top: var(--space-6);
}
.oauth-scopes-list {
  list-style: none;
  padding: 0;
  margin: 0 0 var(--space-6);
}
.oauth-scopes-note { font-size: var(--fs-base-sm); }
.oauth-decision-form {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-4);
}
.oauth-scope-desc { font-size: var(--fs-base-sm); }
.oauth-scope-row {
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--border);
}

/* Status banner used by dashboard POST-redirect-GET surfaces (e.g. the
   reconcile inbox) — a bordered card-ish strip reporting the result of
   the last POST. The ``.flash-ok`` color above tints the success
   variant; the error variant borrows the danger accent. */
.flash {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-2) var(--space-3);
  margin: 0 0 var(--space-4);
  font-size: var(--fs-base-sm);
}
.flash.flash-error { color: var(--danger); border-color: var(--danger); }
.data-table em.col-empty { opacity: 0.7; }

/* Account-profile editor — full-width section under the tile grid.
   The briefing and per-account forms are stacks of labelled inputs /
   textareas, mirroring the credentials row styling but with multiline
   support. Each per-account form sits in its own native disclosure so the
   account list stays scannable without hiding controls from no-JS users. */
.profile { display: flex; flex-direction: column; gap: var(--space-5); }
.profile .blurb {
  margin: 0;
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
}

/* Before/after vignette — the contrast block on the landing page.
   Two side-by-side panels (vanilla AI vs. AI + Slatemark) with the
   "after" panel accent-tinted so the reader's eye lands there. The
   `.vignette-question` row above the grid carries the prompt being
   compared. Falls back to a single column under the .tile-grid
   breakpoint so phones stack cleanly. */
.vignette-question {
  font-family: var(--font-mono);
  font-size: var(--fs-base);
  color: var(--text);
  padding: var(--space-3) var(--space-4);
  background: var(--surface-2);
  border-left: 3px solid var(--text-dim);
  border-radius: 0 var(--radius) var(--radius) 0;
  margin: 0 0 var(--space-4);
}
.vignette-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr));
  gap: var(--space-4);
}
/* Surface-2 panel shared by .vignette and .demo-transcript (L5 —
   design-system audit). Same surface palette, border + radius,
   padding, and internal flex stack; the variants below differ
   only in their left-rule treatment (.before / .after for
   vignette, a neutral rule for demo-transcript) and a couple of
   optional modifiers. Keep these in lockstep — the transcript replay
   on /examples and the landing-page before/after panel share a
   single visual language by construction. */
.vignette,
.demo-transcript {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-4) var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
/* Before panel: neutral left border + dim label (intentionally
   NOT --danger). Coloring vanilla AI as "danger" framed it as a
   competitor failure rather than a typical baseline; neutral
   reads as "this is what an AI without Slatemark says" without the
   comparative loaded coding. Also dodges the red/green a11y
   trap — deuteranopia / protanopia compress red/green contrast,
   so the text labels carry the meaning instead. */
.vignette.before {
  border-left: 3px solid var(--border);
}
.vignette.after {
  border-left: 3px solid var(--accent);
  background: rgba(62, 224, 127, 0.04);
}
.vignette-label {
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 700;
  color: var(--text-dim);
}
.vignette.after .vignette-label { color: var(--accent); }
.vignette-quote {
  margin: 0;
  font-size: var(--fs-base);
  line-height: 1.55;
  color: var(--text);
}
.vignette-meta {
  margin: 0;
  font-size: var(--fs-base-sm);
  color: var(--text-dim);
  border-top: 1px solid var(--border);
  padding-top: var(--space-3);
}
/* Citation chips inside the after-vignette quote. Each chip pairs a
   value (tabular-nums monospace, accent left bar) with a small
   source label so the analyst output reads as sourced, not
   invented. That contrast is the visual punchline opposite the
   prose-only "before" panel. No background fill: the chip is a
   *typographic* mark, not a pill, so the surrounding prose still
   reads as prose. white-space: nowrap keeps value + source atomic
   across line breaks; the surrounding prose still wraps between
   chips. */
.cite {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-1);
  padding-left: var(--space-1);
  border-left: 2px solid var(--accent);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--text);
  white-space: nowrap;
  /* The value + provenance-kind pair can outgrow a 320px viewport
     (#1395 WS7): let the chip wrap internally instead of forcing
     horizontal page overflow. The nowrap above still keeps the pair
     on one line wherever it fits. */
  max-width: 100%;
  flex-wrap: wrap;
}
.cite .cite-src { white-space: normal; }
.cite-src {
  font-family: var(--font-sans);
  font-size: var(--fs-micro);
  color: var(--text-dim);
  font-weight: var(--fw-medium);
  letter-spacing: 0;
}
/* In the segmented after-vignette the chip value carries the punchline
   ("every number is sourced"), so bold the value while the source label
   stays medium (#818 P2). Scoped to .vignette.after so the inline chips
   elsewhere keep their lighter typographic-mark weight. */
.vignette.after .cite { font-weight: var(--fw-bold); }
.vignette.after .cite-src { font-weight: var(--fw-medium); }

/* Compact numbered paths shared by the homepage's record loop and the
   setup value preview. The accent is reserved for the step index only,
   keeping the single-green discipline intact. */
.step-strip {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3) var(--space-4);
  align-items: stretch;
}
.step {
  flex: 1 1 14rem;
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-4) var(--space-5);
}
.step-num {
  flex: none;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: var(--fw-bold);
  font-size: var(--fs-md);
  line-height: 1.2;
  color: var(--accent);
}
.step-body { display: flex; flex-direction: column; gap: var(--space-1); }
.step-title { font-weight: var(--fw-bold); color: var(--text); }
.step-detail {
  margin: 0;
  font-size: var(--fs-base-sm);
  line-height: 1.5;
  color: var(--text-dim);
}
/* Four setup beats read as a deliberate 2 × 2 sequence. The generic
   flex strip lets a fourth item stretch across the whole second row,
   which overstates that beat and makes the sequence look accidental. */
@media (max-width: 640px) {
}

/* Compact in-page orientation for long public journeys (#1562 D15).
   Pricing uses this to reach the two plan cards before a phone visitor
   has to scroll through the full comparison; Examples uses it to jump
   among its transcript landmarks. These remain text links, not
   competing conversion buttons. The vertical padding keeps each link
   comfortably tappable while the flex row wraps at narrow widths. */
.page-jump-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  /* Row gap is load-bearing, not cosmetic: each link is a 44px tap
     target, and /examples now lists six of them, so the row wraps to
     three lines on a phone. At row-gap 0 those targets sit flush and
     a mis-tap lands on the neighbour. Pricing's two links never wrap,
     so this is inert there. */
  gap: var(--space-2) var(--space-4);
  margin: 0 0 var(--space-6);
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
}
.page-jump-label { font-weight: var(--fw-bold); }
.page-jump-nav a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--text);
}
.page-jump-nav a:hover { color: var(--accent); }

/* A one-sentence deployment caveat should not consume a full card
   (#1562 D15). Keep the canonical Availability copy in its established
   pre-close position, but render it as a compact labelled note. */
.availability-note {
  margin: 0 0 var(--space-4);
  padding-left: var(--space-3);
  border-left: 2px solid var(--border-strong);
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
}
.availability-note strong { color: var(--text); }

/* Chat-replay transcript on the /examples page. Each .demo-transcript
   card walks one user question end-to-end: numbered move spine →
   prompt → visible result/provenance summary → collapsed full answer →
   collapsed tool calls. The
   surface palette + geometry are shared with .vignette in the
   grouped rule above (L5); this rule only carries the deltas:
   the bottom margin between stacked transcripts.

   Accent discipline (#817): the left rule is intentionally
   *neutral* here, not accent. /examples formerly spent green on every
   transcript's left rule, every move name, and every tool-call
   name, diluting the "one green, one meaning" reserve the rest of
   the site holds. Green is now reserved for the analyst turn label
   and the CTA only; everything else carries its weight through
   size, position, and neutral color. The .vignette.after panel
   keeps its accent left rule because there green *is* the meaning
   (the "with Slatemark" side of a before/after); a demo replay has
   no contrasting "before" so the rule is pure decoration. */
.demo-transcript {
  /* --border-strong, not --border: at six stacked transcripts the
     page is one continuous column and the weaker rule (1.46:1 on
     --bg) does not read as a card edge at all. --border-strong is
     3.29:1 and already pinned >= 3.0 by test_a11y_fundamentals. */
  border-left: 3px solid var(--border-strong);
  /* Whole transcripts had less separation (--space-4) than a
     one-line note gets below itself. --space-6 makes the boundary
     between two replays heavier than any boundary inside one. */
  margin-bottom: var(--space-6);
  /* Internal proximity (L5 follow-up): the shared .vignette rule
     above gives every child a uniform gap, so the eyebrow/heading
     pair, the turn boundary, and the label/summary/disclosures unit all read
     as equally related. Tighten the base gap and re-open only the two
     real seams below. */
  gap: var(--space-2);
}
.demo-transcript:last-child { margin-bottom: 0; }
.demo-transcript > .demo-prompt { margin-top: var(--space-4); }
.demo-transcript > .demo-reply { margin-top: var(--space-1); }
/* The plan pill paints --surface-2, which is exactly the transcript's
   own background, so a plan marker inside a replay had 1.00:1 shape
   contrast and read as bare text. --bg is the established inset fill
   on this surface (.demo-prompt, .demo-tool). The plan name is in the
   heading's accessible name either way, so this is purely the
   sighted-reader fix. */
.demo-transcript .pill.plan { background: var(--bg); }
/* Lifecycle grouping (#1298 follow-up): six flat transcripts is a
   wall with no rank. Each phase is an <h2> with its transcripts as
   <h3>, which restores a two-level outline, lets a reader skip a
   phase whole, and matches the pillar + tile-heading shape
   .analyst-moves already uses. */
.demo-phase {
  margin-bottom: var(--space-8);
}
.demo-phase-name {
  margin: 0 0 var(--space-2);
}
.demo-phase-lede {
  margin: 0 0 var(--space-4);
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
}
/* Numbered move spine. The eyebrow ("Workflow 1 of 6")
   stays a micro tracked label; the move name below is promoted to a
   real scannable landmark (#817 P1) so the six moves read as a
   spine the eye can hop down, not six identical gray stacks. The
   count is derived from the transcript inventory, so the eyebrow
   cannot go stale. The landmark is an anchor-carrying <h3> (#1298),
   nested under its lifecycle-phase <h2>. */
.demo-move {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  font-family: var(--font-sans);
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 700;
  color: var(--text-dim);
  margin: 0;
}
.demo-move-num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
  color: var(--text);
  letter-spacing: 0;
}
.demo-move-name {
  font-family: var(--font-sans);
  /* The move names are the /examples page's real document outline,
     nested under each lifecycle-phase h2. As an <h2> this rule was
     inert: (0,1,0) always lost to body.layout-marketing h2 at
     (0,1,2), so move names silently rendered at the phase heading's
     size and, below 640px, at exactly the closing CTA's size too.
     At <h3> nothing outranks this rule and the step is real. */
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  line-height: 1.2;
  color: var(--text);
  margin: 0;
  /* This element, not the transcript, carries the anchor id, so the
     scroll offset belongs here: a deep link from the Skills catalog
     otherwise parks the "Workflow N of 6" eyebrow above the fold. */
  scroll-margin-top: var(--space-6);
}
/* Conversational alignment (#817 P1): the page calls these chat
   replays, so make them look like one. The "You" prompt is a
   narrower bubble that hugs the right edge (margin-left: auto on a
   flex-column item pushes it to the cross-end), so it reads as the
   user's side of the thread opposite the full-width analyst turn
   below it — not a full-width box that merely looks indented.
   Falls back to full width on narrow viewports (the inset costs
   more than it buys on a phone). */
.demo-prompt {
  font-family: var(--font-mono);
  font-size: var(--fs-base);
  color: var(--text);
  padding: var(--space-2) var(--space-3);
  background: var(--bg);
  border-radius: var(--radius);
  max-width: 80%;
  margin: 0 0 0 auto;
}
.demo-prompt::before {
  content: "You";
  display: block;
  text-align: right;
  font-family: var(--font-sans);
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 700;
  color: var(--text-dim);
  margin-bottom: var(--space-1);
}
.demo-turn-label {
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 700;
  color: var(--accent);
  margin: 0;
}
/* Full answers and tool calls sit behind separate default-collapsed
   disclosures (#1827 P3.1). The concise result and its provenance lead;
   complete prose and implementation detail remain one keyboard-reachable
   click away. Native details/summary elements toggle on Enter/Space and
   announce expanded/collapsed state to a screen reader with no JS. Both
   controls receive the same visible chevron so the flex summary style does
   not erase the browser's disclosure affordance. */
.demo-answer-toggle,
.demo-tools-toggle {
  margin: 0;
  font-size: var(--fs-base-sm);
  color: var(--text-dim);
}
.demo-answer-toggle > summary,
.demo-tools-toggle > summary {
  cursor: pointer;
  list-style: none;
  color: var(--text-dim);
  font-weight: var(--fw-medium);
  width: fit-content;
  /* Each disclosure control inside a transcript, at
     --fs-base-sm it was roughly a 20px hit area while every sibling
     control on the page is held to 44px. */
  display: flex;
  align-items: center;
  min-height: 2.75rem;
}
.demo-answer-toggle > summary::-webkit-details-marker,
.demo-tools-toggle > summary::-webkit-details-marker { display: none; }
.demo-answer-toggle > summary::before,
.demo-tools-toggle > summary::before {
  content: "▸";
  display: inline-block;
  margin-right: var(--space-2);
  transition: transform 0.15s ease;
}
.demo-answer-toggle[open] > summary::before,
.demo-tools-toggle[open] > summary::before { transform: rotate(90deg); }
.demo-answer-toggle > summary:hover,
.demo-tools-toggle > summary:hover { color: var(--text); }
.demo-tools {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin: var(--space-2) 0 0;
}
.demo-tool {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum", "zero";
  font-size: var(--fs-base-sm);
  color: var(--text-dim);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-2) var(--space-3);
  word-break: break-word;
}
/* Accent reclaimed (#817): tool-call names are neutral monospace,
   not green. The function name is structure, not the one thing on
   the page worth an accent.
   This component renders the call only. The former sibling rules for
   .demo-tool-arrow / .demo-tool-out were dead: nothing in the web
   package has emitted those classes since payload display was
   dropped, and two tests now assert their absence. */
.demo-tool .demo-tool-call { color: var(--text); }
.demo-reply {
  margin: 0;
  font-size: var(--fs-base);
  line-height: 1.6;
  color: var(--text);
}
.demo-reply p { margin: 0 0 var(--space-3); }
.demo-reply p:last-child { margin-bottom: 0; }
/* Marketing prose rhythm (#1300 P2): inline cite chips ride
   mid-sentence (the hero proof line, the vignette quotes, and any
   transcript that adopts them); a hair of horizontal breathing room
   keeps the accent-barred chip from colliding with the surrounding
   words. */
.hero-proof .cite,
.vignette-quote .cite,
.demo-reply .cite { margin: 0 0.15em; }
/* The proof line carries the page's actual differentiating claim
   (which data came from where, and what never touches a brokerage
   connection), so it should not be the least legible element on the
   page. At --fs-micro with no measure cap it ran ~120 characters per
   line; --fs-base-sm plus the 70ch cap the reply already has brings
   it back to a readable column. */
.demo-source {
  margin: 0;
  font-size: var(--fs-base-sm);
  color: var(--text-dim);
  border-top: 1px solid var(--border);
  padding-top: var(--space-2);
}
.demo-source strong { color: var(--text); font-weight: var(--fw-bold); }
/* Mobile: drop the prompt indent (the inset costs more than it buys
   on a phone, #817 P2). The rules that stacked a call above its
   result went with the payload display that used to render one. */
@media (max-width: 640px) {
  .demo-prompt { max-width: none; margin: 0; }
  .demo-prompt::before { text-align: left; }
  /* body.layout-marketing h2 also steps down to --fs-lg here, so
     without this the phase heading and the move name under it render
     at the same size and the two-level outline collapses on exactly
     the viewport where the page is longest. */
  .demo-move-name { font-size: var(--fs-md); }
}
/* The page-level "everything below is fictional" note. Roman, not
   italic, for the same reason .muted-note is (#626): a multi-line
   italic run is harder to read, and this is the one disclaimer on
   the page that must actually be read. Neutral rule rather than
   --warn, which is reserved for a pending or not-yet state; this is
   framing, and nothing here is pending. */
.demo-illustrative {
  font-size: var(--fs-base-sm);
  color: var(--text-dim);
  border-left: 3px solid var(--border-strong);
  padding: var(--space-3) var(--space-4);
  background: var(--surface-2);
  border-radius: 0 var(--radius) var(--radius) 0;
  margin: 0 0 var(--space-6);
}

/* Pricing tiers — same shape as .tile but with a price line and a
   feature list inside. The "featured" tier (Plus) is the page's focal
   point: it carries a multi-signal treatment (a "Recommended" badge,
   accent border, raised wash, and a soft shadow lift) so the eye lands
   there first. Color alone never carries the featured state — issue
   #819 found the old hue-only differentiation was invisible on the
   dark surface and undetectable to low-vision / colorblind users; the
   text badge fixes the design and the a11y gap at once. Tier cards
   stack on mobile. */
.tier-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
  gap: var(--space-4);
  margin-bottom: var(--space-6);
  /* Vertical room for the badge that overhangs the featured card's
     top edge (it's translated up by half its own height). */
  padding-top: var(--space-3);
}
/* Shared-baseline tier rows (#1300 P0). The two asks must sit on one
   eyeline, so each card is a row subgrid over the parent grid: six
   rows — name / price / tagline / CTA / risk reversal / features — each
   sized to the tallest card, which pins the Free and Plus CTAs to the
   exact same y. Free leaves the risk-reversal row empty so both feature
   lists begin below the Plus cancellation/retention note.
   The card order puts the CTA *above* the feature list (price ->
   ask -> evidence), so the ~250px void that #819 hit (bottom-anchored
   CTA under Free's much shorter bullet list, via stretch +
   margin-top:auto) can't reopen: the only slack row is the trailing
   feature list, where extra space reads as list whitespace, not a
   detached button. Browsers without subgrid drop the
   grid-template-rows declaration and fall back to a per-card grid —
   same visual order, CTAs a few px apart at worst. The codebase
   already leans on subgrid (.catalog-row). */
.tier {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-6) var(--space-6) var(--space-5);
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 6;
  align-content: start;
  gap: var(--space-3);
}
.tier.featured {
  position: relative;
  border: 1px solid var(--accent);
  background: var(--accent-wash-strong);
  /* Soft elevation, not a gradient — flat-by-convention (#526) bars
     gradients, not shadows (the abbr tooltip and sidebar already use
     them). This is the lift that makes the focal card read as raised
     without scaling it (scaling would clip the overhanging badge and
     break the auto-fit grid's column alignment). */
  box-shadow: var(--shadow-sm);
}
/* Featured-tier "Recommended" pill — solid accent with the same dark
   --on-accent text the filled .btn uses (AA-passing on accent green).
   Overhangs the card's top edge so it reads as a ribbon, not a list
   item. */
.tier-badge {
  position: absolute;
  top: 0;
  right: var(--space-5);
  transform: translateY(-50%);
  background: var(--accent);
  color: var(--on-accent);
  font-size: var(--fs-micro);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  padding: var(--space-1) var(--space-3);
  border-radius: 999px;
  white-space: nowrap;
}
.tier-name {
  font-size: var(--fs-base-sm);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  /* Issue #527 audit: the kicker exception above (raw 700) applies
     only to the micro-size pattern. .tier-name sits at fs-base-sm
     (14px), where uppercase + tracked + 700 reads overweight on
     dark backgrounds; --fw-bold (600) holds the eyebrow tone
     without the slab. */
  font-weight: var(--fw-bold);
  color: var(--text-dim);
}
.tier.featured .tier-name { color: var(--accent); }
.tier-price {
  font-family: var(--font-mono);
  font-size: var(--fs-2xl);
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum", "zero";
  color: var(--text);
  line-height: 1.1;
  letter-spacing: -0.01em;
  margin: 0;
}
.tier-price .per { font-size: var(--fs-base-sm); color: var(--text-dim); font-weight: var(--fw-regular); }
.tier-tagline {
  font-size: var(--fs-base);
  color: var(--text-dim);
  margin: 0;
}
.tier > .muted-note {
  grid-row: 5;
  margin: 0;
}
.tier-features {
  grid-row: 6;
  list-style: none;
  padding: 0;
  margin: var(--space-2) 0 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  font-size: var(--fs-base-sm);
}
.tier-features li {
  display: flex;
  gap: var(--space-2);
  color: var(--text);
}
.tier-features li::before {
  content: "✓";
  color: var(--accent);
  font-weight: 700;
  flex-shrink: 0;
}
.tier-features li.muted { color: var(--text-dim); }
.tier-features li.muted::before { color: var(--text-dim); content: "·"; }
/* The CTA is row 4 of the tier subgrid (see .tier above), directly
   under the tagline — no bottom-anchoring auto margin, which is what
   opened the #819 void when the rows were a flex column. */
.tier-cta { padding-top: var(--space-2); }
/* The pricing CTA is the page's primary conversion target, so give it
   a roomier touch target than the default .btn (#819): taller vertical
   padding and full card width so it's an easy tap on mobile. */
.tier-cta .btn {
  display: block;
  text-align: center;
  padding-top: var(--space-3);
  padding-bottom: var(--space-3);
}

/* Horizontal-scroll wrapper for wide tables that can't shrink to a
   phone viewport without losing meaning (catalogs of credentials,
   skills, etc.). Pair with a `min-width` on the inner table so the
   columns keep usable width when the wrapper scrolls. */
.scroll-x { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.scroll-x > table { min-width: 32rem; }

/* Inline acronym tooltip (e.g. NLV, ATR, IV). The acronym is rendered
   as a <button> so it's focusable on mobile — tap shows the tip via
   :focus-within, the same way :hover shows it on desktop. Pure CSS.
   The trigger inherits surrounding font / color so it sits inside a
   paragraph without breaking line height; only the dotted underline
   marks it as expandable. */
.abbr {
  position: relative;
  display: inline-block;
  line-height: inherit;
}
.abbr-trigger {
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  border-bottom: 1px dotted var(--text-dim);
  cursor: help;
  /* The button reflows mid-paragraph; default <button> alignment
     would otherwise drop it onto its own baseline. */
  vertical-align: baseline;
}
.abbr-trigger:focus { outline: none; }
.abbr-trigger:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 2px;
}
.abbr-tip {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 0.45rem);
  transform: translateX(-50%);
  width: max-content;
  max-width: min(18rem, calc(100vw - 2rem));
  padding: var(--space-2) var(--space-3);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  font-size: var(--fs-base-sm);
  font-weight: var(--fw-regular);
  line-height: 1.4;
  text-align: left;
  white-space: normal;
  box-shadow: var(--shadow-md);
  z-index: 50;
  /* Hidden with display, not visibility: a visibility-hidden tip
     keeps its layout geometry, and a centered 18rem box anchored to
     a trigger sitting mid-line pokes past the right edge of a phone
     viewport, extending document scrollWidth and leaving the whole
     page a few px of horizontal wiggle. display:none has no
     geometry to leak (it does rule out a CSS fade transition; none
     is used). pointer-events stays off so the open tip never blocks
     a click on whatever sits under it. */
  display: none;
  pointer-events: none;
}
.abbr:hover .abbr-tip,
.abbr:focus-within .abbr-tip {
  display: block;
}
.abbr-tip::after {
  /* Small triangular nub pointing at the trigger. */
  content: "";
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 5px solid transparent;
  border-top-color: var(--border);
}
/* Phone widths: pure CSS can't clamp a trigger-centered bubble to the
   viewport (the stylesheet never knows the trigger's viewport X), so
   the open tip becomes a page-wide bar just under the trigger's line
   instead. Dropping position:relative from .abbr promotes the tip's
   containing block to the page (or the nearest positioned section,
   equally fine), so left/right pin the bar inside the viewport while
   the auto top/bottom keep its static in-flow spot, right after the
   trigger. Not position:fixed: a fixed box's static position ignores
   scroll (it resolves against the initial containing block), which
   put the tip a full page-height below a scrolled-to trigger. The
   nub would point at the bar's center rather than the trigger, so
   it's dropped. */
@media (max-width: 640px) {
  .abbr { position: static; }
  .abbr-tip {
    left: 1rem;
    right: 1rem;
    bottom: auto;
    width: auto;
    max-width: none;
    transform: none;
    margin-top: var(--space-2);
  }
  .abbr-tip::after { display: none; }
}

/* Row of "back to X" links above a page heading. Flex + wrap +
   gap so the buttons sit side-by-side on desktop and stack flush
   against the left edge on a phone — the prior margin-left hack
   left the second button visibly indented when it wrapped. */
.back-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0 0 var(--space-4) 0;
}

/* Mobile breakpoint. Phones land here (~iPhone 13/14 logical width
   is 390px; the small Plus/Mini sizes are 320–375). The desktop
   layout assumes ~700px+ before the public topbar fits its brand,
   navigation, and account action on one row, so anything narrower
   needs to stack. */
@media (max-width: 640px) {
  .topbar {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-2);
    padding: var(--space-3) var(--space-4);
  }
  .topbar-right {
    width: 100%;
    flex-wrap: wrap;
    gap: var(--space-2) var(--space-3);
    justify-content: flex-start;
  }
  .topbar nav { display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-3); }
  /* Touch-target bump (#627, WCAG 2.5.5 / Apple HIG ≥44px): the public
     top-nav links (Features / Examples / Pricing / Sign in)
     are bare text on desktop; on phones they need vertical padding so
     the tap target clears 44px. 14px text × 1.5 line-height + 12px × 2
     padding ≈ 45px. */
  .topbar nav a { margin-left: 0; font-size: var(--fs-base-sm); display: inline-block; padding: var(--space-3) 0; }
  .shell { padding: 0 var(--space-4); }
  main { padding: var(--space-6) 0 var(--space-12); }
  /* Mobile rescales: h1 stays distinctly larger than body, h2 stays
     larger than body, lede stays heavier than body via line-height
     even when sizes converge. Don't drop h2 to 1rem — collapses
     against body and the hierarchy disappears. */
  h1 { font-size: var(--fs-h1-mobile); }
  h2 { font-size: var(--fs-md); }
  p.lede { font-size: var(--fs-base); margin-bottom: var(--space-5); }
  .card { padding: var(--space-4); }
  .tile { padding: var(--space-4); }
  .tile-grid { gap: var(--space-3); }
  /* Touch-target bumps (issue #426). Apple HIG / WCAG 2.5.5 both
     ask for ≥44px on touchable targets. Desktop padding is fine
     for pointer; on phones the same controls need to be thumb-sized.
     ``.nav-item`` and ``.btn`` are the two surfaces the dashboard
     leans on most heavily. */
  .nav-item { padding: var(--space-3); }
  /* Step/tile labels can exceed a 320px column; wrapping beats
     horizontal page overflow at phone widths (#1395 WS7). */
  .tile .row > .label { white-space: normal; }
  .btn { padding: var(--space-3) var(--space-4); }
  .btn.secondary { padding: var(--space-3) var(--space-4); }
  /* ``.btn.small`` (e.g. the reconcile inbox tag/link/unlink actions,
     #731/#600) stays compact on desktop but needs the same ≥44px tap
     target on touch — bump min-height + padding inside the media query
     only so desktop is untouched. */
  .btn.small {
    min-height: 44px;
    padding: var(--space-2) var(--space-4);
  }
  /* Hamburger nudge: 2rem (32px) on desktop is fine for pointer
     but undersized for touch — bump to 2.75rem (44px) on mobile. */
  .hamburger { width: 2.75rem; height: 2.75rem; }
  .footer .shell { flex-wrap: wrap; }
  /* The footer links run beyond one phone-width row — the lone
     horizontal-overflow source at phone widths (pre-existing;
     spotted during the #1300 mobile pass). Let the row wrap. */
  .faq { grid-template-columns: 1fr; gap: var(--space-1) 0; }
  .faq dt { font-size: var(--fs-base); margin-top: var(--space-3); }
  .faq dt:first-of-type { margin-top: 0; }
  .faq dd { padding-bottom: var(--space-1); border-bottom: 1px solid var(--border); }
  .faq dd:last-of-type { border-bottom: 0; padding-bottom: 0; }
}

/* A signed-in marketing page loses 14rem to the sidebar and 2rem more to shell/card
   padding. Stack its FAQs until they have the same room as the public 640px
   breakpoint; otherwise the fixed question gutter crushes the answer column. */
@media (min-width: 641px) and (max-width: 896px) {
  body.layout-sidebar.layout-marketing .faq { grid-template-columns: 1fr; gap: var(--space-1) 0; }
  body.layout-sidebar.layout-marketing .faq dt { font-size: var(--fs-base); margin-top: var(--space-3); }
  body.layout-sidebar.layout-marketing .faq dt:first-of-type { margin-top: 0; }
  body.layout-sidebar.layout-marketing .faq dd { padding-bottom: var(--space-1); border-bottom: 1px solid var(--border); }
  body.layout-sidebar.layout-marketing .faq dd:last-of-type { border-bottom: 0; padding-bottom: 0; }
}

/* Public header collapse breakpoint (#1300 P1, widened by #1395 WS7).
   The desktop header needs roughly 700px before brand + nav + CTA fit
   on one row; between 641px and that width the wordmark and nav
   collided, "Sign in" wrapped, and "Get started" broke to two lines.
   So the public header collapses at 700px while the rest of the
   mobile styling keeps its 640px boundary. */
@media (max-width: 700px) {
  /* Public marketing header collapse (#1300 P1). The un-collapsed nav
     wrapped six links into a ~154px two-row block with 19px-tall
     targets. On phones the collapsible header keeps one row — brand
     left, the "Get started" CTA plus a hamburger right — and parks
     the nav links behind a native details/summary disclosure. The
     browser owns Enter/Space activation and the expanded state. The nav
     stays a single DOM instance across breakpoints. ``display: contents``
     lifts the disclosure and its content out of .topbar-right so the open
     menu spans the full header width instead of indenting under the brand. */
  .topbar.collapsible {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-start;
    gap: var(--space-2) var(--space-3);
  }
  .topbar.collapsible .brand { margin-right: auto; }
  .topbar.collapsible .topbar-right { display: contents; }
  .topbar.collapsible .public-menu { display: contents; }
  .topbar.collapsible .public-menu::details-content { display: contents; }
  .topbar.collapsible .hamburger {
    display: inline-flex;
    order: 2;
    /* >=44px touch target (WCAG 2.5.5); the generic bump lives in
       the <=640 block, and this control now shows to 700px. */
    width: 2.75rem;
    height: 2.75rem;
  }
  .topbar.collapsible .nav-cta { order: 1; }
  .topbar.collapsible nav { display: none; }
  .topbar.collapsible .public-menu[open] > nav {
    display: flex;
    flex-direction: column;
    gap: 0;
    order: 10;
    flex-basis: 100%;
    border-top: 1px solid var(--border);
    padding-top: var(--space-1);
  }
  /* The compact desktop CTA out-specifies the generic mobile .btn
     bump in this block, so restate the ≥44px touch target here
     (14px × 1.5 line-height + 12px × 2 padding + border ≈ 47px). */
  .btn.nav-cta { padding: var(--space-3) var(--space-4); }
  .topbar.collapsible nav a {
    margin-left: 0;
    font-size: var(--fs-base-sm);
    display: inline-block;
    padding: var(--space-3) 0;
  }
}

/* The 320px squeeze band still has to fit the wordmark, persistent CTA,
   and 44px navigation control on one row. Tighten only horizontal space;
   the CTA keeps its mobile vertical padding and the hamburger keeps its
   2.75rem target. */
@media (max-width: 340px) {
  .topbar.collapsible { column-gap: var(--space-2); }
  .btn.nav-cta { padding: var(--space-3) var(--space-2); }
}

/* Shared small-form components (#1395 WS7). Calendar and Weekly
   Slate previously fell to user-agent styling for fieldsets, legends,
   and checkbox rows, and every card-level action row matched no rule.
   Owned here so a new surface composes instead of improvising. */
.card .actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-3);
}
fieldset {
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  padding: var(--space-3) var(--space-4);
  margin: 0 0 var(--space-4);
  min-width: 0;
}
/* Radio-card pickers use fieldset for semantics, not panel chrome. */
legend {
  padding: 0 var(--space-2);
  font-weight: var(--fw-bold);
  font-size: var(--fs-base-sm);
  color: var(--text);
}
input[readonly], textarea[readonly] {
  background: var(--surface);
  color: var(--text-dim);
  cursor: text;
}

/* Base h3/h4 scale (#1395 WS7). Only h1/h2 had element rules; bare
   <h3>/<h4> outside .tile fell to the UA's 700-weight defaults the
   h1/h2 comments explicitly avoid. */
h3 {
  font-size: var(--fs-base);
  font-weight: var(--fw-bold);
  margin: 0 0 var(--space-2);
}
h4 {
  font-size: var(--fs-base-sm);
  font-weight: var(--fw-bold);
  margin: 0 0 var(--space-2);
}

/* Filled pills stay text-first: their visible status label (or signed P&L
   value) carries meaning without a generated symbol prefix. Semantic color
   supports scanning, while forced-colors mode below restores a boundary when
   alpha fills disappear. */

/* Labelled horizontal-scroll regions (#1395 WS7). Call sites add
   tabindex="0" + role="region" + a name so keyboard users can scroll
   and AT users know what the region holds; the focus ring marks the
   scrollable surface. */
.scroll-x:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Forced-colors (Windows High Contrast) baseline (#1395 WS7). The
   alpha-fill pills, checkbox-driven drawer, and accent focus ring
   otherwise lose their boundaries when the UA strips backgrounds. */
@media (forced-colors: active) {
  .pill { border: 1px solid CanvasText; }
  .btn { border: 1px solid ButtonText; }
  .banner { border: 1px solid CanvasText; }
  .nav-item[aria-current],
  .context-nav a[aria-current],
  .topbar nav a[aria-current="page"] { outline: 2px solid Highlight; }
  .banner-progress > span { background: Highlight; }
}

/* Reduced-motion escape hatch (WCAG 2.3.3, issue #533). Sidebar
   slide-in, scrim fade, and details-marker rotate all run
   unconditionally otherwise; honoring the OS-level preference is
   the system default for users who set it. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto !important; }
  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
}

/* Embedded chat surface (epic #638, Phase 1). Kept deliberately
   plain: the demo's job is to show the analyst frame, not to be a
   polished product chat. Radii are tokens (#1206), not literals, even
   though this gated-off surface is not a full skin member. */
.chat { max-width: 760px; margin: 0 auto; }
.chat-banner {
  background: var(--accent-bg);
  border: 1px solid var(--accent-dim);
  color: var(--text);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius);
  font-size: var(--fs-base-sm);
  margin-bottom: var(--space-2);
}
.chat-transcript {
  min-height: 220px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  padding: var(--space-2);
  margin-bottom: var(--space-2);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  overflow-y: auto;
  max-height: 60vh;
}
.chat-empty { color: var(--text-dim); }
.chat-msg { padding: var(--space-1) var(--space-2); border-radius: var(--radius); }
.chat-msg p { margin: 0 0 0.4rem; white-space: pre-wrap; }
.chat-msg p:last-child { margin-bottom: 0; }
.chat-msg-footer { color: var(--text-dim); font-size: var(--fs-base-sm); }
.chat-msg-error p { color: var(--danger); }
.chat-form { display: flex; flex-direction: column; gap: var(--space-1); }
.chat-form textarea {
  width: 100%;
  font: inherit;
  resize: vertical;
}
.chat-form button { align-self: flex-end; }
.chat-tos { max-width: 640px; margin: 0 auto; }
.chat-tos-points { color: var(--text-dim); line-height: 1.6; }
.chat-tos-points li { margin-bottom: var(--space-1); }
.chat-tos-fine { font-size: var(--fs-base-sm); color: var(--text-dim); }
