
:root {
  --bg: #0b0d10;
  --surface: #11151a;
  --surface-2: #161b22;
  --border: #1f2630;
  /* Control boundary (#1395 WS7). --border is a decorative hairline
     (~1.2: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;
  --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);
}
* { box-sizing: border-box; }
/* 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;
  /* Antialias on dark backgrounds: subpixel rendering thickens
     light-on-dark text and we want crisp, not chunky. */
  -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;
}
main { flex: 1 0 auto; }
.footer { flex-shrink: 0; }
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
/* 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">, set by
   page() when ``nav_path`` is provided. Signed-in dashboard pages
   use this shell; public marketing pages keep the original 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 — no JS, matching the rest of layout.py's inline-only
   philosophy. 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;
  background: var(--surface);
  border-right: 1px solid var(--border);
  /* 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-block;
  padding: var(--space-1);
  font-size: var(--fs-md);
}
.side-nav { display: flex; flex-direction: column; gap: var(--space-4); flex: 1; }
.nav-group { display: flex; flex-direction: column; gap: var(--space-1); }
.nav-group-label {
  font-size: var(--fs-micro);
  /* Kicker/eyebrow exception (issue #527): the small-caps pattern
     (micro size + uppercase + tracked) needs the extra weight to
     hold legibility against the dim color; --fw-bold reads thin at
     12px under uppercase. Same exception applies to ``p.kicker``,
     ``.tool-list-label``, ``.vignette-label``, ``.demo-prompt::
     before``, ``.demo-turn-label`` (all on the same micro-uppercase
     pattern) and the ``.tier-features li::before`` ✓ icon. */
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-dim);
  padding: 0 var(--space-2) var(--space-1);
  margin-top: var(--space-1);
}
.nav-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  /* 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-item:hover {
  background: var(--surface-2);
  color: var(--text);
  text-decoration: none;
}
.nav-item.active {
  background: var(--surface-2);
  color: var(--text);
  border-left-color: var(--nav-active);
}
.nav-item .nav-meta {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  color: var(--text-dim);
}
.nav-item.active .nav-meta { color: var(--nav-active); }
/* Attention count (#980) — the reconcile "Needs attention" item's
   badge reads as a warn tone so a non-zero worklist stands out in the
   sidebar, matching the in-page ``.pill.warn`` badge on the scorecard.
   Stays warn even when the item is active (an open worklist is still
   pending regardless of which page you're on). */
.nav-item .nav-meta.warn,
.nav-item.active .nav-meta.warn { color: var(--warn); }
.nav-dot {
  display: inline-block;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  /* Default to the untouched/hollow shape so state carries a
     non-color cue (issue #626): filled disc = done/partial,
     hollow ring = default/untouched. Colorblind users read the
     shape; the optional fraction meta ("6/7") carries the
     partial-vs-done distinction in text. */
  background: transparent;
  border: 1px solid var(--text-dim);
  box-sizing: border-box;
  flex-shrink: 0;
}
/* green = done/healthy, amber = partial, gray = default/untouched —
   one status vocabulary shared with the tile pills (issue #626). */
.nav-dot.ok { background: var(--success); border-color: var(--success); }
.nav-dot.warn { background: var(--warn); border-color: var(--warn); }
.nav-dot.dim { background: transparent; border-color: var(--text-dim); }
/* "Advanced" disclosure fold inside a nav group (#1342): the group's
   long-tail rows (Data vendor keys under "Ask your AI") collapse
   behind a closed-by-default <details> so the capability rows lead.
   The summary reuses the nav-item metrics (44px touch target, same
   padding/radius) with the how-it-works caret idiom. */
.nav-advanced summary {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 2.75rem;
  padding: var(--space-2);
  border-radius: 6px;
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
  cursor: pointer;
  list-style: none;
}
.nav-advanced summary::-webkit-details-marker { display: none; }
.nav-advanced summary::before {
  content: "▸";
  display: inline-block;
  transition: transform 0.15s ease;
}
.nav-advanced[open] summary::before { transform: rotate(90deg); }
.nav-advanced summary:hover { background: var(--surface-2); color: var(--text); }
.sidebar-foot {
  border-top: 1px solid var(--border);
  padding-top: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.sidebar-foot .who {
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
  font-family: var(--font-mono);
  padding: 0 var(--space-2);
  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; }
.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-6) 0 4rem; /* 4rem bottom = generous page-bottom breathing; --space-12 (3rem) would crowd the foot on long dashboards */ }
body.layout-sidebar .shell { max-width: 60rem; margin: 0 auto; padding: 0 var(--space-6); }
body.layout-sidebar > .topbar:not(.mobile-only) { display: none; }
body.layout-sidebar .footer { margin-left: 14rem; /* sidebar offset, see above */ }

/* Mobile (≤640px): sidebar becomes a slide-in drawer. */
@media (max-width: 640px) {
  body.layout-sidebar > .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-4) 0 var(--space-12); }
  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);
}
.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); }
.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;
}
/* The public header's collapse toggle is a mobile-only control; the
   sidebar shell's hamburger (inside .topbar.mobile-only) keeps its
   own visibility rules. */
.topbar.collapsible .hamburger { display: none; }
.topbar .who {
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
  font-family: var(--font-mono);
}
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;
}
/* 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 the dashboard uses (.nav-group-label /
     .tile-section-head), 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-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;
  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;
}
/* #1458 — the per-step "More detail" fold inside a stepper row. The
   page-level variant's bottom margin is sized for standalone use;
   inside a tile row it doubles the row's own rhythm, so zero it and
   lean on the row's padding. */
.setup-step-row details.how-it-works.setup-step-detail {
  margin: var(--space-2) 0 0;
}
.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);
}
.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
   (.hero-card, .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); }
/* Hero variant for anchor-as-card: the top-margin reset frames
   the recommended-action surface above the regular tile grid.
   Stacks on top of ``.card`` (so all card chrome inherits) and
   ``.hero-card-link`` (so an ``<a>`` wrapping the tile drops its
   underline / inherits text color). The focal accent-left rule
   is shared with ``.tile.hero`` and lives in the grouped rule
   below (L5 — design-system audit). Introduced for
   ``_hero_card_html`` on /dashboard/connect (issue #297); future
   hero CTAs (upgrade-to-Pro, broker-link) reuse the same class
   instead of duplicating inline styles. */
.hero-card {
  margin-top: var(--space-6);
}
.hero-card-link {
  display: block;
  text-decoration: none;
  color: inherit;
}
.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);
}
.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;
}
.tile-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: auto;
  padding-top: var(--space-2);
}
.tile-actions .btn { white-space: nowrap; }
/* Primary identifier on a tile whose own content is the headline —
   e.g. the tool name in the /dashboard/tools catalog. Mono so a
   symbol like `get_price_history` reads as an identifier, but
   normal-case and full-text color so it doesn't inherit the dim
   label treatment used for header chrome on metric tiles. */
.tile-title {
  font-family: var(--font-mono);
  font-size: var(--fs-base);
  font-weight: var(--fw-bold);
  color: var(--text);
  word-break: break-word;
}
/* Whole-tile-clickable variant. Pairs with .hero-card-link so the
   wrapping <a> drops its underline and inherits text color, and
   adds a subtle hover affordance so the click target is obvious. */
a.tile.hero-card-link:hover {
  border-color: var(--text-dim);
  background: var(--surface-2);
}
/* Focal accent-left rule — single point of truth for the
   "this is the focal one" framing on single-card heroes
   (``.hero-card``, issue #297, on /dashboard/connect) and
   tile-grid heroes (``.tile.hero``, issue #422, used by the
   Overview route to promote one tile per dashboard state).
   ``.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. */
.hero-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. */
.skill-install {
  margin-top: var(--space-1);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-2);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.skill-install-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.skill-install-label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
}
.skill-install-hint {
  margin: 0;
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
  line-height: 1.55;
}
.skill-install-hint code {
  /* em-relative shrink (not a token): monospace inline <code> renders
     visually larger than the surrounding sans, so we scale it down
     relative to its parent. A rem-based --fs-* token would decouple
     the shrink from the parent and reintroduce the imbalance. */
  font-size: 0.9em;
}
/* Tertiary "Personalize your analyst / open editor" link rendered
   below the install sub-card. Sized smaller than the .btn family
   so it reads as a navigation hint, not a competing action. */
.setup-step-edit-link {
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
  text-decoration: none;
}
.setup-step-edit-link:hover {
  color: var(--text);
  text-decoration: underline;
}
.card > .muted-note:first-of-type {
  margin-top: 0;
  margin-bottom: var(--space-4);
  max-width: 48rem;
}
.section-stack > * + * { margin-top: var(--space-6); }
/* Grouped tile sections on the Overview (issue #626) — the grid is
   split into AMBIENT / YOUR RECORD / ASK YOUR AI blocks (#1342) that
   mirror the sidebar's grouped nav so the two surfaces reinforce one
   mental model. The header reuses the sidebar nav-group-label
   treatment. */
.tile-section + .tile-section { margin-top: var(--space-6); }
/* Fresh-account Overview fold (#1461). While the funnel is open and
   the user has no record yet, the grid leads with the next step and
   the rest of the map sits behind this disclosure. Deliberately
   styled like the page-level "How this works" expander (quiet,
   text-weight summary, no button chrome) so it reads as a way in
   rather than competing with the hero tile's one primary CTA. */
details.dashboard-more { margin-top: var(--space-6); }
details.dashboard-more > summary {
  cursor: pointer;
  list-style: none;
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
  font-weight: var(--fw-medium);
}
details.dashboard-more > summary::-webkit-details-marker { display: none; }
details.dashboard-more > summary::before {
  content: "▸";
  display: inline-block;
  margin-right: var(--space-2);
  transition: transform 0.15s ease;
}
details.dashboard-more[open] > summary::before { transform: rotate(90deg); }
details.dashboard-more > summary:hover { color: var(--accent); }
details.dashboard-more > .section-stack { margin-top: var(--space-5); }
.tile-section-head {
  font-size: var(--fs-micro);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-dim);
  margin: 0 0 var(--space-3);
}
/* Returning-user continuation panel on the Overview (#1395 WS6).
   Rides .card for its section rhythm; these rules only shape the
   inner list and rows. */
.activity-continue { margin: 0 0 var(--space-3); }
.activity-recent {
  list-style: none;
  margin: 0 0 var(--space-3);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.activity-attention { margin: 0 0 var(--space-2); }
.activity-scorecard { margin: 0; }
/* Brokers page (multi-connection): a dense connections list + add picker. */
.brokers-section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin: var(--space-6) 0 var(--space-3);
  flex-wrap: wrap;
}
.brokers-section-head h2 { margin: 0; }
/* Once a connection exists the list leads the page (#1375); adding
   another demotes to this compact secondary button in the section head,
   beside the "N linked" count, not the empty state's accent CTA. */
.brokers-head-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.btn.add-connection-compact {
  padding: var(--space-1) var(--space-3);
  font-size: var(--fs-base-sm);
}
.connections-list { display: flex; flex-direction: column; gap: var(--space-3); }
.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 (the `.credential-row`
   idiom), 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 (the `.credential-head` idiom) 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); }
.connection-row .rename .inline-form {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  margin-top: var(--space-2);
  flex-wrap: wrap;
}
.add-connection-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: var(--space-3);
}
/* Both classes on purpose: the flex layout must out-rank ``.btn``'s
   ``display: inline-block``, declared later in this sheet. At equal
   specificity that later rule wins and the CTA label renders glued
   to its trailing glyph. The glyph now sits *beside* the label via
   `gap`. It used to use `justify-content: space-between`, which (once
   #1333 un-glued this control by widening the track) pinned the glyph
   ~1000px away at the far edge of the full-width track — the
   over-correction #1375 fixes, now that auto-fill bounds the track. */
.btn.add-broker {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  text-align: left;
}
.coming-soon { margin-top: var(--space-4); }
.coming-soon-pills {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-2);
}
.credentials {
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--border);
  margin-top: var(--space-1);
}
.credential-row {
  padding: var(--space-4) 0;
  border-bottom: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.credential-row:last-child { border-bottom: none; }
.credential-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.credential-head strong { font-size: var(--fs-base); }
.credential-head .status {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-style: normal;
}
summary.credential-head {
  cursor: pointer;
  list-style: none;
  padding-right: var(--space-5);
  position: relative;
}
summary.credential-head::-webkit-details-marker { display: none; }
summary.credential-head::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;
}
details[open] > summary.credential-head::after {
  margin-top: -0.15rem;
  transform: rotate(45deg);
}
summary.credential-head:hover::after { border-color: var(--accent); }
.credential-row .blurb {
  margin: 0;
  font-style: normal;
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
}
.credential-row .blurb a { color: var(--text-dim); border-bottom: 1px dotted var(--text-dim); }
.credential-row .blurb a:hover { color: var(--accent); border-bottom-color: var(--accent); text-decoration: none; }
.credential-row.highlight {
  box-shadow: inset 3px 0 0 0 var(--accent);
  padding-left: var(--space-3);
  background: var(--surface-2);
  border-radius: 0 var(--radius) var(--radius) 0;
}
.banner .subtle { color: var(--text-dim); font-size: var(--fs-base-sm); }
h2.cred-group {
  font-size: var(--fs-base-sm);
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-dim);
  margin: 0 0 var(--space-1) 0;
}
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): same rationale as
     .skill-install-hint code above — keep inline <code> visually
     proportional to its parent's 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;
}
.tool-list-label {
  font-size: var(--fs-micro);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--accent);
  margin-right: var(--space-2);
}
.tool-pill {
  display: inline-block;
  padding: var(--space-1) var(--space-2);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  color: var(--text-dim);
}
/* `display: contents` lifts the details wrapper out of the flex
 * layout so its summary (the "+N more" chip) and the expanded
 * .tool-list-extra siblings behave as direct children of .tool-list.
 * That lets the extras wrap onto a new line via flex-basis: 100%
 * without pushing the toggle chip out of line with the visible pills.
 */
.tool-list-more {
  display: contents;
}
summary.more-toggle {
  cursor: pointer;
  list-style: none;
  font-family: var(--font-sans);
  color: var(--text);
}
summary.more-toggle::-webkit-details-marker {
  display: none;
}
summary.more-toggle:hover {
  border-color: var(--text-dim);
  color: var(--accent);
}
.tool-list-extra {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  margin-top: var(--space-1);
  flex-basis: 100%;
}
/* Shared input skin (#985, extended #1206). One base, the .input class,
   owns the surface + border + radius + padding + text color + accent
   focus-border that .credential-form / .form-stack / .scorecard-filters
   / .reconcile-tag-form / .tool-search-input / .profile-form each
   redeclared identically; #1206 folds in the last two one-offs, the
   step-2 client picker (.skill-install-label select) and the coming-soon
   waitlist field (.notify-me-form input), which had drifted onto their
   own near-copies. Per-system deltas (width / max-width / font-family /
   font-size / flex / resize) stay on the individual rules below; the
   step-2 picker additionally keeps a compact padding as a documented
   override (its rule sits just after the focus block so that override
   wins the cascade). The selector list folds in each system's existing
   matched set (descendant input/select/textarea, or the
   .tool-search-input class) alongside .input, so this is a pure CSS
   extraction: 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 the stacking forms already had, plus the
   two #1206 one-offs, whose new focus accent is the one intended visual
   change of that pass. (.scorecard-filters / .tool-search-input had
   neither and stay intentionally absent from the focus lists, keeping
   their original behavior.) A single ::placeholder treatment (dim +
   italic) is hoisted alongside the skin (#1206) so placeholder tone no
   longer varies page to page; per-system font-family on the placeholder
   (e.g. .credential-form forcing sans) stays local. 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,
.credential-form input[type="text"],
.form-stack input[type="text"],
.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,
.scorecard-filters input,
.scorecard-filters select,
.reconcile-tag-form select,
.reconcile-tag-form input[type="text"],
.reconcile-tag-form textarea,
.tool-search-input,
.profile-form input[type="text"],
.profile-form input[type="number"],
.profile-form input[type="email"],
.profile-form input[type="date"],
.profile-form select,
.profile-form textarea,
.skill-install-label select,
.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;
}
.input:focus,
.credential-form input[type="text"]: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,
.profile-form input:focus,
.profile-form select:focus,
.profile-form textarea:focus,
.skill-install-label select:focus,
.notify-me-form input[type="email"]:focus { border-color: var(--accent); }
.input:focus:not(:focus-visible),
.credential-form input[type="text"]: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),
.profile-form input:focus:not(:focus-visible),
.profile-form select:focus:not(:focus-visible),
.profile-form textarea:focus:not(:focus-visible),
.skill-install-label select:focus:not(:focus-visible),
.notify-me-form input[type="email"]: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
   (.credential-form and .profile-form already did this; .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 and
   .credential-form overrides back to sans below. Selects carry no
   placeholder and are absent from the list. */
.input::placeholder,
.credential-form input[type="text"]::placeholder,
.form-stack input::placeholder,
.form-stack textarea::placeholder,
.scorecard-filters input::placeholder,
.reconcile-tag-form input[type="text"]::placeholder,
.reconcile-tag-form textarea::placeholder,
.tool-search-input::placeholder,
.profile-form input::placeholder,
.profile-form textarea::placeholder,
.notify-me-form input[type="email"]::placeholder,
.chat-form textarea::placeholder {
  color: var(--text-dim);
  font-style: italic;
}
/* Step-2 client picker deltas (#1206). It is a skin member (surface,
   border, radius, focus accent, and native-chrome theming all come from
   the skin above), but keeps a compact 4/8 padding and smaller type than
   the skin's 8/12 default because it sits inline in a tight sub-card.
   This rule sits after the skin so the padding override wins the cascade
   (same specificity, later source order). */
.skill-install-label select {
  padding: var(--space-1) var(--space-2);
  font-family: inherit;
  font-size: var(--fs-base-sm);
}
/* 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 both skins of the unified
   labelled-field system (#1211) — ``.form-stack`` and ``.profile-form``
   — plus the single-input ``.credential-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"],
.profile-form input[aria-invalid="true"],
.profile-form select[aria-invalid="true"],
.profile-form textarea[aria-invalid="true"],
.credential-form 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;
}
/* Per-field "Clear this value" control (#978). Sits as a sibling
   directly under a field (not nested in the field's <label>, which would
   be invalid HTML) so the no-accidental-clear default (blank = no
   change) and an explicit null coexist. Compact + dim so it reads as a
   secondary affordance, not a primary input. The negative margin cancels
   most of the form-stack ``gap`` so the control hugs the field it clears
   (total spacing ≈ var(--space-1)) instead of floating midway to the
   next field. */
.form-stack .field-clear {
  display: flex;
  margin-top: calc(var(--space-1) - var(--space-5));
}
.form-stack .field-clear label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--fs-base-sm);
  font-weight: var(--fw-regular);
  color: var(--text-dim);
  cursor: pointer;
}
.form-stack .field-clear input[type="checkbox"] { width: auto; margin: 0; }
.credential-form {
  display: flex;
  align-items: stretch;
  gap: var(--space-2);
  margin-top: var(--space-1);
  flex-wrap: wrap;
}
.credential-form input[type="text"] {
  flex: 1 1 16rem;
  min-width: 0;
  font-size: var(--fs-base);
  font-family: var(--font-mono);
}
.credential-form input[type="text"]::placeholder {
  /* The field text is mono, but the placeholder reads better in sans; the
     shared placeholder rule (#1206) supplies the dim + italic. */
  font-family: var(--font-sans);
}
.btn {
  display: inline-block;
  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; }
.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); }
/* ``.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`` so it stays well above
   WCAG AA contrast on the dark surface. */
.pill.danger { background: var(--danger-bg); color: var(--danger); }
/* ``.pill.pnl-up`` / ``.pill.pnl-down`` (issue #982) — P&L-as-a-pill
   tone for a realized gain / loss rendered as a chip (the auto-stub
   realized-P&L chip on the journal symbol page). Same fill recipe as
   ``.ok`` / ``.danger`` but pinned to the semantic ``--pnl-up`` /
   ``--pnl-down`` tokens, NOT the brand ``--accent`` / ``--danger``, so
   the gain/loss *meaning* is decoupled from CTA + destructive intent
   (the table-cell ``.pnl-pos`` / ``.pnl-neg`` do the same for plain
   numeric cells). The gain keeps the exact green of the prior
   ``.pill.ok`` (visual parity); the loss now reads as a loss (red),
   replacing the earlier yellow ``.pill.warn`` that signalled
   "pending", not "down". */
.pill.pnl-up { background: var(--accent-bg-soft); color: var(--pnl-up); }
.pill.pnl-down { background: var(--danger-bg); color: var(--pnl-down); }
/* ``.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 stays well above WCAG AA
   contrast against the dark surface. */
.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);
}
/* ``.pill.off`` (issue #424) — deploy-disabled surfaces where the
   feature is gated off by an env flag the operator controls, not by
   anything the user can do. Reads as "locked away" via a dashed
   outline rather than the filled background of ``.pill.dim`` so a
   user comparing two muted tiles can tell at a glance which is
   "you haven't acted" (``.pill.dim`` — fillable background) vs.
   "this isn't available on this deploy" (``.pill.off`` — dashed
   border, no fill). Same color tone as dim so neither shouts.
   Subscription "cancelled" stays on ``.pill.dim`` because the
   meaning there is "inactive" rather than "off". */
.pill.off {
  background: transparent;
  color: var(--text-dim);
  border: 1px dashed var(--border-strong);
  /* dashed border eats ~2px vs solid fill; 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);
  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"]: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-form's 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); }
/* ``.setup-progress`` (issue #417) — real progress bar on
   /dashboard/setup. Reuses the ``.banner-progress`` shape so the
   onboarding page reads the same as the dashboard's "Finish setup"
   banner. Two stacked bars: required progress in the accent color
   (the highest-signal element on the page after the H1), optional
   progress as a thinner secondary bar in ``--text-dim`` so it
   informs without competing. The numeric label sits in ``--text``
   instead of the page's ``.muted-note`` (italic-dim) class so it
   doesn't read like a hint. */
.setup-progress { margin: var(--space-2) 0 var(--space-6); }
.setup-progress .banner-progress.required > span { background: var(--accent); }
.setup-progress .banner-progress.optional {
  height: 0.2rem;
  margin-top: var(--space-2);
}
.setup-progress .banner-progress.optional > span { background: var(--text-dim); }
/* #632 — the required count is the headline fact of a funnel page,
   so it leads at body size in ``--text``; the optional count stays
   demoted at ``--fs-base-sm`` / ``--text-dim`` so it informs without
   competing. Upright (non-italic) by inheritance — the count is real
   content, not the ``.muted-note`` affordance-hint treatment. */
.setup-progress-label {
  margin: var(--space-2) 0 0;
  color: var(--text);
  font-size: var(--fs-base);
  font-variant-numeric: tabular-nums;
}
.setup-progress-label.optional {
  margin-top: var(--space-1);
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
}
/* #632 — "Continue → {next step}" primary button promoted next to
   the progress bar so the next action isn't below the fold. */
.setup-continue { margin: 0 0 var(--space-6); }
.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); }
.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-6) }``
   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: 60rem;
  padding-left: var(--space-6);
  padding-right: var(--space-6);
}
.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); }
/* Registrations-paused access line, demoted below the hero CTA (#627). Neutral
   --text-dim, not the green .kicker accent — the access gate must not
   out-shout the value prop or the one green CTA. */
.hero-paused-note {
  margin: 0 0 var(--space-3);
  font-size: var(--fs-base-sm);
  line-height: 1.5;
  color: var(--text-dim);
}
.hero-paused-note a { color: var(--text-dim); text-decoration: underline; }
.hero-paused-note 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 lede value claim (#818 P1) — p.lede sets --text-dim for the
   "without Slatemark" setup framing; the value claim that follows is
   promoted back to full --text so the page's core sentence isn't
   rendered in the de-emphasized secondary color. */
p.lede .lede-claim { 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; }
.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); }
/* Flex-wrap container for a stack of inline chips/pills inside a
   table cell (e.g. the journal status column: status pill + realized
   P&L + disposition chip). ``gap`` spaces them without a margin-left
   that would survive as a left indent when a narrow column wraps a
   chip onto its own line. Row gap is tighter than the column gap so
   wrapped chips read as one cluster. */
.cell-chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-2);
}
/* Render a <form> on the same row as adjacent text or buttons.
   POST forms are block by default; this is the inline-flow
   wrapper used by Stripe-cancel / disconnect / unlink / billing-
   portal buttons that sit alongside other actions. */
.inline-form { display: inline; }

/* Labelled-field stack — the form-stack skin of the unified
   labelled-field system (#1211), used by the rules, skills,
   journal-edit, 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 (see the
   ``labelled_field`` helper). ``.profile-form`` (below) is the second
   skin over the *same* association pattern: 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. The two skins
   differ only in class names and field widths; the markup contract
   (for/id, aria-describedby help, one help position) is identical and
   produced by the shared ``labelled_field`` / ``field_control_attrs``
   helpers, so a future labelled form reuses one of the two skins
   rather than inventing a third. */
.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. Mirrors ``.profile-row`` so the two skins share spacing
   behavior. */
.field-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.form-stack .field-label {
  display: block;
  font-weight: var(--fw-medium);
}
.form-stack .field-help {
  display: block;
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
  line-height: 1.4;
}
/* Multi-fragment help group (the rules editor stacks a description, a
   seed-default hint, and a nested-value usage hint under one aria id).
   One element carries id="{field}-help" so aria-describedby references
   the whole description; the fragments inside space with a tight gap. */
.field-help-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.form-stack input[type="text"],
.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 render
   through ``layout.page(..., show_nav=False)`` like every other
   branded surface — the clean sign-in shell, so no distracting nav on
   a trust-sensitive consent step — instead of the old standalone
   light-theme ``_PAGE_STYLE``. 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 th.col-actions,
.data-table td.col-actions { text-align: right; padding-right: 0; }
/* Multi-button action cell (issue #760) — lay several action buttons in
   a right-aligned row that wraps as a unit on narrow widths, with each
   button kept to a single line so a hyphenated label like "Re-download"
   never breaks mid-word. */
.row-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--space-2);
}
.row-actions .btn { white-space: nowrap; }
/* Column-level cell modifiers — combine on a <td> (or, for
   ``col-dim``, on an inline child) to vary alignment, weight, or
   color without an inline style. */
.data-table td.col-strong { font-weight: var(--fw-medium); }
.data-table .col-dim { color: var(--text-dim); }
.data-table td.col-nowrap { white-space: nowrap; }
.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). */
.scorecard-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-3) var(--space-4);
}
.scorecard-filters label {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: var(--fs-base-sm);
  color: var(--text-dim);
}
.scorecard-filters input,
.scorecard-filters select {
  font-family: inherit;
  font-size: var(--fs-base-sm);
}
.scorecard-filters .filter-actions {
  display: flex;
  gap: var(--space-2);
  align-items: center;
}
.data-table th.sortable a {
  color: inherit;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}
.data-table th.sortable a:hover { color: var(--accent); text-decoration: none; }
.data-table th .sort-arrow { color: var(--accent); font-size: var(--fs-micro); }
.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); }
/* R-coverage "N of M" hint under an Avg R cell (#582): same small, dim
   sub-line as the win-rate confidence band. */
.r-coverage { display: block; color: var(--text-dim); font-size: var(--fs-micro); }
.data-table td.unlock-note {
  color: var(--text-dim);
  font-style: italic;
  text-align: left;
  white-space: normal;
}
.lowconf-badge {
  display: inline-block;
  margin-left: var(--space-2);
  padding: 0 var(--space-1);
  border: 1px solid var(--warn);
  border-radius: var(--radius);
  color: var(--warn);
  font-size: var(--fs-micro);
}
/* Portfolio rollup as headline stat tiles (#625). Reuses the shared
   ``.tile`` / ``.value`` component (so it matches the overview grid) but
   packs tighter than the default 20rem ``.tile-grid`` — these are single
   numbers, not content tiles — and steps the value up to ``--fs-lg`` so
   the portfolio figures read as the page's headline. Reflows to fewer
   columns / single-column on narrow viewports via ``auto-fit``. */
.scorecard-stats {
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  margin-bottom: var(--space-4);
}
.scorecard-stats .value { font-size: var(--fs-lg); }
.scorecard-stats .win-ci { margin-top: var(--space-1); }
/* Setups-card action row (#625): the "Merge duplicate tags" /
   "Reconcile trades" links are navigation, not caveats — a single
   button row instead of a stack of muted notes. */
.scorecard-actions {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-top: var(--space-4);
}
/* 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); }
/* Trades-needing-attention inbox (#731): the per-trade "add a setup"
   form. A trade summary above, then the setup picker + optional close
   note + submit on one wrapping row so clearing an item is a single
   glance-and-pick. */
.reconcile-row {
  padding: var(--space-3) 0;
  border-top: 1px solid var(--border);
}
.reconcile-row:first-of-type { border-top: none; }
.reconcile-stub { margin: 0 0 var(--space-2); }
.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-note { flex: 1 1 16rem; }
.reconcile-tag-note input { width: 100%; }
/* Reconcile tag form controls (trades-needing-attention inbox, #731):
   the shared .input skin (#985) supplies the dark-theme surface /
   border / focus; these are just the reconcile-only deltas (select
   min-width, font sizing). */
.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;
}
/* Inline exit-reason capture on the journal symbol page (#1409). The
   form rides a full-width row directly beneath the trade it annotates,
   because a facet picker plus a textarea does not fit the thesis column.
   It reuses .reconcile-tag-form (and so the shared .input skin) so both
   capture surfaces look identical; these are the table-context deltas. */
.rt-capture > td {
  padding: 0 0 var(--space-3);
  border-top: none;
}
.capture-form { align-items: flex-end; }
/* 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); }
/* Jump-nav under the inbox lede (trades-needing-attention, #731):
   a quiet horizontal row of per-queue counts as anchor links
   (e.g. "3 to finish · 2 to review") so a long inbox is one click away
   from each section. */
.reconcile-jumpnav {
  display: flex;
  gap: var(--space-3);
  flex-wrap: wrap;
  align-items: center;
  font-size: var(--fs-base-sm);
  color: var(--text-dim);
  margin-bottom: var(--space-4);
}
.reconcile-jumpnav a {
  color: var(--accent);
  text-decoration: none;
}
.reconcile-jumpnav a:hover { text-decoration: underline; }
/* 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); }
/* Setups pagination: the per-setup table paginates (the portfolio
   rollup + insight card always reflect the whole history). The count
   note sits above the table; the pager (Prev / "Page X of Y" / Next)
   sits below it, query-param driven, no JS. */
.setups-count { margin-bottom: var(--space-2); }
.scorecard-pager {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-top: var(--space-3);
}
.scorecard-pager .pager-status {
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
}
/* Journal catalog view control (#1346): a two-state, query-param
   driven filter (Open and planned / All trades). The active state is a
   non-anchor <span> so it is not a tab stop, matching the pager's
   disabled-link shape above. */
.journal-view-filter {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-top: var(--space-3);
}
/* 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); }
.scorecard-filters-disclosure[open] .scorecard-filters { margin-top: var(--space-3); }
.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);
}
.insight-card .insight-eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: var(--fs-micro);
  color: var(--accent);
  margin: 0 0 var(--space-2);
}
.insight-card .insight-headline {
  font-size: var(--fs-lg, var(--fs-base));
  line-height: 1.4;
  margin: 0;
}
.insight-card .insight-headline strong { color: var(--text); }
.insight-card .insight-gap {
  margin: var(--space-3) 0 0;
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
}
.insight-card .insight-disclaimer {
  margin: var(--space-4) 0 0;
  padding-top: var(--space-3);
  border-top: 1px solid var(--border);
  color: var(--text-dim);
  font-size: var(--fs-micro);
}
.insight-empty { color: var(--text-dim); }
/* 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;
}
/* Story-shaped journal entry (#1414). The "Running notes" history reads
   as a quiet sub-block inside the Your-read card; the raw record snapshots
   collapse into per-item disclosures in the Record details card. */
.notes-block > h3 {
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  margin: 0 0 var(--space-2);
}
.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); }
/* The truncated entry-id suffix tacked onto the journal entry
   ``<h1>`` (e.g. ``AAPL · 9f3a4b27c0d1…``). Subordinate to the
   symbol's display weight — dim color + body-size + mono so the
   id reads as a footnote, not a competing headline. */
.entry-id-suffix {
  color: var(--text-dim);
  font-size: var(--fs-base);
}
/* Long copyable identifier rendered on a tinted surface — the
   user's Slatemark URL (/dashboard/connected-apps) and the
   client_id / client_secret pair on the detail page. Same
   surface-2 + radius + padding chrome, with ``word-break`` so a
   long URL wraps inside its container instead of forcing a
   scrollbar. */
.endpoint-display {
  background: var(--surface-2);
  padding: var(--space-3);
  border-radius: var(--radius);
  word-break: break-all;
}
/* Data-table empty-state row. The single cell that spans all
   columns with the "Nothing here yet" affordance copy needs a
   little internal padding so the placeholder doesn't collide
   with the table border. */
.data-table td.empty-row { padding: var(--space-4); }
/* 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 {
  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);
}
/* Form-stack modifier — the manual-credential form on
   /dashboard/connected-apps is narrower than the default
   form-stack and tighter on the vertical rhythm, so the
   AI-client dropdown and the optional-label input don't sprawl
   across the card. */
.manual-credential-form { max-width: 32rem; gap: var(--space-3); }
/* Word-wrap utility — long copyable identifiers (URLs, client
   ids, secrets) need to wrap inside narrow ``<dd>`` and ``<li>``
   cells so they don't push the table or list into a horizontal
   scrollbar. */
.break-all { word-break: break-all; }
/* "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; }
/* 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); }

/* ===========================================================
   Onboarding (/dashboard/connect, /dashboard/connect/<client>)
   ===========================================================
   The pages under ``slatemark.web.onboarding`` carried the
   single heaviest concentration of inline ``style=""`` blocks
   before the L4 cleanup. Each block below replaces one recurring
   pattern (hero card, walkthrough step, copy-row variants,
   waitlist form, page title row). Names are deliberately scoped
   to the onboarding surface so the global vocabulary stays
   small — see the class-naming guide at the top of this file. */
.hero-card-meta {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-1);
}
.hero-card-title { margin: 0 0 0.35rem; font-size: var(--fs-lg); }
.hero-card-blurb { margin: 0; }
/* Provider-group heading on /dashboard/connect (e.g. "Anthropic",
   "OpenAI"). Subordinate to the page <h1> — dim color + medium
   weight + body-size so it groups the tile-grid below without
   competing with the headline. */
.provider-heading {
  margin: var(--space-6) 0 var(--space-3);
  font-size: var(--fs-base);
  color: var(--text-dim);
  font-weight: var(--fw-medium);
}
.coming-soon-blurb { margin: var(--space-2) 0 var(--space-4); }
.disclosure-summary.muted {
  color: var(--text-dim);
  font-weight: var(--fw-regular);
  padding: var(--space-2) 0;
}
/* Page-footer disclaimer (issue #848 finding C4): the onboarding
   not-an-adviser note sits at the page foot, matching the muted-footer
   convention the scorecard uses (.scorecard-disclaimer). Micro +
   --text-dim clears WCAG AA (~7:1 on --surface; see .muted-note). */
.disclaimer-blurb {
  margin-top: var(--space-5);
  font-size: var(--fs-micro);
}
/* 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;
}
/* Skill-install card chrome — the download-button row, the
   per-client install path hint, the attestation form gap. */
.download-row { margin: var(--space-4) 0; }
.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;
}
/* Walkthrough step list — three or four ordered steps per
   per-client onboarding page (Connect Claude Desktop, …). */
.walkthrough-list { list-style: none; padding: 0; margin: 0; }
.walkthrough-step { margin-bottom: var(--space-5); }
.walkthrough-step-title { font-size: var(--fs-base); margin: 0 0 0.4rem; }
.walkthrough-step-body { font-size: var(--fs-base); }
/* The final "Verify" beat is the celebrated first-win (#974): the
   first place a pasted prompt returns a real answer. Lift it with the
   focal accent-left + a faint wash so it reads as the payoff, not a
   dry checklist, and tint the lead line in accent. */
.walkthrough-step-win {
  border-left: 3px solid var(--accent);
  background: var(--accent-wash);
  border-radius: var(--radius);
  padding: var(--space-3) var(--space-4);
}
.first-win-lede {
  color: var(--accent);
  font-weight: var(--fw-medium);
  margin: 0 0 var(--space-2);
}
/* Existing-credentials status line — sits flush under the H1 so
   it reads as a subhead. */
.status-line { margin-top: -0.5rem; }
/* Page-title row — H1 sitting next to a status pill (per-client
   onboarding page). The `.flush` modifier on the H1 kills the
   default heading top-margin so the pill aligns. */
.page-title-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: 0 0 0.4rem;
}
/* Accent-tinted pill modifier — keeps the green-on-green
   "Recommended for new users" badge on the hero card from
   shouting via the solid accent fill. */
.pill.ok.accent-bg { background: var(--accent-bg); }
.mt-1 { margin-top: var(--space-1); }

/* ===========================================================
   Setup funnel (/dashboard/setup)
   =========================================================== */
.setup-lede-blurb { margin-top: -0.25rem; }
/* Top-level ordered list wrapping the required-step tiles —
   pulls the stepper off the page heading by ``--space-6`` and
   kills the default list-style. */
ol.setup-stepper {
  list-style: none;
  padding: 0;
  margin: var(--space-6) 0;
}
/* Variant used inside the optional-group disclosure — same kill
   of list-style but tighter top margin (no page heading above). */
ol.setup-stepper.nested { margin: var(--space-4) 0 0; }
.setup-step-row { margin-bottom: var(--space-3); }
.setup-step-body {
  margin: var(--space-2) 0;
  font-size: var(--fs-base);
  font-style: normal;
}
.setup-step-number {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.8rem;
  height: 1.8rem;
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
  font-weight: var(--fw-bold);
  margin-right: var(--space-3);
}
/* The boxed wrapper around the optional steps — surface-2
   border, radius, internal padding. Replaces the inline style on
   the setup-disclosure element. */
.setup-optional-group {
  margin: var(--space-6) 0;
  border: 1px solid var(--border);
  border-radius: 0.5rem;
  padding: var(--space-3) var(--space-4);
}
.disclosure-summary.plain {
  cursor: pointer;
  font-size: inherit;
  color: inherit;
  font-weight: var(--fw-bold);
}
/* Promoted Done-state CTA for setup step 2 (#566) — a "Make it
   useful" kicker, a "Personalize your analyst →" button, and a
   one-line value blurb. Block layout so the kicker and blurb stack
   around the button without competing for inline space. */
.personalize-cta {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
}
/* "Make it useful" kicker above the button (issue #566). Lifted out
   of the button label so the filled .btn carries only the verb
   phrase and hugs its content instead of stretching to a sentence's
   width. Quiet/dim so it reads as motivation, not a competing
   action. */
.personalize-cta-kicker {
  margin: 0;
  font-size: var(--fs-base-sm);
  font-weight: var(--fw-bold);
  color: var(--text-dim);
}
.personalize-cta-blurb {
  margin: 0;
  max-width: 60ch;
}
/* Inline "Mark as installed" / "I uninstalled it" submit form
   used for skill-install attestation (#566). The attest variant
   carries a ``.btn.secondary`` button; the undo variant carries a
   ``.link-button`` styled as a quiet text link so the done state
   reads as a step transition with an undo escape hatch, not as a
   persistent setting. */
.attestation-form {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}
.attestation-undo {
  display: inline-block;
}
.link-button {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.link-button:hover { color: var(--text); }
.link-button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Strip an anchor's text-decoration without flipping its color —
   used by the Download skill-md buttons where the underline
   would compete with the button chrome. */
.no-underline { text-decoration: none; }

/* ===========================================================
   Skill editor (/dashboard/skills, /dashboard/skills/<name>)
   =========================================================== */
.conflict-sibling-note {
  font-size: var(--fs-micro);
  color: var(--text-dim);
  margin-top: var(--space-1);
}
/* Page-level staleness banner actions (issue #760) — the catalog's
   "N skills need re-downloading" banner lays its per-skill download
   buttons in a wrapping row so multiple stale skills don't stack as a
   tall column of full-width buttons. */
.skill-stale-banner-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.relaxed-leading { line-height: 1.5; }
/* Skill detail page — three blurb sizes share the dim color but
   differ in weight (intro vs. body), size (default vs. small),
   and max-width (60ch keeps long explainers readable). */
.dim-blurb { color: var(--text-dim); max-width: 60ch; }
.dim-blurb-small {
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
  max-width: 60ch;
}
.install-lede { font-weight: var(--fw-medium); max-width: 60ch; }
/* Numbered "Use in your AI" steps (download -> place -> confirm). The
   ordered sequence replaces two equal-weight buttons that read as
   redundant twins; the numbered badges make the confirm step the next
   beat rather than a competing action. */
.install-steps {
  list-style: none;
  counter-reset: install-step;
  margin: var(--space-2) 0 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}
.install-step {
  counter-increment: install-step;
  display: flex;
  gap: var(--space-3);
}
.install-step::before {
  content: counter(install-step);
  flex: 0 0 auto;
  width: 1.7rem;
  height: 1.7rem;
  border-radius: 50%;
  background: var(--surface-2);
  color: var(--text-dim);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-base-sm);
  font-weight: var(--fw-medium);
}
.install-step-body { flex: 1 1 auto; min-width: 0; }
.install-step-title {
  margin: 0 0 var(--space-1) 0;
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
}
/* Confirm step starts quiet; the download click "arms" it (adds
   ``.armed`` via inline JS) so the attestation reads as the next
   action, not a button competing with Download. Visible and usable
   with JS off (progressive enhancement) — the dim is a nudge, never a
   gate. */
.install-step.confirm { opacity: 0.65; transition: opacity 0.2s ease; }
.install-step.confirm.armed { opacity: 1; }
.install-step.confirm.armed::before {
  background: var(--accent-bg);
  color: var(--accent);
}
/* Already-attested: the step reads done, badge takes the ok tone. */
.install-step.done::before {
  background: var(--accent-bg-soft);
  color: var(--accent);
}
.version-line { color: var(--text-dim); font-size: var(--fs-base-sm); }
/* Slot help text — a wider readable measure than the default help
   (a slot value is a sentence or two of analyst guidance, not a
   terse hint). Vertical spacing comes from the ``.field-row`` gap. */
.form-stack .slot-help { max-width: 60ch; }
/* Slot textarea variant — taller minimum, body font (not mono),
   tighter line-height than the journal-edit textareas because
   slot values are typically a couple of sentences of analyst
   guidance, not bulky free-form notes. */
.form-stack textarea.slot-textarea {
  font-family: inherit;
  font-size: var(--fs-base);
  line-height: 1.4;
}
/* Skill preview pre — slightly taller max-height than the
   default ``.code-block`` because rendered SKILL.md content
   benefits from showing several screen-fulls of context. */
.preview-block { max-height: 32rem; }

/* ===========================================================
   Framework rules (/dashboard/rules, /dashboard/rules/<name>)
   =========================================================== */
.rationale-p { margin: 0 0 0.9rem 0; }
.kind-blurb {
  color: var(--text-dim);
  margin: var(--space-2) 0 var(--space-4) 0;
}
/* "N customized of M total" progress marker on the catalog (#1349). */
.rules-summary {
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
  margin: 0 0 var(--space-4) 0;
}
/* Per-section rule count trailing the kind header — demoted so the
   friendly label stays the emphasis (#1349). */
.kind-count {
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
  font-weight: var(--fw-regular);
}
/* 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);
}
/* The rule-meta block ("Kind: …, Applies to: …, Governs: …")
   sits flush under the page lede. Dim + small + slightly looser
   line-height than the body so the three lines read as metadata,
   not a stacked sentence. */
.rule-meta {
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
  line-height: 1.6;
}
.kind-label-suffix {
  color: var(--text-dim);
  font-weight: var(--fw-regular);
}
/* Rationale (seed) block — wide tinted surface with vertical
   scroll for rules whose rationale runs to a half-page essay. */
.rationale-block {
  background: var(--surface-2);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius);
  max-height: 28rem;
  overflow: auto;
  font-size: var(--fs-base-sm);
  line-height: 1.55;
}
/* Micro modifier on the inline "Customized" pill next to each
   per-parameter label. Smaller than the default pill text so it
   sits cleanly beside the parameter's ``<code>`` name. */
.pill.micro { font-size: var(--fs-micro); }
/* Per-parameter help text — the rules editor's description sentence
   under each parameter name. A touch looser line-height than the
   default help; vertical spacing comes from the ``.field-row`` gap. */
.form-stack .field-help.param-help { line-height: 1.45; }
/* Rule parameter inputs/selects use a tighter ``max-width`` than
   the form-stack default — single numbers (caps, multipliers,
   day counts) read better in a 16rem field than a 24rem field. */
.form-stack input.rule-param,
.form-stack select.rule-param { max-width: 16rem; }

/* ===========================================================
   Persona picker (/onboarding/persona, /dashboard/persona)
   and magic-first-question panel
   =========================================================== */
.persona-grid {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  margin-top: var(--space-4);
}
.persona-card { cursor: pointer; padding: var(--space-4); }
.persona-card-head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
}
.persona-card input[type="radio"] {
  margin-top: var(--space-1);
  flex-shrink: 0;
}
.persona-card-title { flex: 1; }
.persona-card-tagline {
  margin: var(--space-2) 0 0;
  font-size: var(--fs-base);
  font-style: normal;
}
/* #973 — progressive disclosure: the one-line tagline is the default
   summary; the longer description stays collapsed until the user
   engages a card (hover, keyboard focus, or selecting it). Keeps the
   default grid scannable (five one-line cards) instead of a wall of
   ~350 words. Pure CSS, matching the .abbr tooltip philosophy; the
   global reduced-motion escape hatch already neutralizes the
   transition. The text stays in the DOM (max-height/opacity, not
   display) so it remains available to assistive tech. */
.persona-card-description {
  margin: 0;
  font-size: var(--fs-base-sm);
  max-height: 0;
  overflow: hidden;
  opacity: 0;
  transition: max-height 0.2s ease, opacity 0.2s ease, margin 0.2s ease;
}
.persona-card:hover .persona-card-description,
.persona-card:focus-within .persona-card-description,
.persona-card:has(input:checked) .persona-card-description {
  max-height: 16em;
  opacity: 1;
  margin-top: var(--space-3);
}
/* Magic-first-question list — one card per prompt, each with a
   bold label, the prompt text, and a Copy button. */
.magic-prompts-list {
  list-style: none;
  padding: 0;
  margin: var(--space-4) 0 0;
}
.magic-prompt {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-3);
}
.magic-prompt-label {
  font-weight: var(--fw-medium);
  margin-bottom: var(--space-2);
}
.magic-prompt-text {
  margin: 0 0 var(--space-3);
  color: var(--text-dim);
  font-size: var(--fs-base);
}

/* Prompt-pattern library (#761). ``.prompt-slot`` highlights a
   ``$SLOT`` fill-in token inside an example so the reader can see at a
   glance which parts they swap out. Monospace + a subtle chip so it
   reads as a placeholder, not body copy. */
.prompt-slot {
  font-family: var(--font-mono, monospace);
  font-size: 0.9em;
  padding: 0 var(--space-1);
  border-radius: var(--radius-sm);
  background: var(--surface-2, rgba(127, 127, 127, 0.12));
  color: var(--text);
  white-space: nowrap;
}
/* An example sits *inside* a ``.prompt-pattern`` card; a matching border
   + radius repeated the card's own frame, so the nesting read as a box in
   a box (#783). Demote it to a flat ``--surface-2`` fill with a left rule
   so the examples read as "items within this card", not peer cards. */
.prompt-example {
  background: var(--surface-2);
  border-left: 3px solid var(--border);
  border-radius: var(--radius-sm);
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-3);
}
.prompt-example-label {
  font-weight: var(--fw-medium);
  margin-bottom: var(--space-2);
}
.prompt-example-text {
  margin: 0 0 var(--space-3);
  color: var(--text-dim);
  font-size: var(--fs-base);
  max-width: 65ch;
}
/* The teaching annotation is body copy, so cap it at a readable measure
   rather than letting it run the full ~60rem dashboard column (#783),
   matching the opt-in ``p.lede.measure`` treatment from #760. */
.prompt-annotation {
  margin: 0 0 var(--space-3);
  color: var(--text-dim);
  font-size: var(--fs-base);
  max-width: 65ch;
}
.prompt-pattern {
  margin-bottom: var(--space-5);
}
/* Per-intent section divider (#783). A bare ``h2`` rendered identically
   to a card title and sat on the same rhythm as the gap between two
   cards, so the page's main organizing axis was its weakest signal.
   Re-level it to the kicker/eyebrow treatment (uppercase accent) plus
   ``space-6`` of top air so a section divider never reads at the same
   level as a card title. Adjacent-margin collapse keeps the first
   heading from doubling up on the intro/legend's bottom margin. */
.prompt-intent-heading {
  margin: var(--space-6) 0 var(--space-3);
  font-size: var(--fs-micro);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  line-height: 1.3;
  color: var(--accent);
}

/* ===========================================================
   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);
}

/* Tier-A "Advanced data sources" disclosure header on
   /dashboard/credentials. Slightly larger font + tighter padding
   than the default ``.disclosure-summary``. */
.disclosure-summary.disclosure-summary-tier-a {
  font-size: var(--fs-md);
  padding: var(--space-1) 0;
}

/* ===========================================================
   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);
}

/* ===========================================================
   Tool browser (/dashboard/tools)
   =========================================================== */
.tool-search-status { margin: var(--space-1) 0 var(--space-5); }
/* The tool-browser search form layers onto the .signin form
   pattern (used by the sign-in card) but stays single-row and
   stretches edge-to-edge so the search input doesn't render as
   a centered 32rem card on a dashboard page.

   The selector must out-specify ``form.signin`` (0,1,1) — which
   sets ``flex-direction: column`` and ``max-width: 22rem`` — so we
   qualify it as ``form.signin.tool-search-form`` (0,2,1). A bare
   ``.tool-search-form`` (0,1,0) loses the cascade and the button
   drops onto a second full-width row (#766). */
form.signin.tool-search-form {
  flex-direction: row;
  align-items: stretch;
  max-width: none;
  gap: var(--space-2);
  margin-top: var(--space-2);
}
/* Mobile: stacking is acceptable at narrow widths, and a
   full-width button is fine there (#766 responsive note). */
@media (max-width: 640px) {
  form.signin.tool-search-form { flex-direction: column; }
}
.tool-search-input {
  flex: 1;
  font-size: var(--fs-base);
  font-family: inherit;
}
.tool-search-reset {
  align-self: stretch;
  display: inline-flex;
  align-items: center;
}
.tool-category-heading {
  margin: 0 0 var(--space-1);
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.tool-category-example { margin: 0 0 var(--space-3); }
.tool-card-description {
  margin: 0;
  font-size: var(--fs-base-sm);
  color: var(--text);
}
.tool-meta-row { justify-content: flex-start; }
.tool-setup-note { margin: var(--space-2) 0 var(--space-4); }
.tool-example { font-size: var(--fs-base); }

/* Flash banner color for the broker-unlink success message on
   /dashboard/brokers. Uses the brand --accent green (#984) — a
   deliberate, brighter step up from the old #0a4 literal, which also
   lifts the success-banner contrast (#0a4 computed to ~5.96:1, the
   weakest color in the system per the a11y audit). */
.flash-ok { color: var(--success); }

/* 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); }
/* The standing coverage count under a capture-save payoff (#1413): its own
   line below the reward sentence. */
.flash-coverage { margin-top: var(--space-1); }

/* ===========================================================
   Journal (/dashboard/journal/symbol/<sym>)
   =========================================================== */
/* Per-column modifiers on the entries-for-symbol table. Combined
   with the generic ``.col-dim`` / ``.col-nowrap`` modifiers so
   each cell keeps its column-specific font tweaks without an
   inline style. */
.data-table td.col-when { font-size: var(--fs-base-sm); }
.data-table td.col-thesis {
  max-width: 48ch;
  line-height: 1.45;
}
.data-table .col-instr { font-size: var(--fs-base-sm); }
.data-table em.col-empty { opacity: 0.7; }
/* Edit-form intro blurb — slightly different bottom-margin from
   the generic ``.dim-blurb`` so the form sits closer to the
   explanation. */
.edit-blurb { margin: 0 0 1rem 0; max-width: 70ch; }

/* Account-profile editor — full-width section under the tile grid.
   Each panel (defaults, per-account override, add-account) is a
   stack of labelled inputs / textareas, mirroring the credentials
   row styling but with multiline support. */
.profile { display: flex; flex-direction: column; gap: var(--space-5); }
.profile-heading {
  font-size: var(--fs-base);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-dim);
  margin: var(--space-2) 0 0;
}
.profile .blurb {
  margin: 0;
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
}
.profile-account {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-4) var(--space-5);
  background: var(--surface-2);
  /* Several override blocks + the Add form now stack in one card
     (#203), so each needs its own vertical separation. */
  margin-bottom: var(--space-4);
}
.profile-account-head {
  font-size: var(--fs-base);
  margin: 0 0 var(--space-2);
  color: var(--text);
}
.profile-account .muted-note.mono {
  /* The raw account key (often a 64-char hashValue) under the
     display-name head: small, dim, and allowed to wrap. */
  font-size: var(--fs-base-sm);
  color: var(--text-dim);
  margin: 0 0 var(--space-2);
  word-break: break-all;
}
.profile-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-top: var(--space-2);
}
/* The Add-account framing fields nest one level below the form so a
   script (profile.py) can collapse them until an account key is picked;
   re-stack them with the same gap the form gives its own children.
   Scoped :not([hidden]) so the ``hidden`` attribute still wins over the
   author display rule and actually collapses the block. */
.profile-account-detail:not([hidden]) {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.profile-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.profile-label {
  font-size: var(--fs-base-sm);
  color: var(--text);
  font-weight: var(--fw-medium);
}
.profile-blurb {
  font-size: var(--fs-base-sm);
  color: var(--text-dim);
  line-height: 1.4;
  /* Cap the help-text measure so a long blurb wraps on a readable
     line length instead of running the full card width (~55rem on
     desktop), matching the .card > .muted-note:first-of-type cap. */
  max-width: 44rem;
}
.profile-form input[type="text"],
.profile-form input[type="number"],
.profile-form input[type="email"],
.profile-form input[type="date"],
.profile-form select,
.profile-form textarea {
  font-size: var(--fs-base);
  font-family: inherit;
  width: 100%;
}
/* Short single-value inputs hint their expected length instead of
   stretching the full card width on desktop; textareas stay
   full-width for free-text framing. */
.profile-form input[type="text"],
.profile-form input[type="email"],
.profile-form input[type="date"],
.profile-form select { max-width: 24rem; }
.profile-form input[type="number"] { max-width: 8rem; }
.profile-form textarea {
  font-family: var(--font-mono);
  font-size: var(--fs-base-sm);
  line-height: 1.5;
  resize: vertical;
  min-height: 5rem;
}
/* .profile-form placeholders (dim + italic) come from the shared
   placeholder rule (#1206); focus accent comes from the shared .input
   skin (#985). */
.profile-actions { display: flex; gap: var(--space-2); margin-top: var(--space-1); }
/* User ID is support-grade metadata, not something the user acts on —
   demote it below the email so identity confirmation doesn't outrank
   the editable framing above it. */
.profile-userid {
  margin: var(--space-2) 0 0;
  font-size: var(--fs-base-sm);
  color: var(--text-dim);
}
/* Read-first briefing (#1217): a saved briefing renders back as prose
   above the "Edit briefing" disclosure, the way the analyst receives it,
   so a return visit reads as "here is your briefing" rather than a wall
   of textareas. Short facts sit in a .kv grid; the free-text answers keep
   their line breaks via pre-wrap. */
.briefing-read {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}
.briefing-read .kv { max-width: 34rem; }
.briefing-field-label {
  font-size: var(--fs-base-sm);
  color: var(--text-dim);
  font-weight: var(--fw-medium);
  margin: 0 0 var(--space-1);
}
.briefing-field-value {
  margin: 0;
  white-space: pre-wrap;
  max-width: 48rem;
}
.briefing-empty { margin: 0 0 var(--space-4); }
/* The edit form sits behind a .disclosure-summary; nudge it below the
   read view / empty-state line. */
.briefing-edit { margin-top: var(--space-2); }
.briefing-edit-body { margin-top: var(--space-3); }

/* 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, always-accent 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); }

/* "How it works" step strip (#818 P3) — a compact three-step path
   (connect the endpoint -> download a skill -> ask your AI) so a
   curious-but-confused visitor grasps the bring-your-own-client model
   without leaving the homepage. The accent is reserved for the step
   index only, keeping the single-green discipline intact; steps wrap to
   a vertical stack under the .tile-grid breakpoint. */
.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);
}

/* Chat-replay transcript on the /examples page. Each .demo-transcript
   card walks one user question end-to-end: numbered move spine →
   prompt → analyst turn (tool calls collapsed behind a toggle so
   the prose reply leads) → prose answer → primary-source line. 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-left: 3px solid var(--border);
  margin-bottom: var(--space-4);
}
/* Numbered move spine. The eyebrow ("1 / 3 · Prompt-library move")
   stays a micro tracked label; the move name below is promoted to a
   real scannable landmark (#817 P1) so the three moves read as a
   spine the eye can hop down, not three identical gray stacks. The
   landmark is an anchor-carrying <h2> (#1298); this class overrides
   every global h2 property, so the promotion changed the outline,
   not the render. */
.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);
  /* Tracks the marketing h2 step (#1300) — the move names are the
     /examples page's real document outline, so they ride the same
     24px register the body.layout-marketing h2 rule sets; stating it
     here keeps this rule from silently losing to that one. */
  font-size: var(--fs-xl);
  font-weight: var(--fw-bold);
  line-height: 1.2;
  color: var(--text);
  margin: 0;
}
/* 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;
}
/* Tool calls behind a default-collapsed disclosure (#817 P1): the
   prose reply is the product, so it leads; the raw-JSON proof is
   one keyboard-reachable click away. A native details/summary
   element is focusable, toggles on Enter/Space, and announces
   expanded/collapsed state to a screen reader with no JS. */
.demo-tools-toggle {
  margin: 0;
  font-size: var(--fs-base-sm);
  color: var(--text-dim);
}
.demo-tools-toggle > summary {
  cursor: pointer;
  list-style: none;
  color: var(--text-dim);
  font-weight: var(--fw-medium);
  width: fit-content;
}
.demo-tools-toggle > summary::-webkit-details-marker { display: none; }
.demo-tools-toggle > summary::before {
  content: "▸";
  display: inline-block;
  margin-right: var(--space-2);
  transition: transform 0.15s ease;
}
.demo-tools-toggle[open] > summary::before { transform: rotate(90deg); }
.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. */
.demo-tool .demo-tool-call { color: var(--text); }
.demo-tool .demo-tool-arrow { color: var(--text-dim); }
.demo-tool .demo-tool-out { color: var(--text-dim); }
.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; }
.demo-source {
  margin: 0;
  font-size: var(--fs-micro);
  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, and stack each tool call above
   its result (the inline → and word-break wrapping ragged mid-token
   on a phone, #817 P2). aria-hidden arrow is hidden in the stacked
   layout since the vertical order already reads call-then-result. */
@media (max-width: 640px) {
  .demo-prompt { max-width: none; margin: 0; }
  .demo-prompt::before { text-align: left; }
  .demo-tool { display: flex; flex-direction: column; gap: var(--space-1); }
  .demo-tool .demo-tool-arrow { display: none; }
}
.demo-illustrative {
  font-size: var(--fs-base-sm);
  color: var(--text-dim);
  font-style: italic;
  border-left: 3px solid var(--warn);
  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 (Pro) 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: five
   rows — name / price / tagline / CTA / features — each sized to the
   tallest card, which pins the Free and Pro CTAs to the exact same y.
   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 5;
  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-features {
  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,
   no JS, matching the rest of layout.py's inline-only philosophy.
   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; }
}

/* Hide-on-mobile column. The journal symbol-detail table includes a
   thesis preview that's readable on desktop but squeezes to ~6 chars
   on a phone (the other 4 cols are nowrap and the table is min-32rem),
   wrapping each word onto its own line. Hide it on narrow viewports;
   the full thesis is one tap away on the entry page. */
@media (max-width: 640px) {
  .scroll-x .col-thesis { display: none; }
  .scroll-x.fits-mobile > table { min-width: 0; }
}

/* 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;
}

/* Responsive catalog list (used by /dashboard/rules). CSS grid so
   each row is name | status | description | action on desktop and
   collapses to a stacked card on phones. Status sits next to the
   identifier (left), conventional for a state indicator, so it no
   longer reads as a disabled sibling of the action button. Avoids
   the 32rem-min horizontal scroll the table layout forced. The
   columns live on the list and rows opt in via subgrid so the name
   column is sized once from the widest rule name — a per-row grid
   sizes max-content from that row alone, so descriptions wouldn't
   line up across rows. */
.catalog-list {
  display: grid;
  grid-template-columns: minmax(9rem, max-content) auto 1fr auto;
}
.catalog-row {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: subgrid;
  column-gap: var(--space-4);
  row-gap: var(--space-2);
  align-items: start;
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--border);
}
.catalog-row:last-child { border-bottom: 0; }
.catalog-row .catalog-name {
  font-weight: var(--fw-medium);
  font-size: var(--fs-base);
  line-height: 1.5;
  word-break: break-word;
}
/* Two-line name cell (#1349): the humanized title leads as a link
   (color inherited so it reads as a heading rather than default link
   blue; underline only on hover/focus), and the raw kebab-case slug
   sits beneath as a demoted mono reference key — still citable in a
   journal rule_ref or a tool call, no longer the loudest token. */
.catalog-row .catalog-name .catalog-title {
  display: inline-block;
  color: inherit;
  text-decoration: none;
}
.catalog-row .catalog-name .catalog-title:hover,
.catalog-row .catalog-name .catalog-title:focus-visible {
  text-decoration: underline;
}
.catalog-row .catalog-name .catalog-slug {
  display: block;
  margin-top: var(--space-1);
  font-size: var(--fs-base-sm);
  font-weight: var(--fw-regular);
  color: var(--text-dim);
}
.catalog-row .catalog-desc {
  color: var(--text-dim);
  font-size: var(--fs-base-sm);
  line-height: 1.5;
}
.catalog-row .catalog-status {
  display: flex;
  align-items: center;
  align-self: center;
}
.catalog-row .catalog-action {
  justify-self: end;
  align-self: center;
}
@media (max-width: 640px) {
  .catalog-list { grid-template-columns: 1fr; }
  .catalog-row {
    grid-column: auto;
    grid-template-columns: 1fr auto;
    row-gap: var(--space-2);
    padding: var(--space-4) 0;
  }
  .catalog-row .catalog-desc { grid-column: 1 / -1; }
  .catalog-row .catalog-action {
    grid-column: 1 / -1;
    justify-self: stretch;
  }
  .catalog-row .catalog-action .btn { width: 100%; text-align: center; }
}

/* 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 topbar fits brand + nav +
   email + sign-out 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 / Workflows / 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; }
  .topbar .who {
    font-size: var(--fs-base-sm);
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .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 seven footer links run ~455px as one 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; }
}

/* 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 the same hidden-checkbox disclosure the
     sidebar drawer uses (no JS; the keyboard shim in
     _COPY_BUTTON_JS handles Enter/Space on the label). The nav stays
     a single DOM instance across breakpoints — no duplicated links
     (the chrome-link pins count hrefs). ``display: contents`` lifts
     the nav 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 .hamburger {
    display: inline-flex;
    /* >=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 { display: none; }
  .topbar.collapsible .nav-toggle:checked ~ .topbar-right 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;
  }
}

/* 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;
}
legend {
  padding: 0 var(--space-2);
  font-weight: var(--fw-bold);
  font-size: var(--fs-base-sm);
  color: var(--text);
}
.check-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  color: var(--text);
}
.check-row + .check-row { margin-top: var(--space-2); }
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);
}

/* Redundant non-color cues on the filled status pills (#1395 WS7,
   WCAG 1.4.1). The outline/off pills already differ by border
   treatment; the filled family differed by hue alone. The glyph uses
   the alternative-text content form so supporting screen readers
   skip it (the pill's text label is the accessible signal). */
.pill.ok::before { content: "✓" / ""; margin-right: var(--space-1); }
.pill.warn::before { content: "!" / ""; margin-right: var(--space-1); }
.pill.danger::before { content: "✕" / ""; margin-right: var(--space-1); }
.pill.pnl-up::before { content: "▲" / ""; margin-right: var(--space-1); }
.pill.pnl-down::before { content: "▼" / ""; margin-right: var(--space-1); }
/* Opt-out for pills that carry their own non-color cue in text (the
   early-win quote pill's arrow): 'bare' suppresses the glyph so the
   cue never doubles. Declared after the tone rules so it wins. */
.pill.bare::before { content: none; }

/* 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="page"],
  .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) {
  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !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-user { background: var(--surface-2); align-self: flex-end; max-width: 85%; }
.chat-msg-assistant { background: transparent; align-self: flex-start; max-width: 100%; }
.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%;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-1) var(--space-2);
  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); }
