/* ===========================================================================
   Informed Trader — the signed-in app's stylesheet (cloud #89).

   Everything here was the second half of site.css until the static pages
   stopped blocking their first paint on it. Only app/layout.tsx links this
   file, AFTER site.css, which must come first: site.css's `@layer` statement
   makes `app` the last and strongest layer (cloud #90), and read first this
   file would make it the weakest. It defines no tokens and no colours of its
   own: the palette lives in site.css, as cloud #23b decided, and this file
   only composes it.

   A rule that one of the five static pages (the marketing and legal pages)
   needs does NOT belong here, because those pages never load this file.
   `test_marketing_nav` fails if a class a static page uses is styled only in
   this file. The sign-in pages are Next routes, so they load both.
   =========================================================================== */

/* Every rule below is in the `app` layer, the last one, declared in
   site.css (cloud #90). */
@layer app {

/* The heading of an app card. This is site.css's `.card h3`, restated as the
   FIRST rule of this layer (cloud #90): before the layers it outranked any
   single-class rule an app card put on an h3, and left in its own layer it
   would now lose to them. Same specificity, ahead of every other app rule, so
   it wins and loses where it always did; `.card h3.val-group-title` below
   still overrides it.
   EXCEPT the two title classes (cloud #92). `.panel-title` and
   `.tax-year-title` are 0.8125rem, and this rule had always set the nine of
   them written as an h3 inside a card at 1.1rem: a card's "Top gainers"
   larger than the "Watch today" above it. The owner chose the titles' own
   size, so they are left out here. `:where()` keeps the specificity at
   (0,1,1), so every other h3 this rule reaches renders as before. */
.card h3:where(:not(.panel-title, .tax-year-title)) {
  font-size: 1.1rem; font-weight: 800; letter-spacing: -0.015em; margin-bottom: 8px;
}

/* ===========================================================================
   APP SHELL + ADMIN (cloud #27)
   =========================================================================== */

.app-header {
  position: sticky; top: 0; z-index: 40;
  background: var(--header-bg);
  backdrop-filter: saturate(160%) blur(14px);
  -webkit-backdrop-filter: saturate(160%) blur(14px);
  border-bottom: 1px solid var(--border);
}
/* min-height, not height, and both rows may wrap. The nav gained a link this
   round and gains one per admin; at 320px a fixed-height row with no wrap
   would have overflowed, and an over-wide header takes the layout viewport
   with it — the failure cloud #28f measured at 218px from a single hidden
   label. Growing taller is the failure mode to prefer: it is visible, local,
   and cannot scroll the page sideways. */
.app-header-inner {
  display: flex; align-items: center; gap: 12px;
  min-height: 62px; flex-wrap: wrap; padding-block: 6px;
}
.app-nav { display: flex; align-items: center; gap: 4px; margin-left: auto; flex-wrap: wrap; }
.app-nav a, .app-nav button {
  font-family: var(--font-mono); text-transform: uppercase; letter-spacing: .1em;
  font-size: 0.65625rem; font-weight: 500; color: var(--muted);
  padding: 8px 11px; border-radius: 9px; white-space: nowrap;
  transition: color .18s ease, background-color .18s ease;
}
.app-nav a:hover, .app-nav button:hover { color: var(--text); background: var(--surface); }
.app-nav a[aria-current="page"] { color: var(--accent-ink-soft); background: var(--accent-soft); }
.app-nav .is-admin { color: var(--accent); }

.page { padding-block: clamp(24px, 4vw, 40px); }
.page-title { font-size: clamp(1.5rem, 4vw, 2.1rem); font-weight: 900; letter-spacing: -0.03em; }
.page-sub { color: var(--muted); font-size: 0.9375rem; margin-top: 8px; }

/* One tab design for every tab strip: /admin's pages, the views of the
   board, a ticker, the Advisor and the news, and the valuation models. It was
   `.admin-tabs` while only /admin had tabs; a class named after one of its
   callers is a name that lies the moment there is a second. Both <a> and
   <button> are styled: /admin's routes are links, and since cloud #90 so is
   every `?view=` tab, each to its view's own URL (components/ViewTabs.tsx).
   The valuation models are buttons, switching the form inside one view. */
.tabs {
  display: flex; gap: 4px; flex-wrap: wrap;
  margin: 22px 0 26px; padding-bottom: 2px;
  border-bottom: 1px solid var(--border);
}
.tabs a, .tabs button {
  font-family: var(--font-mono); text-transform: uppercase; letter-spacing: .1em;
  font-size: 0.65625rem; font-weight: 500; color: var(--muted);
  padding: 10px 13px; border-radius: 9px 9px 0 0;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  background: none; cursor: pointer;
  /* A button's own default; a link would otherwise start-align a label that
     ever wraps. */
  text-align: center;
}
.tabs a:hover, .tabs button:hover { color: var(--text); }
.tabs a[aria-current="page"], .tabs button[aria-current="page"] {
  color: var(--accent); border-bottom-color: var(--accent);
}

/* --- metric tiles --------------------------------------------------------- */

.metrics { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.metric {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 14px; padding: 16px 18px; min-width: 0;
}
.metric-label {
  font-family: var(--font-mono); text-transform: uppercase; letter-spacing: .11em;
  font-size: 0.59375rem; color: var(--muted);
}
.metric-value {
  font-size: clamp(1.5rem, 4vw, 2rem); font-weight: 900;
  letter-spacing: -0.03em;
  /* The whole margin, not just the top: these are <dd>s, and the browser's
     own 40px inline-start margin was indenting every tile's figure. */
  margin: 6px 0 0;
}
.metric-note { font-size: 0.75rem; color: var(--muted); margin-top: 4px; }

/* --- tables ---------------------------------------------------------------
   The scroll container is the DIV, and the table inside it carries the
   min-width. A min-width on a grid/flex child escapes its own box and drags
   the page sideways — that is what detached core's bottom nav (CLAUDE.md's
   scorecard gotcha). `min-width: 0` on the wrapper is what lets it shrink at
   all. */

.table-wrap {
  border: 1px solid var(--border); border-radius: 14px;
  overflow-x: auto; min-width: 0; background: var(--surface);
  /* `position: relative` is load-bearing, not decoration. Overflow only clips
     descendants whose CONTAINING BLOCK is inside the scroller — so without a
     positioning context here, an absolutely-positioned child resolves against
     the initial containing block and escapes both this box and any
     `overflow: hidden` card around it. Measured on /dashboard at 320px: the
     visually-hidden "Actions" column label sat at document x=523 with
     `offsetParent: BODY`, giving the page 218px of horizontal scroll while
     every visible element was properly contained and the wrapper itself
     measured 263px wide. That is the same FAMILY as the min-width escape
     CLAUDE.md records detaching core's bottom nav, reached by a different
     route, and it is just as invisible: the offending element is 1px and
     cannot be seen. One line here fixes it for every table on the site. */
  position: relative;
}
table.admin, table.wl {
  width: 100%; min-width: 640px; border-collapse: collapse; font-size: 0.875rem;
}
table.admin th, table.wl th {
  text-align: left; white-space: nowrap;
  font-family: var(--font-mono); text-transform: uppercase; letter-spacing: .1em;
  font-size: 0.59375rem; font-weight: 500; color: var(--muted);
  padding: 12px 14px; border-bottom: 1px solid var(--border);
}
table.admin td, table.wl td { padding: 12px 14px; border-bottom: 1px solid var(--border); vertical-align: middle; }
table.admin tr:last-child td, table.wl tr:last-child td { border-bottom: 0; }
/* A row HEADER cell too — the board's ticker cell is a <th scope="row">, and
   without this the last row kept a short border under the ticker alone.
   Scoped to tbody so the column headers keep theirs. */
table.wl tbody tr:last-child > th[scope="row"] { border-bottom: 0; }
table.admin tbody tr:hover, table.wl tbody tr:hover { background: var(--accent-soft); }
table.admin .num, table.wl .num { font-family: var(--font-mono); font-size: 0.78125rem; white-space: nowrap; }
table.admin .muted, table.wl .muted { color: var(--muted); }
.cell-actions { display: flex; gap: 6px; flex-wrap: wrap; }

/* --- badges --------------------------------------------------------------- */

.badge {
  display: inline-block;
  font-family: var(--font-mono); text-transform: uppercase; letter-spacing: .1em;
  font-size: 0.5625rem; font-weight: 500;
  padding: 4px 8px; border-radius: 6px; white-space: nowrap;
  border: 1px solid var(--border); color: var(--muted); background: var(--bg);
}
.badge-admin   { color: var(--accent-ink-soft); border-color: var(--accent-line); background: var(--accent-soft); }
.badge-beta    { color: var(--accent-ink-soft); border-color: var(--accent-line); background: var(--accent-soft); }
.badge-pro     { color: var(--pos-ink); border-color: color-mix(in srgb, var(--pos) 34%, transparent); background: var(--pos-bg); }
.badge-ok      { color: var(--pos-ink); border-color: color-mix(in srgb, var(--pos) 34%, transparent); background: var(--pos-bg); }
.badge-warn    { color: var(--neg-ink); border-color: color-mix(in srgb, var(--neg) 34%, transparent); background: var(--neg-bg); }

/* --- forms / controls ----------------------------------------------------- */

/* align-items: START, not end. With `end`, a field carrying a hint is taller
   than its neighbours, so bottom-aligning them pushes its INPUT up and the
   labels stop lining up across the row — the form reads as ragged. Aligning to
   the top anchors the labels, which is what the eye scans. */
.admin-form {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  align-items: start;
}
/* The submit is the exception: it belongs level with the inputs, not with the
   labels above them. */
.admin-form > .btn { align-self: start; margin-top: 21px; }
.admin-form .field { margin-bottom: 0; }
select {
  font: inherit; font-size: 0.875rem; color: var(--text);
  background: var(--surface); border: 1px solid var(--input-border);
  border-radius: 11px; padding: 12px 13px; width: 100%; min-width: 0;
}
select:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
select:disabled { color: var(--muted); cursor: not-allowed; }

.pager { display: flex; align-items: center; gap: 12px; margin-top: 16px; flex-wrap: wrap; }
.pager span {
  font-family: var(--font-mono); text-transform: uppercase; letter-spacing: .1em;
  font-size: 0.625rem; color: var(--muted);
}

.reveal-code {
  font-family: var(--font-mono); font-size: 0.8125rem; word-break: break-all;
  background: var(--surface); border: 1px solid var(--accent-line);
  border-radius: 11px; padding: 14px 16px; margin-top: 6px;
}
.admin-empty { color: var(--muted); padding: 26px 14px; text-align: center; font-size: 0.875rem; }

/* --- skeletons (components/Skeleton.tsx) ----------------------------------
   Muted bars where content will land. The fill is --muted thinned rather than
   a new colour, so it follows the theme with nothing to add to [data-theme].
   The pulse is opacity only — no layout, no paint of anything around it — and
   the reduced-motion rule near the top of this file stops it outright. */
.skel { display: grid; gap: 14px; padding: 10px 2px; }
.skel-item { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.skel-bar {
  display: block; height: 12px; max-width: 100%; border-radius: 6px;
  background: color-mix(in srgb, var(--muted) 22%, transparent);
  animation: skel-pulse 1.6s ease-in-out infinite;
}
.skel-thin { height: 9px; }
.skel-inline { display: inline-block; width: 7.5rem; height: 14px; vertical-align: middle; }
.skel-rows .skel-item {
  flex-direction: row; align-items: center; gap: 14px;
  padding-bottom: 14px; border-bottom: 1px solid var(--border);
}
.skel-rows .skel-item:last-child { border-bottom: 0; padding-bottom: 0; }
.skel-rows .skel-end { margin-left: auto; }
.skel-block { padding: 0; }
.skel-fill { width: 100%; height: 100%; border-radius: 10px; }
.skel-cards { gap: 16px; }
.skel-cards .skel-item {
  border: 1px solid var(--border); border-radius: 16px; padding: 18px;
}
@keyframes skel-pulse { 50% { opacity: .45; } }

/* --- Getting started (components/GettingStarted.tsx) ----------------------
   Every rule is scoped under `.onboard`: the marketing `.card p` rule is
   (0,1,1) and would otherwise out-rank a bare `.onboard-title` and paint the
   step titles muted. The done mark is the pos pair already measured against
   its own tint; titles stay full ink when done, muted only — a strikethrough
   would make the finished steps harder to read, not easier. */
.onboard { margin-bottom: 18px; border-left: 3px solid var(--accent); }
.onboard .onboard-steps { list-style: none; margin: 2px 0 0; padding: 0; }
.onboard .onboard-step {
  display: grid; grid-template-columns: 28px minmax(0, 1fr) auto;
  gap: 8px 14px; align-items: center;
  padding: 13px 0; border-top: 1px solid var(--border);
}
.onboard .onboard-step:first-child { border-top: 0; }
.onboard .onboard-mark {
  width: 28px; height: 28px; border-radius: 50%; display: grid; place-items: center;
  font-family: var(--font-mono); font-size: 0.75rem; font-weight: 700;
  border: 1.5px solid var(--accent-line); color: var(--accent-ink-soft);
}
.onboard .is-done .onboard-mark { background: var(--pos-bg); border-color: transparent; color: var(--pos-ink); }
.onboard .onboard-title { color: var(--text); font-size: 0.9375rem; font-weight: 700; }
.onboard .is-done .onboard-title { color: var(--muted); font-weight: 600; }
.onboard .onboard-body { color: var(--muted); font-size: 0.84375rem; margin-top: 2px; }
/* Steps not yet reachable are quieter than the one to take now. */
.onboard .onboard-step:not(.is-current):not(.is-done) .onboard-title { color: var(--muted); font-weight: 600; }
.onboard .onboard-step { padding: 11px 0; }
.onboard .is-current { padding: 14px 0; }
.onboard .onboard-foot {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 8px 16px; margin-top: 4px; padding-top: 12px; border-top: 1px solid var(--border);
}
.onboard .onboard-extra { color: var(--muted); font-size: 0.8125rem; }
.onboard .onboard-extra a { color: var(--accent-ink-soft); }
.onboard .onboard-dismiss { margin-left: auto; }
/* A phone puts each step's button under its text, not squeezed beside it. */
@media (max-width: 560px) {
  .onboard .onboard-step { grid-template-columns: 28px minmax(0, 1fr); }
  .onboard .onboard-cta { grid-column: 2; }
}

/* --- capped lists (components/CappedList.tsx) -----------------------------
   A phone shows the top three of each ranked list; the rest is one tap away.
   The 3 is also CAP in the component. The toggle is a full-width, 40px-tall
   row rather than a text link: it is the one control under a thumb at the
   foot of every card. */
.capped-toggle {
  display: none; width: 100%; min-height: 40px; margin-top: 6px;
  align-items: center; justify-content: center;
  border-top: 1px dashed var(--border);
  font-size: 0.8125rem; font-weight: 600; color: var(--accent-ink-soft);
}
.capped-toggle:hover { color: var(--text); }
.capped-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: 8px; }
/* 639.98, not 639: at a fractional zoom the viewport can be 639.2px wide,
   which `max-width: 639px` misses (measured at 1.25x). */
@media (max-width: 639.98px) {
  .capped:not(.is-open) > :nth-child(n+4) { display: none; }
  .capped-toggle { display: flex; }
}

/* A value quoted VERBATIM, as opposed to `.mono`, which is a label style and
   uppercases. The difference matters wherever the exact characters are the
   point: an OAuth scope shown as "QUOTE:READ" is not the string the provider
   granted, and that string is the evidence for the read-only claim beside it.
   `break-word` rather than `break-all` so a long scope list wraps between its
   segments instead of mid-token. */
/* --- account menu ----------------------------------------------------------
   Replaces the bare "Sign out" button. Positioned relative so the panel hangs
   off it; `position: relative` also gives absolutely-positioned descendants a
   containing block INSIDE the header, which is what stops one escaping to the
   initial containing block and dragging the page sideways (cloud #28f). */
/* NOT `position: relative` on .acct itself, and the browser is why.
   Anchoring the panel to the TRIGGER puts its right edge at the trigger's,
   which is fine on a wide header and wrong at 320px: the nav wraps, the
   trigger lands at x=14, and a 244px panel right-anchored to x=158 starts at
   -86 — the avatar and the email address literally off the left edge, and no
   page overflow to give it away because it overflows LEFTWARD.

   Anchored to `.app-header-inner` instead, so `right: 0` means the shell's
   right edge, which is on screen at every width and is where an account menu
   belongs anyway. That `position: relative` also gives every absolutely
   positioned descendant of the header a containing block INSIDE it, which is
   what stops one escaping to the initial containing block (cloud #28f). */
.app-header-inner { position: relative; }
.acct { position: static; }
.acct-trigger {
  display: inline-flex; align-items: center; gap: 8px;
  background: none; border: 1px solid var(--border); border-radius: 999px;
  padding: 4px 10px 4px 4px; color: var(--text); cursor: pointer;
  font: inherit; font-size: 0.8125rem;
}
.acct-trigger:hover { border-color: var(--accent-line); background: var(--accent-soft); }
/* The circle is sized in em, so it grows with its letter when a reader's text
   size is larger (cloud #92): at a fixed 24px the letter outgrew it. 2em of
   0.75rem is 24px, and 34/15em of 0.9375rem is 34px, at the default size. */
.acct-avatar {
  display: inline-grid; place-items: center;
  width: 2em; height: 2em; border-radius: 999px;
  background: var(--accent); color: var(--accent-ink);
  font-size: 0.75rem; font-weight: 700;
}
.acct-avatar.is-lg { width: calc(34em / 15); height: calc(34em / 15); font-size: 0.9375rem; }
.acct-name { max-width: 12ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.acct-menu {
  position: absolute; right: 0; top: calc(100% + 8px); z-index: 40;
  min-width: 244px;
  /* Belt and braces for a viewport narrower than the panel's own minimum. */
  max-width: calc(100vw - 24px);
  padding: 6px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
  box-shadow: 0 24px 60px -20px rgba(0, 0, 0, .55);
  display: grid; gap: 2px;
}
.acct-head {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 10px 12px; border-bottom: 1px solid var(--border); margin-bottom: 6px;
}
.acct-id { display: grid; min-width: 0; flex: 1; }
.acct-id strong { font-size: 0.84375rem; }
.acct-email {
  color: var(--muted); font-size: 0.71875rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.acct-item {
  display: block; width: 100%; text-align: left;
  padding: 9px 10px; border: 0; border-radius: 9px;
  background: none; color: var(--text); font: inherit; font-size: 0.84375rem;
  cursor: pointer; text-decoration: none;
}
.acct-item:hover { background: var(--accent-soft); color: var(--accent-ink-soft); }
.acct-sep { height: 1px; background: var(--border); margin: 6px 0; }
.acct-item.is-signout { color: var(--neg-ink); }
.acct-item.is-signout:hover { background: var(--neg-bg); color: var(--neg-ink); }

/* --- settings page ----------------------------------------------------------
   NOT `.section-title` / `.section-lede`: those already exist for the MARKETING
   pages, at clamp(1.6rem, 4.6vw, 2.4rem) — a display size that is wrong between
   two settings cards. Two rules for one class name is the "two entries
   disagreeing about the same constant" problem CLAUDE.md records, and the one
   that wins is whichever happens to come later in the file. Own names instead. */
.settings-title {
  font-size: 0.9375rem; font-weight: 700; margin: 34px 0 8px;
}
.settings-lede {
  color: var(--muted); font-size: 0.84375rem; line-height: 1.6;
  margin: 0 0 6px; max-width: 74ch;
}
.settings-lede strong { color: var(--text); }

.btn-row { display: flex; gap: 10px; flex-wrap: wrap; }

/* Group picker. Rows wrap rather than shrink — a checkbox label squeezed to
   two characters is worse than one on its own line. */
.group-list {
  list-style: none; margin: 0 0 14px; padding: 0; display: grid; gap: 4px;
}
.group-row {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 8px 10px; border: 1px solid var(--border); border-radius: 10px;
  cursor: pointer; font-size: 0.84375rem;
}
.group-row:hover { border-color: var(--accent-line); background: var(--accent-soft); }
.group-name { flex: 1 1 8rem; min-width: 0; overflow-wrap: break-word; }
.group-type {
  font-family: var(--font-mono); font-size: 0.65625rem; letter-spacing: .08em;
  text-transform: uppercase; color: var(--muted);
}

/* Coming-soon brokers. Muted with TOKENS, never opacity — opacity drags the
   label's contrast down with it (cloud #25g), and these rows are inert by
   definition so they would be the first to fall below 4.5:1. */
.broker-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.broker-row {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 11px 13px; border: 1px solid var(--border); border-radius: 11px;
}
.broker-row.is-soon { background: var(--surface); }
.broker-name { font-weight: 600; font-size: 0.84375rem; }
.broker-note { flex: 1 1 14rem; min-width: 0; color: var(--muted); font-size: 0.78125rem; }

/* A DISABLED button must not look like an enabled one.
   Found in a browser while measuring the consent card: `.btn:disabled` had no
   styling at all — identical colour, identical border, and `cursor: pointer` —
   so a blocked "Turn on" invited a click that could never do anything. It is
   not local to that card either: every button in this portal goes disabled
   while a request is in flight (scan, silence, disconnect, the scheduler
   toggle), and all of them were giving no feedback at all.

   Muted with TOKENS rather than `opacity`, which is the same call `.chip`
   already made and the one cloud #25g arrived at for the inert Google button:
   opacity drags the label's contrast down with it, so a "helpfully" faded
   control ends up unreadable rather than merely inactive. Measured after:
   still above 4.5:1 in both themes. */
.btn:disabled {
  color: var(--muted);
  border-color: var(--border);
  cursor: not-allowed;
}
.btn-solid:disabled {
  /* The solid variant carries its own fill and shadow, so muting the ink alone
     would leave it looking pressable. */
  background: var(--surface);
  box-shadow: none;
}

/* A switch with its explanation, which is the whole point of the layout: the
   copy is what makes a consent a consent rather than a checkbox, so it gets
   the room and the button does not float away from what it permits. Wraps
   rather than shrinking at narrow widths — a button squeezed to two characters
   is worse than one on its own line. */
.consent-row {
  display: flex; gap: 14px; align-items: flex-start; justify-content: space-between;
  flex-wrap: wrap;
  border-top: 1px solid var(--border); padding: 14px 0 0; margin-top: 14px;
}
.consent-row > div { min-width: 0; flex: 1 1 18rem; }
.consent-title { margin: 0 0 3px; font-size: 0.84375rem; font-weight: 600; }
.consent-note {
  margin: 0; color: var(--muted); font-size: 0.78125rem; line-height: 1.55;
  max-width: 62ch;
}
.consent-note strong { color: var(--text); }

/* Holdings: stacked below 640, the table above it. Same split as
   .record-stack / .record-table, and for the same reason — P/L is the question
   a holdings table answers, and at 320px the table puts it off the right edge
   behind a scroller (cloud #29's Track-record defect). */
.pos-stack { list-style: none; display: grid; gap: 12px; margin: 0; padding: 0; }
.pos-stack li {
  display: grid; gap: 3px;
  border-left: 2px solid var(--border); padding-left: 11px;
}
.pos-key {
  font-size: 0.8125rem; font-weight: 700;
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
}
.pos-val { font-family: var(--font-mono); font-size: 0.75rem; color: var(--muted); }
.pos-note { color: var(--muted); font-size: 0.6875rem; line-height: 1.5; }
.pos-table { display: none; }
@media (min-width: 640px) {
  .pos-stack { display: none; }
  .pos-table { display: block; }
}

/* Portfolio Advisor: stacked below 640px, the table above it — same split as
   .pos-stack/.pos-table and .record-stack/.record-table, same reason: the fix
   for a table on a phone is not to contain it better, it is not to hand a
   phone a table it cannot fit (cloud #29's Track-record defect). */
.adv-stack { list-style: none; display: grid; gap: 12px; margin: 0; padding: 0; }
.adv-stack li {
  display: grid; gap: 3px;
  border-left: 2px solid var(--border); padding-left: 11px;
}
.adv-key {
  font-size: 0.8125rem; font-weight: 700;
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
}
.adv-val { font-family: var(--font-mono); font-size: 0.75rem; color: var(--muted); }
.adv-note { color: var(--muted); font-size: 0.6875rem; line-height: 1.5; }
.adv-table { display: none; }
@media (min-width: 640px) {
  .adv-stack { display: none; }
  .adv-table { display: block; }
}

/* Owned-ticker risk: a stack of full SetupCards (each already carries its
   own background/border/radius), so the list itself needs only spacing, not
   a second border around each row. */
.owned-risk-list { list-style: none; display: grid; gap: 14px; margin: 0; padding: 0; }
.owned-risk-list > li {
  display: grid; gap: 10px;
  padding-top: 14px; border-top: 1px solid var(--border);
}
.owned-risk-list > li:first-child { padding-top: 0; border-top: none; }
.owned-risk-code { font-size: 0.8125rem; font-weight: 700; }

/* Dividend calculator: each row is three small manual inputs plus the
   computed readout, wrapping naturally at narrow widths rather than a rigid
   grid — the fields are short enough that flex-wrap alone keeps them legible
   down to 320px. */
.dividend-row {
  display: flex; flex-wrap: wrap; align-items: end; gap: 10px 14px;
  padding: 12px; border: 1px solid var(--border); border-radius: 10px;
}
.dividend-ticker { flex: 1 1 100%; font-size: 0.8125rem; font-weight: 700; }
.dividend-row .field { flex: 1 1 110px; min-width: 90px; margin-bottom: 0; }
.dividend-computed {
  display: flex; flex-wrap: wrap; gap: 4px 16px; align-items: baseline;
  flex: 1 1 100%; font-family: var(--font-mono); font-size: 0.8125rem;
  padding-top: 10px; margin-top: 2px; border-top: 1px solid var(--border);
}
.dividend-computed .metric-label {
  text-transform: uppercase; letter-spacing: .09em; font-size: 0.59375rem;
  color: var(--muted);
}

/* Tax records (cloud #76). The form reuses .admin-form's auto-fit grid; the
   lists reuse .adv-stack/.adv-table, stacked below 640px for the reason every
   table here is. What is new is only the sale-and-slices shape, which is a
   list of lists rather than a table: a sale with three parcels has three
   slices, and a fixed-column table would either repeat the sale on every row
   or hide the slices behind a click. */
.tax-kinds { display: flex; flex-wrap: wrap; gap: 8px; margin: 2px 0 16px; }
.tax-form .field { margin-bottom: 0; }
.tax-form-wide { grid-column: 1 / -1; margin: 0; }
.tax-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.tax-actions > span { font-size: 0.78125rem; color: var(--muted); }
.tax-muted { color: var(--muted); font-weight: 500; font-size: 0.6875rem; }
.tax-problems { list-style: none; display: grid; gap: 8px; margin: 0; padding: 0; }
.tax-problems .flash { margin: 0; line-height: 1.55; }
.tax-notes { margin-top: 12px; }
.tax-notes summary { cursor: pointer; font-size: 0.78125rem; color: var(--muted); }
.tax-notes ul {
  list-style: none; display: grid; gap: 8px; margin: 10px 0 0; padding: 0;
  font-size: 0.78125rem; color: var(--muted); line-height: 1.55;
}
.tax-year { display: grid; gap: 14px; padding-top: 18px; margin-top: 18px; border-top: 1px solid var(--border); }
.tax-year-title { font-size: 0.8125rem; font-weight: 800; margin: 0; }
.tax-sales { list-style: none; display: grid; gap: 16px; margin: 0; padding: 0; }
.tax-sale { display: grid; gap: 6px; border-left: 2px solid var(--border); padding-left: 11px; min-width: 0; }
.tax-sale-head {
  display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between;
  gap: 4px 12px; font-size: 0.8125rem; font-weight: 700;
}
.tax-sale-meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px;
  font-size: 0.75rem; color: var(--muted);
}
.tax-slices {
  list-style: none; display: grid; gap: 4px; margin: 0; padding: 0;
  font-family: var(--font-mono); font-size: 0.75rem; color: var(--muted); line-height: 1.5;
  overflow-wrap: anywhere;
}
.tax-income-section { display: grid; gap: 6px; }
.tax-section-title {
  font-family: var(--font-mono); text-transform: uppercase; letter-spacing: .1em;
  font-size: 0.65625rem; font-weight: 500; color: var(--muted); margin: 0;
}
.tax-total { margin: 0; font-size: 0.8125rem; line-height: 1.55; }
.tax-notes-list { margin: 0; padding-left: 18px; display: grid; gap: 6px; font-size: 0.8125rem; line-height: 1.55; }
.tax-form-note { margin: -6px 0 14px; max-width: 62ch; }
.tax-income-list { margin-top: 18px; }
.tax-import-preview { display: grid; gap: 12px; margin-top: 14px; }
.tax-import-preview .flash { margin: 0; }
.tax-import-flash { margin: 12px 0 0; }
/* Monthly statements imported (cloud #80): one cell per month of the income
   year. auto-fill gives two across at 320px and six on a wide card. */
.tax-months-wrap { display: grid; gap: 12px; margin-top: 18px; }
.tax-months {
  list-style: none; margin: 8px 0 0; padding: 0; display: grid; gap: 6px;
  grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
}
.tax-month {
  display: grid; gap: 2px; padding: 7px 9px; border: 1px solid var(--border);
  border-radius: 8px; font-size: 0.75rem; line-height: 1.35; min-width: 0;
}
.tax-month-name { font-weight: 700; }
.tax-month-state { font-size: 0.6875rem; color: var(--muted); overflow-wrap: anywhere; }
.tax-month-ok { border-color: color-mix(in srgb, var(--pos) 34%, transparent); background: var(--pos-bg); }
.tax-month-ok .tax-month-state { color: var(--pos-ink); }
.tax-month-differs { border-color: color-mix(in srgb, var(--neg) 34%, transparent); background: var(--neg-bg); }
.tax-month-differs .tax-month-state { color: var(--neg-ink); }
.tax-months-note { margin: 10px 0; }
.tax-months-diff { display: grid; gap: 6px; margin-top: 10px; }
.tax-chooser { display: grid; gap: 10px; padding: 12px; border: 1px solid var(--border); border-radius: 10px; }
.tax-chooser-row {
  display: grid; gap: 6px 12px; align-items: center;
  grid-template-columns: minmax(0, 1fr) minmax(90px, 140px);
  font-size: 0.78125rem;
}
.tax-chooser-row input {
  font: inherit; font-size: 0.875rem; color: var(--text); background: var(--surface);
  border: 1px solid var(--input-border); border-radius: 9px; padding: 9px 11px;
  width: 100%; min-width: 0;
}
.tax-chooser-row input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }

/* Volatility cone / seasonality bars: hand-rolled SVG, same family as the
   candlestick chart's `.chart-pane` sizing rule — the SVG must never be
   wider than its column, or a 320px phone drags sideways the way a
   too-wide canvas would (CLAUDE.md's documented gotcha). `width: 100%` +
   `height: auto` on a viewBox'd svg scales responsively with no JS. */
.vol-cone, .seasonality-bars { display: grid; gap: 6px; min-width: 0; }
.vol-cone-svg, .seasonality-svg, .risk-map-svg, .income-svg {
  display: block; width: 100%; height: auto; min-width: 0;
}

/* --- allocation donut (target vs. actual) ---------------------------------- */

/* The SVG is capped, NOT the container: capping the container squeezed the
   caption into a 220px ribbon beside 900px of empty card. Donut and prose sit
   side by side when there is room and stack when there isn't. */
.alloc-donut {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 14px 22px; min-width: 0; margin: 0 0 20px;
}
.alloc-donut-svg {
  display: block; width: 100%; max-width: 200px; height: auto;
  min-width: 0; flex: none;
}
.alloc-donut .sched-note { flex: 1 1 18rem; min-width: 0; margin: 0; }

/* --- guided setup ------------------------------------------------------ */

.setup-steps {
  display: flex; flex-wrap: wrap; gap: 8px; list-style: none;
  margin: 0 0 24px; padding: 0; font-size: 0.75rem; color: var(--muted);
}
.setup-steps li {
  display: flex; align-items: center; gap: 6px;
  padding: 5px 10px 5px 5px; border: 1px solid var(--border); border-radius: 999px;
}
.setup-steps li[aria-current="step"] {
  color: var(--text); border-color: var(--accent-line); background: var(--accent-soft);
}
.setup-steps .step-num {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; border-radius: 50%; flex: none;
  font-family: var(--font-mono); font-size: 0.625rem;
  background: var(--border); color: var(--text);
}
.setup-steps li[aria-current="step"] .step-num { background: var(--accent); color: var(--bg); }

.setup-choices {
  display: grid; gap: 12px; margin: 4px 0 18px; min-width: 0;
}
@media (min-width: 640px) {
  .setup-choices { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.setup-choice {
  display: block; width: 100%; text-align: left; min-width: 0;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 14px; padding: 16px; cursor: pointer;
  font: inherit; color: inherit;
  transition: border-color .15s ease, background-color .15s ease;
}
.setup-choice:hover:not(:disabled) { border-color: var(--accent-line); }
.setup-choice:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.setup-choice:disabled { cursor: not-allowed; opacity: 0.6; }
.setup-choice h3 { font-size: 0.9375rem; font-weight: 700; margin: 0 0 4px; color: var(--text); }
.setup-choice p { font-size: 0.78125rem; color: var(--muted); margin: 0 0 12px; }
/* The same two lines as spans (cloud #70). A button may hold only phrasing
   content, so an h3 inside one is invalid HTML and announces a heading
   mid-control; spans keep the look and leave the text as the button's name. */
.setup-choice .choice-title { display: block; font-size: 0.9375rem; font-weight: 700; margin: 0 0 4px; color: var(--text); }
.setup-choice .choice-blurb { display: block; font-size: 0.78125rem; color: var(--muted); }
.setup-choice .choice-blurb + .setup-preview { margin-top: 12px; }
/* A multi-select choice (the /start questionnaire, cloud #70) shows that it is
   chosen the way a chip does, with the same accent pair. */
.setup-choice[aria-pressed="true"] { border-color: var(--accent-line); background: var(--accent-soft); }
.setup-preview { display: flex; gap: 18px; min-width: 0; }
.setup-preview div { display: grid; gap: 2px; min-width: 0; }
.setup-preview b {
  font-family: var(--font-mono); font-size: 1rem; font-weight: 700; color: var(--text);
}
.setup-preview span {
  font-size: 0.59375rem; text-transform: uppercase; letter-spacing: .08em; color: var(--muted);
}

/* P/L ink. --pos/--neg are FILL colours; used as text they measure 4.31:1 and
   3.88:1 on their own grounds, which is why the -ink pair exists (cloud #23d).
   This is the third caller of that split, and the first on a plain table cell
   rather than a tinted one. */
.pnl-pos { color: var(--pos-ink); }
.pnl-neg { color: var(--neg-ink); }

/* --- alerts ---------------------------------------------------------------
   The only surface here that demands attention, so it is the only one styled
   to. Held-positions-only by design (decisions #53), and it renders nothing at
   all when there is nothing — see Alerts.tsx. */
.alerts-card { margin-top: 36px; min-width: 0; }
.alerts-card.is-blind {
  border-color: color-mix(in srgb, var(--neg) 34%, transparent);
  background: var(--neg-bg);
}
.alerts-blind {
  color: var(--neg-ink); font-size: 0.84375rem; line-height: 1.6;
  margin: 0; max-width: 70ch;
}
.alerts-blind strong { color: var(--neg-ink); }
.alerts-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.alert-row {
  display: flex; gap: 12px; align-items: flex-start; justify-content: space-between;
  flex-wrap: wrap;
  border: 1px solid var(--border); border-radius: 12px; padding: 11px 13px;
}
/* Severity tints the ROW rather than only a dot, because a list where the
   critical item looks like the others is a list nobody triages. */
.alert-row.is-critical {
  border-color: color-mix(in srgb, var(--neg) 34%, transparent);
  background: var(--neg-bg);
}
.alert-main { min-width: 0; flex: 1 1 16rem; }
.alert-title {
  margin: 0 0 3px; font-size: 0.875rem; font-weight: 600;
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
.alert-detail {
  margin: 0; color: var(--muted); font-size: 0.8125rem; line-height: 1.55;
  overflow-wrap: break-word;
}
.alert-row.is-critical .alert-detail { color: var(--neg-ink); }
.alerts-foot {
  color: var(--muted); font-size: 0.78125rem; line-height: 1.55;
  margin: 14px 0 0; max-width: 70ch;
}

/* --- tidy your watchlist (cloud #56) --------------------------------------
   A question, not an alert, so it borrows the alert ROW's shape and none of
   its colour. Every paragraph carries its own class: `:where(.card) p` is
   element specificity (cloud #54e), so one class is enough to beat it. */
.tidy-card { margin-top: 36px; min-width: 0; }
.tidy-card-body { margin: 0; color: var(--text); font-size: 0.875rem; line-height: 1.55; }
.tidy-card-why { margin: 4px 0 0; color: var(--muted); font-size: 0.8125rem; line-height: 1.5; }
.tidy-card-actions { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-top: 12px; }
.tidy { min-width: 0; }
.tidy-lede { margin: 0 0 14px; color: var(--muted); font-size: 0.84375rem; line-height: 1.6; max-width: 70ch; }
.tidy-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.tidy-row {
  display: flex; gap: 12px 16px; align-items: flex-start; justify-content: space-between;
  flex-wrap: wrap; min-width: 0;
  border: 1px solid var(--border); border-radius: 12px; padding: 12px 14px;
}
.tidy-main { min-width: 0; flex: 1 1 20rem; }
.tidy-title {
  margin: 0 0 4px; color: var(--text); font-size: 0.9375rem; font-weight: 700;
  display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap;
}
.tidy-title a { color: var(--text); }
.tidy-name { color: var(--muted); font-size: 0.8125rem; font-weight: 500; overflow-wrap: anywhere; }
.tidy-reasons {
  margin: 0 0 8px; padding-left: 18px; color: var(--text);
  font-size: 0.84375rem; line-height: 1.55; overflow-wrap: break-word;
}
.tidy-reasons li + li { margin-top: 3px; }
.tidy-price { display: flex; gap: 8px 14px; align-items: center; flex-wrap: wrap; margin: 6px 0; min-width: 0; }
.tidy-spark { width: 120px; height: 32px; flex: none; }
.tidy-spark polyline { stroke: var(--accent-ink-soft); stroke-width: 1.5; }
.tidy-figures { margin: 6px 0; min-width: 0; color: var(--muted); font-size: 0.78125rem; line-height: 1.55; }
.tidy-figures p { margin: 0; color: var(--muted); font-size: 0.78125rem; }
.tidy-figures .tidy-moves { display: flex; flex-wrap: wrap; gap: 0 12px; }
.tidy-pos { color: var(--pos-ink); }
.tidy-neg { color: var(--neg-ink); }
.tidy-assumes, .tidy-note {
  margin: 6px 0 0; color: var(--muted); font-size: 0.8125rem; line-height: 1.55;
  max-width: 70ch; overflow-wrap: break-word;
}
.tidy-assumes-label { color: var(--text); font-weight: 600; }
.tidy-assumes a { color: var(--accent-ink-soft); }
.tidy-actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.tidy-foot { margin: 14px 0 0; color: var(--muted); font-size: 0.78125rem; line-height: 1.55; max-width: 70ch; }
.tidy-rules { margin-top: 14px; color: var(--muted); font-size: 0.8125rem; line-height: 1.6; }
.tidy-rules summary { cursor: pointer; color: var(--text); font-weight: 600; }
.tidy-rules ul { margin: 8px 0 0; padding-left: 18px; }
.tidy-rules li + li { margin-top: 6px; }
.tidy-rules strong { color: var(--text); }
.tidy-rules select { width: auto; font-size: 0.8125rem; padding: 4px 8px; border-radius: 9px; }
.tidy-check { display: inline-flex; gap: 8px; align-items: center; }

.verbatim {
  font-family: var(--font-mono); font-size: 0.92em;
  letter-spacing: 0; text-transform: none;
  overflow-wrap: break-word;
}

/* ===========================================================================
   THE PRODUCT SURFACE — the board and one ticker.

   Everything below is the authenticated app, and it deliberately adds no
   colours: it composes the tokens at the top of this file, the same ones the
   marketing pages and the admin panel use. A product that reads as a different
   site from its own landing page is what one stylesheet exists to prevent.

   Two structural rules carried from CLAUDE.md, both learned by shipping the
   bug: every grid child gets `min-width: 0` (a min-width inside a grid item
   escapes its own scroll container and drags the whole page sideways), and a
   wide table lives inside `.table-wrap`, never in the grid child directly.
   =========================================================================== */

/* --- board header ---------------------------------------------------------- */

.board-head {
  display: flex; flex-wrap: wrap; align-items: flex-end;
  justify-content: space-between; gap: 20px; margin-bottom: 22px;
}
.board-head > div { min-width: 0; }
.board-stats { margin: 0; grid-template-columns: repeat(3, minmax(96px, auto)); }
.board-stats .metric { padding: 11px 14px; }
.board-stats dd { margin: 0; }
/* Only the VALUE shrinks. The label is already at its floor, and a header
   strip that shouts its own numbers competes with the board it introduces. */
.metrics-compact .metric-value { font-size: 1.05rem; margin-top: 3px; }
/* The tiles' phone form: one mono line under the subtitle. Hidden above 560px,
   where the tiles show; the tiles hide below it (the 560px block further down). */
.board-stats-line {
  display: none; margin-top: 6px;
  font-family: var(--font-mono); font-size: 0.71875rem; letter-spacing: .02em;
  color: var(--muted); font-variant-numeric: tabular-nums;
}

.board-inline { margin-bottom: 14px; }
.board-card { padding: 0; overflow: hidden; }
.board-top { margin-top: 22px; }

.panel-title {
  font-size: 0.8125rem; font-weight: 800; letter-spacing: -0.01em; margin: 0;
}
.card-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-bottom: 12px;
}
.card-meta {
  font-family: var(--font-mono); font-size: 0.65625rem; color: var(--muted);
  text-transform: uppercase; letter-spacing: .09em;
}

/* --- add-a-ticker row ------------------------------------------------------ */

.board-add {
  display: grid; gap: 10px 12px; align-items: end; padding: 18px;
  grid-template-columns: minmax(0, 1fr) auto; border-bottom: 1px solid var(--border);
}
.board-add .field { margin-bottom: 0; min-width: 0; }
/* The hint spans both columns and sits under them. Aligning a form to its
   LABELS rather than its inputs is the fix a previous round had to ship
   (cloud, ed3497d) — `align-items: end` is what keeps the button on the input
   baseline when the label above it wraps. */
.board-add .field-hint { grid-column: 1 / -1; margin: 0; }

/* --- flashes --------------------------------------------------------------- */

.flash {
  border: 1px solid var(--border); border-radius: 12px;
  padding: 11px 14px; margin-bottom: 14px; font-size: 0.875rem;
}
.flash-ok  { color: var(--pos-ink); border-color: color-mix(in srgb, var(--pos) 34%, transparent); background: var(--pos-bg); }
.flash-bad { color: var(--neg-ink); border-color: color-mix(in srgb, var(--neg) 34%, transparent); background: var(--neg-bg); }
.flash-busy { color: var(--muted); background: var(--surface); }

/* Portfolio Advisor beta announcement — the first persistent banner in the
   header. Sits below .app-header-inner as its own row (in its own .shell
   wrapper) so it cannot disturb that row's own min-height/flex-wrap overflow
   fix. Reuses .flash's tone tokens; adds only the row layout and the
   vertical breathing room a plain flash never needed here. */
.advisor-banner-row { padding-block: 10px 14px; }
.advisor-banner {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px 16px; flex-wrap: wrap; margin: 0;
}
.advisor-banner-actions { display: flex; gap: 8px; flex: none; }

/* --- watchlist table ------------------------------------------------------- */

.wl th[scope="row"] { font-weight: 400; }
/* Alignment for the whole column; the figure's TYPE only on data cells. A
   numeric column's header is a label like every other header — it used to
   take 12.5px here and sat a size above its neighbours in six tables. */
.wl .numeric { white-space: nowrap; text-align: right; }
.wl td.numeric { font-family: var(--font-mono); font-size: 0.78125rem; }
/* ...except where the headers are values themselves: the valuation matrix's
   axes are discount and growth RATES, so both read as figures, not labels. */
.sens-matrix thead th.numeric, table.wl.sens-matrix th[scope="row"] {
  font-family: var(--font-mono); font-size: 0.78125rem; letter-spacing: normal;
  text-transform: none; color: var(--muted);
}
.wl-code {
  font-weight: 800; letter-spacing: -0.01em; font-size: 0.9375rem;
  display: block; color: var(--text);
}
.wl-code:hover { color: var(--accent-ink-soft); }
.wl-name {
  display: block; font-size: 0.71875rem; color: var(--muted);
  max-width: 22ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.wl-read { display: inline-flex; align-items: center; gap: 8px; }
.wl-conviction { font-family: var(--font-mono); font-size: 0.78125rem; }
.wl-none { color: var(--muted); font-size: 0.8125rem; }
.wl-age { color: var(--muted); font-size: 0.78125rem; white-space: nowrap; }
/* A row header that IS an age (the ticker page's history) keeps the age's
   look; the board's row-header rule would otherwise make it 14px body text. */
.wl th.wl-age[scope="row"] { color: var(--muted); font-size: 0.78125rem; }
/* The cell stays a table cell — see WatchlistBoard's TableRow — and the row
   of controls is an inline flex box inside it. */
.wl-actions { text-align: right; white-space: nowrap; }
.wl-actions-inner { display: inline-flex; gap: 10px; align-items: center; vertical-align: middle; }

/* --- the grouped board (WatchlistBoard.tsx) -------------------------------
   The board was one flat table: 60 rows measured 4,312px and a phone got a
   782px table in a 332px box. It is now grouped sections under a toolbar, a
   table from 640px up and stacked cards below it (the .pos-stack split). */

/* The row header inherited the column headers' uppercase mono, which is what
   rendered company names as "BANK OF AMERICA CO…". Only the column labels
   are meant to look like labels. */
.wl th[scope="row"] {
  font-family: var(--font-display); text-transform: none; letter-spacing: normal;
  font-size: 0.875rem; color: var(--text);
}
/* Compact actions: the site's .btn-sm is sized for forms, and on a board it
   was most of each row's 67px height. */
.wl-actions .btn-sm, .wl-card-actions .btn-sm, .wl-add { padding: 7px 12px; }
.wl-add { flex: none; margin-left: auto; }
/* Tighter rows than the admin tables they share a class with: a board is
   scanned by eye, and 12px of padding each way made every row 67px. */
.wl-wrap table.wl td, .wl-wrap table.wl th[scope="row"] { padding-block: 8px; }

.wl-controls {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 10px 16px; padding: 12px 18px; border-bottom: 1px solid var(--border);
}
.wl-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.wl-chip-n { font-family: var(--font-mono); font-size: 0.65625rem; color: var(--muted); }
.chip.on .wl-chip-n { color: var(--text); }
.wl-selects { display: flex; flex-wrap: wrap; gap: 10px; }
.wl-select {
  display: inline-flex; align-items: center; gap: 7px;
  font-family: var(--font-mono); text-transform: uppercase; letter-spacing: .1em;
  font-size: 0.625rem; color: var(--muted);
}
.wl-select select {
  width: auto; font-family: var(--font-display); text-transform: none; letter-spacing: 0;
  font-size: 0.8125rem; padding: 6px 10px; border-radius: 9px;
}

/* Sortable column headers: the label IS the button. */
.wl-sort {
  display: inline-flex; align-items: center; gap: 5px;
  font: inherit; letter-spacing: inherit; text-transform: inherit; color: inherit;
  background: none; border: 0; padding: 0; cursor: pointer;
}
.wl-sort:hover, th[aria-sort] .wl-sort { color: var(--text); }
.wl-sort:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }
.wl-sort-mark { font-size: 0.5rem; }

/* Group headers — a button that folds its section. */
.wl-group-head { margin: 0; }
.wl-group-toggle {
  display: flex; align-items: center; gap: 9px; width: 100%;
  padding: 11px 14px; background: none; border: 0; cursor: pointer; text-align: left;
  font-family: var(--font-display); font-size: 0.84375rem; font-weight: 800;
  letter-spacing: -0.01em; text-transform: none; color: var(--text);
}
.wl-group-toggle svg { flex: none; color: var(--muted); transition: transform .15s ease; }
.wl-group-toggle[aria-expanded="true"] svg { transform: rotate(90deg); }
.wl-group-toggle:hover .wl-group-label { color: var(--accent-ink-soft); }
.wl-group-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: -3px; border-radius: 10px; }
.wl-group-count {
  font-family: var(--font-mono); font-size: 0.6875rem; font-weight: 500; color: var(--muted);
  border: 1px solid var(--border); border-radius: 999px; padding: 1px 8px;
}
/* One line between sections, never two. A group header draws the line ABOVE
   it (except the first, which sits under the column headers), and draws the
   line below it only while its rows are showing. A collapsed header is its
   tbody's last row, so a bottom border there doubled up with the next
   section's top one — and with the table's own edge on the last section. */
.wl tr.wl-group-row th { padding: 0; border-bottom: 0; }
.wl tr.wl-group-row.is-open th { border-bottom: 1px solid var(--border); }
.wl tbody.wl-group + tbody.wl-group tr.wl-group-row th { border-top: 1px solid var(--border); }
table.wl tbody tr.wl-group-row:hover, table.wl tbody tr.wl-detail-row:hover { background: none; }

/* A row, its close, and its detail. */
.wl-read { flex-wrap: wrap; }
.wl-read .wl-age { font-size: 0.71875rem; }
.wl-close { display: inline-grid; justify-items: end; gap: 2px; }
.wl-chg { font-size: 0.71875rem; }
.wl-chg-pos { color: var(--pos-ink); }
.wl-chg-neg { color: var(--neg-ink); }
.wl-expand {
  display: inline-grid; place-items: center; width: 32px; height: 32px; flex: none;
  border: 1px solid var(--border); border-radius: 9px; color: var(--muted);
  background: none; cursor: pointer;
}
.wl-expand:hover { color: var(--text); border-color: var(--accent-line); }
.wl-expand:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.wl-expand svg { transition: transform .15s ease; }
.wl-expand[aria-expanded="true"] svg { transform: rotate(180deg); }
.wl tr.is-open > th, .wl tr.is-open > td { border-bottom: 0; }
.wl-detail-row > td { padding: 0 14px 16px; }
.wl-detail { display: grid; gap: 10px; max-width: 76ch; white-space: normal; }
.wl-detail-reasoning { margin: 0; font-size: 0.875rem; line-height: 1.6; }
.wl-detail-notes { margin: 0; font-size: 0.8125rem; line-height: 1.5; color: var(--muted); }
.wl-detail-industry {
  font-family: var(--font-mono); font-size: 0.71875rem; letter-spacing: .02em;
  color: var(--muted); overflow-wrap: anywhere;
}
.wl-sector-note { margin: 0; padding: 10px 16px; font-size: 0.78125rem; color: var(--muted);
  border-bottom: 1px solid var(--border); }
.wl-detail-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 16px; }
.wl-confirm { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; font-size: 0.8125rem; }
/* --- named lists (cloud #51): the strip, the row's checkboxes, the manager --
   The strip is the board's first row, above search: which list is the frame
   for everything under it. One line on a phone, scrolled sideways rather than
   wrapped, the Read chips' treatment at the same width. */
.wl-lists {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  padding: 12px 18px; border-bottom: 1px solid var(--border); min-width: 0;
  /* Load-bearing, as on `.board-search`: the synced chip's visually-hidden
     label is `position: absolute`, and without a positioned scroller to
     contain it, it escaped the strip's clipping and widened the whole page
     by 23px at 320 — measured. */
  position: relative;
}
.wl-lists .chip { flex: none; }
.wl-synced { font-size: 0.6875rem; color: var(--muted); }
.chip.on .wl-synced { color: var(--text); }
.wl-lists-act { flex: none; padding: 6px 4px; }
.wl-lists-act:hover { color: var(--accent-ink-soft); }
@media (max-width: 639.98px) {
  .wl-lists { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; padding: 10px 16px; }
  .wl-lists::-webkit-scrollbar { display: none; }
}
.wl-detail-lists {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px 14px;
  margin: 0; padding: 0; border: 0; min-width: 0;
}
.wl-detail-lists legend {
  float: left; margin-right: 4px; padding: 0;
  font-family: var(--font-mono); font-size: 0.65625rem; letter-spacing: .1em;
  text-transform: uppercase; color: var(--muted);
}
.wl-list-check { display: inline-flex; align-items: center; gap: 6px; font-size: 0.8125rem; min-height: 28px; }
.wl-list-check input { width: 16px; height: 16px; accent-color: var(--accent); }
.wl-list-tag {
  font-size: 0.75rem; color: var(--muted); border: 1px dashed var(--border);
  border-radius: 8px; padding: 3px 8px;
}

.lm-panel { padding-bottom: 4px; }
.lm-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 14px 16px; border-bottom: 1px solid var(--border);
}
.lm-head .panel-title { margin: 0; }
.lm-error { margin: 12px 16px 0; }
.lm-empty { margin: 0; padding: 16px; font-size: 0.84375rem; color: var(--muted); }
.lm-list { list-style: none; margin: 0; padding: 6px 8px; max-height: min(46vh, 360px); overflow-y: auto; }
.lm-row {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center;
  gap: 2px 12px; padding: 10px 8px; border-bottom: 1px solid var(--border);
}
.lm-row:last-child { border-bottom: 0; }
.lm-name { font-weight: 700; overflow-wrap: anywhere; }
.lm-meta { grid-column: 1; font-size: 0.75rem; color: var(--muted); }
.lm-actions {
  grid-column: 2; grid-row: 1 / span 2;
  display: inline-flex; align-items: center; gap: 10px;
}
.lm-move {
  width: 30px; height: 30px; border: 1px solid var(--border); border-radius: 8px;
  color: var(--muted); font-size: 0.875rem;
}
.lm-move:hover:not(:disabled) { color: var(--text); border-color: var(--accent-line); }
.lm-move:disabled { opacity: .35; cursor: default; }
.lm-move:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.lm-edit, .lm-confirm {
  grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px;
  font-size: 0.8125rem;
}
.lm-edit input, .lm-new-row input {
  flex: 1 1 12rem; min-width: 0; font: inherit; font-size: 0.875rem; color: var(--text);
  background: var(--bg); border: 1px solid var(--input-border); border-radius: 9px; padding: 7px 10px;
}
.lm-edit input:focus, .lm-new-row input:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
.lm-new { padding: 12px 16px 10px; border-top: 1px solid var(--border); }
.lm-new label {
  display: block; margin-bottom: 6px;
  font-family: var(--font-mono); font-size: 0.65625rem; letter-spacing: .1em;
  text-transform: uppercase; color: var(--muted);
}
.lm-new-row { display: flex; gap: 8px; }
.lm-hint { margin: 8px 0 0; font-size: 0.75rem; color: var(--muted); }

.wl-danger { color: var(--neg-ink); border-color: color-mix(in srgb, var(--neg) 45%, transparent); }
.wl-danger:hover { background: var(--neg-bg); border-color: var(--neg); }

/* Phones: cards. Wider: the table. Both are rendered; one is hidden — the
   same split Holdings, the Track record and the Advisor already use. */
.wl-wrap { display: none; }
.wl-stack-group + .wl-stack-group { border-top: 1px solid var(--border); }
.wl-stack .wl-group-toggle { padding: 12px 16px; }
.wl-cards { list-style: none; margin: 0; padding: 0; }
.wl-card { display: grid; gap: 4px; padding: 10px 16px; border-top: 1px solid var(--border); min-width: 0; }
.wl-card-top, .wl-card-sub {
  display: flex; align-items: center; justify-content: space-between; gap: 10px; min-width: 0;
}
.wl-card-main { display: flex; align-items: center; gap: 10px; min-width: 0; color: var(--text); }
.wl-card-main .wl-code { display: inline; }
.wl-card-main:hover .wl-code { color: var(--accent-ink-soft); }
.wl-card-main .wl-read { flex-wrap: nowrap; min-width: 0; }
.wl-card-close { flex: none; font-family: var(--font-mono); font-size: 0.8125rem; }
.wl-card-close .wl-close { grid-auto-flow: column; gap: 8px; align-items: baseline; }
.wl-card-sub .wl-name { min-width: 0; max-width: none; }
.wl-card-levels {
  font-family: var(--font-mono); font-size: 0.6875rem; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.wl-card-levels .level-atmarket { font-size: 0.6875rem; }
.wl-card-actions { display: flex; flex: none; align-items: center; gap: 8px; }
.wl-card .wl-detail { padding-top: 6px; }

/* The toolbar on a phone: chips on ONE scrolling row, Group and Sort side by
   side with their labels above. It was ~230px of controls before a single
   ticker. The chip row scrolls inside itself; `min-width: 0` up the chain is
   what stops it widening the page (the escape CLAUDE.md records). */
@media (max-width: 639px) {
  .wl-controls { display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px; padding: 10px 16px; }
  .wl-chips { flex-wrap: nowrap; overflow-x: auto; min-width: 0; scrollbar-width: none; margin-inline: -2px; padding-inline: 2px; }
  .wl-chips::-webkit-scrollbar { display: none; }
  .wl-chips .chip { flex: none; }
  .wl-selects { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  .wl-select { display: grid; gap: 4px; min-width: 0; }
  .wl-select select { width: 100%; }
  .board-search { padding-inline: 16px; }
}
/* Group and Sort fold behind one row on a phone (cloud #71). 639.98px, not
   639px: at a fractional zoom a "639px" viewport matches neither side (#48b),
   and here that would show the toggle AND both selects. */
.wl-opts-toggle {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  width: 100%; min-height: 40px; padding: 8px 12px; border-radius: 9px;
  border: 1px solid var(--border); background: transparent; color: var(--muted);
  font: inherit; font-size: 0.78125rem; cursor: pointer; text-align: left;
}
.wl-opts-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (max-width: 639.98px) { .wl-selects:not(.is-open) { display: none; } }
@media (min-width: 640px) { .wl-opts-toggle { display: none; } }

@media (min-width: 640px) {
  .wl-stack { display: none; }
  .wl-wrap { display: block; }
}

/* Column labels that stay put while a long group scrolls. A sticky header
   needs an ancestor chain with no scroll container in it, and both
   `.board-card` (overflow: hidden) and `.table-wrap` (overflow-x: auto) are
   one — so from 900px, where the table's 640px minimum can never exceed its
   card, both switch to `overflow: clip`, which clips exactly the same but is
   NOT a scroll container. Below 900px the table keeps its own scroller and
   the labels scroll with the page, as before. */
@media (min-width: 900px) {
  .board-card, .wl-wrap { overflow: clip; }
  .wl-wrap thead th {
    position: sticky; top: 62px; z-index: 2; background: var(--surface);
  }
}

/* The ticker page's way back and way along. */
.ticker-nav { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px; }
.ticker-steps { display: inline-flex; gap: 6px; }
.ticker-step {
  font-family: var(--font-mono); font-size: 0.75rem; color: var(--muted);
  border: 1px solid var(--border); border-radius: 9px; padding: 6px 10px;
  min-width: 36px; text-align: center;
}
a.ticker-step:hover { color: var(--text); border-color: var(--accent-line); }

.linkish {
  color: var(--muted); font-size: 0.78125rem; text-decoration: underline;
  text-underline-offset: 3px; padding: 0;
}
.linkish:hover { color: var(--neg-ink); }
.linkish:disabled { opacity: .55; cursor: not-allowed; }
.linkish:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }

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

/* --- direction badges ------------------------------------------------------
   `--pos`/`--neg` are FILLS. As ink on their own tint they measure 5.19:1 and
   4.31:1 in dark and worse in light, which is why `--pos-ink`/`--neg-ink`
   exist — the same split the landing page's hit/miss cells needed. Neutral is
   not a weak Bullish and gets no directional colour at all. */

.badge-pos  { color: var(--pos-ink); border-color: color-mix(in srgb, var(--pos) 34%, transparent); background: var(--pos-bg); }
.badge-neg  { color: var(--neg-ink); border-color: color-mix(in srgb, var(--neg) 34%, transparent); background: var(--neg-bg); }
.badge-flat { color: var(--muted);   border-color: var(--border); background: var(--surface); }

/* --- thesis card ----------------------------------------------------------- */

.thesis {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 16px; padding: 18px; min-width: 0;
}
.thesis-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.thesis-conviction { font-family: var(--font-mono); font-size: 0.9375rem; }
.thesis-conviction strong { font-size: 1.1875rem; font-weight: 700; }
.thesis-outof { color: var(--muted); font-size: 0.75rem; }
.thesis-when { margin-left: auto; font-size: 0.75rem; color: var(--muted); }
.thesis-reasoning { margin-top: 12px; font-size: 0.9375rem; line-height: 1.65; }

.levels {
  display: grid; gap: 10px; margin: 16px 0 0;
  grid-template-columns: repeat(auto-fit, minmax(74px, 1fr));
}
.level { min-width: 0; }
.level dt {
  font-family: var(--font-mono); text-transform: uppercase; letter-spacing: .11em;
  font-size: 0.59375rem; color: var(--muted);
}
.level dd {
  margin: 3px 0 0; font-family: var(--font-mono); font-size: 0.9375rem;
  /* Wrapping, never overflow: at 320px these four cells are ~70px each and a
     five-figure price plus a sign has to fold rather than push the card. */
  white-space: normal; overflow-wrap: anywhere;
}
/* null entry means "worth acting on at the current price". It is an ANSWER, so
   it is set in words at text weight rather than dashed out as missing. */
.level-atmarket { font-family: var(--font-display); font-size: 0.8125rem; color: var(--muted); }

.thesis-notes { margin-top: 14px; font-size: 0.84375rem; color: var(--muted); }
.thesis-foot {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-top: 16px; padding-top: 13px; border-top: 1px solid var(--border);
  font-size: 0.71875rem; color: var(--muted);
}
.thesis-compact { padding: 14px; }

/* --- ticker page ----------------------------------------------------------- */

.backlink { font-size: 0.8125rem; color: var(--muted); }
.backlink:hover { color: var(--accent-ink-soft); }

.ticker-head {
  display: flex; flex-wrap: wrap; align-items: flex-end;
  justify-content: space-between; gap: 16px; margin: 10px 0 20px;
}
.ticker-title { display: grid; gap: 4px; min-width: 0; }
.ticker-id { display: flex; align-items: center; gap: 10px; min-width: 0; }
/* The company's listed name, under the symbol and above its industry. */
.ticker-name { margin: 0; font-size: 1rem; font-weight: 600; overflow-wrap: anywhere; }
.ticker-industry { margin: 0; font-size: 0.8125rem; color: var(--muted); overflow-wrap: anywhere; }
.ticker-sector { color: var(--muted); }
.ticker-id .page-title { margin: 0; }
.ticker-price { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.ticker-last { font-family: var(--font-mono); font-size: 1.375rem; font-weight: 700; }
.ticker-asof {
  font-family: var(--font-mono); font-size: 0.65625rem; color: var(--muted);
  text-transform: uppercase; letter-spacing: .09em;
}

/* The chart beside the current thesis (cloud #86). `align-items: start`, so
   the shorter of the two keeps its own height rather than stretching. */
.ticker-lead {
  display: grid; gap: 16px; align-items: start;
  grid-template-columns: minmax(0, 1fr);
}
.ticker-lead > * { min-width: 0; }
.chart-card { min-width: 0; }

@media (min-width: 1024px) {
  .ticker-lead { grid-template-columns: minmax(0, 2fr) minmax(280px, 1fr); }
}

/* Every other card, in two equal columns filled from whichever is shorter
   (components/MasonryBoard.tsx says how and why). Before the component has
   measured anything this is an ordinary two-column grid, and on a phone it is
   one column in the page's own order. */
.ticker-board {
  display: grid; gap: 16px; margin-top: 16px; align-items: start;
  grid-template-columns: minmax(0, 1fr);
}
/* No margins on a card: MasonryBoard sizes each span from the border box, so
   a margin would eat the gap and two cards would touch. */
.ticker-board > * { min-width: 0; margin: 0; }
/* The thesis history sits in one of those columns, 476-572px wide on a
   desktop, so it sheds the 640px floor every `.wl` table carries and tightens
   its cells: five columns then fit down to a card about 380px wide. Narrower
   than that, `.table-wrap` scrolls it as it does on a phone. */
.thesis-history table.wl { min-width: 0; }
.thesis-history table.wl th, .thesis-history table.wl td { padding: 10px; }

@media (min-width: 768px) {
  .ticker-board { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-auto-flow: row dense; }
  /* 8px rows, matching ROW in MasonryBoard.tsx. Each card's span includes the
     16px between cards, so the last one in a column carries 16px too many;
     the negative margin gives it back to the chat panel below. */
  .ticker-board[data-masonry] { grid-auto-rows: 8px; row-gap: 0; margin-bottom: -16px; }
}

/* --- indicator list -------------------------------------------------------- */

.dl { margin: 0; display: grid; gap: 9px; }
.dl-row { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.dl-row dt { font-size: 0.8125rem; color: var(--muted); min-width: 0; }
.dl-row dd {
  margin: 0; font-family: var(--font-mono); font-size: 0.8125rem;
  text-align: right; white-space: normal; overflow-wrap: anywhere;
}

/* --- chart ----------------------------------------------------------------- */

.chart { display: grid; gap: 8px; min-width: 0; }
.chart-legend { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.chip {
  display: inline-flex; align-items: center; gap: 7px;
  border: 1px solid var(--border); border-radius: 9px;
  padding: 6px 10px; font-size: 0.71875rem; color: var(--muted);
  background: transparent; transition: color .15s ease, border-color .15s ease, background-color .15s ease;
}
.chip.on { color: var(--text); border-color: var(--accent-line); background: var(--accent-soft); }
.chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.swatch { width: 14px; height: 2px; border-radius: 2px; flex: none; }
.swatch.accent { background: var(--accent); }
.swatch.faint { background: var(--muted); }
.swatch.pos { background: var(--pos); }
.swatch.neg { background: var(--neg); }
.chart-note { font-size: 0.71875rem; color: var(--muted); }
/* The canvas sizes itself from clientWidth, so the pane must never be wider
   than its column — `min-width: 0` above plus this is what keeps a 900-bar
   chart inside a 320px phone. */
.chart-pane { width: 100%; min-width: 0; overflow: hidden; border-radius: 10px; }
.chart-pane-label {
  font-family: var(--font-mono); font-size: 0.65625rem; color: var(--muted);
  text-transform: uppercase; letter-spacing: .09em; padding-left: 2px;
}
.chart-warnings {
  list-style: none; margin: 4px 0 0; padding: 10px 13px;
  border: 1px solid color-mix(in srgb, var(--neg) 30%, transparent);
  background: var(--neg-bg); border-radius: 11px;
  font-size: 0.71875rem; color: var(--neg-ink);
}

/* The ticker's recent news (`TickerNews`), a card of its own since the dots
   came off the chart (cloud #54). */
.chart-news-list {
  list-style: none; margin: 6px 0 0; padding: 0;
  display: grid; gap: 6px; font-size: 0.78125rem;
}
.chart-news-list li { display: flex; gap: 8px; align-items: baseline; min-width: 0; }
.chart-news-list li > :last-child { min-width: 0; overflow-wrap: anywhere; }
.chart-news-date {
  font-family: var(--font-mono); font-size: 0.65625rem; color: var(--muted);
  flex: none;
}
/* The ten newest, or three on a phone, with the rest one tap away
   (TickerNews, cloud #86). The two numbers are WIDE_CAP and PHONE_CAP there.
   The toggle borrows `.capped-toggle`'s look, and shows at every width, except
   that a list of ten or fewer has nothing hidden above a phone's width. */
.news-capped:not(.is-open) > :nth-child(n+11) { display: none; }
.news-toggle { display: flex; }
@media (max-width: 639.98px) {
  .news-capped:not(.is-open) > :nth-child(n+4) { display: none; }
}
@media (min-width: 640px) {
  .news-toggle.is-phone-only { display: none; }
}

/* --- narrow ---------------------------------------------------------------- */

/* The app header carries four controls plus the wordmark, and at 320px they
   do not fit: measured, the nav alone is 278px against 265px of usable width,
   putting its right edge 5px past the viewport. The logomark still links home
   and still identifies the product, so the WORD is what gives way — scoped to
   `.app-header` so the marketing and sign-in headers, which have room, keep
   theirs. Verified at 320px: nav right edge 291 against a 305px client width,
   header height unchanged at 62px. */
@media (max-width: 420px) {
  .app-header-inner { gap: 6px; padding-inline: 14px; }
  .app-header .wordmark { display: none; }
  .app-nav { gap: 2px; }
  .app-nav a, .app-nav button { padding-inline: 8px; font-size: 0.625rem; }
}

/* --- scans in progress, and toasts (cloud #45) --------------------------
   A scanning row's button is disabled but must not read as INERT: it is the
   one thing on the row that is happening. Accent tint rather than the muted
   disabled look, tabular figures so "Scanning · 42s" does not jitter as the
   seconds change, and `cursor: progress`. */
.btn.is-scanning:disabled {
  color: var(--accent-ink-soft); border-color: var(--accent-line);
  background: var(--accent-soft); cursor: progress;
  font-variant-numeric: tabular-nums;
}
.wl-detail-hint { font-size: 0.78125rem; color: var(--muted); }

/* Toasts: bottom-right on wide screens; on phones full-width and ABOVE the
   bottom nav, which would otherwise cover them. The stack ignores the pointer
   so the page beneath stays clickable; each toast takes it back. The colours
   are the .flash pairs, already measured, on their opaque fills. */
.toasts {
  position: fixed; right: 16px; bottom: 16px; z-index: 50;
  display: grid; gap: 8px; width: min(380px, calc(100vw - 32px));
  pointer-events: none;
}
.toasts .toast {
  pointer-events: auto; margin: 0;
  display: flex; align-items: flex-start; gap: 10px;
  font-size: 0.84375rem; line-height: 1.45; box-shadow: var(--shadow-md);
}
.toast-text { flex: 1; min-width: 0; }
.toast-link {
  flex: none; color: inherit; font-weight: 700; white-space: nowrap;
  text-decoration: underline; text-underline-offset: 3px;
}
.toast-close {
  flex: none; background: none; border: 0; padding: 0 2px; cursor: pointer;
  color: inherit; font-size: 1.125rem; line-height: 1;
}
.toast-close:focus-visible, .toast-link:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px;
}
@media (prefers-reduced-motion: no-preference) {
  .toasts .toast { animation: toast-in .18s ease-out; }
}
@keyframes toast-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@media (max-width: 639px) {
  .toasts {
    left: 12px; right: 12px; width: auto;
    bottom: calc(58px + env(safe-area-inset-bottom) + 10px);
  }
}

/* --- jump to a ticker (QuickSwitch.tsx, cloud #46) -----------------------
   A native <dialog>, so the page behind is inert and focus is trapped. Near
   the top rather than centred, so the list grows DOWN as you type instead of
   the field jumping as results arrive. The options reuse .combo-opt, already
   measured for the highlighted state (cloud #28g's hover trap). */
.qs {
  width: min(560px, calc(100vw - 24px)); max-width: none;
  margin: 12vh auto auto; padding: 0; border: 0; background: transparent;
  color: var(--text); overflow: visible;
}
.qs::backdrop { background: rgba(8, 10, 14, 0.55); }
.qs-panel {
  background: var(--surface); border: 1px solid var(--border); border-radius: 16px;
  box-shadow: var(--shadow-lg); overflow: hidden;
}
.qs-field {
  display: flex; align-items: center; gap: 10px;
  padding: 14px 16px; border-bottom: 1px solid var(--border);
}
.qs-field input {
  flex: 1; min-width: 0; font: inherit; font-size: 1rem; color: var(--text);
  background: none; border: 0; padding: 2px 0;
}
.qs-field input::placeholder { color: var(--placeholder); }
.qs-field input:focus { outline: none; }
.qs-field:focus-within { box-shadow: inset 0 -2px 0 var(--accent); }
.qs-esc {
  flex: none; font-family: var(--font-mono); font-size: 0.65625rem; color: var(--muted);
  border: 1px solid var(--border); border-radius: 6px; padding: 3px 7px;
  background: none; cursor: pointer;
}
.qs-esc:hover { color: var(--text); border-color: var(--accent-line); }
.qs-esc:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.qs-list { max-height: min(56vh, 420px); overflow-y: auto; padding: 6px; }
.qs-list:empty { display: none; }
.qs-group {
  font-family: var(--font-mono); text-transform: uppercase; letter-spacing: .1em;
  font-size: 0.59375rem; color: var(--muted); padding: 8px 9px 4px;
}
.qs-opt { align-items: center; }
.qs-read { flex: none; font-size: 0.71875rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.qs-opt.is-active .qs-read { color: var(--text); }
.qs-empty { margin: 0; padding: 16px; font-size: 0.84375rem; color: var(--muted); }
.qs-empty a { color: var(--accent-ink-soft); text-decoration: underline; text-underline-offset: 3px; }
.qs-error { margin: 10px 12px 0; }
.qs-hint {
  margin: 0; padding: 9px 16px; border-top: 1px solid var(--border);
  font-family: var(--font-mono); font-size: 0.65625rem; color: var(--muted);
}
@media (max-width: 639px) {
  .qs { margin-top: 12px; }
  /* No keyboard shortcuts to advertise on a touch screen. */
  .qs-hint { display: none; }
}

/* Earnings in the account menu: phones only (the bar's cell went to Search). */
.acct-item.is-phone-only { display: none; }
@media (max-width: 639px) { .acct-item.is-phone-only { display: block; } }

/* --- Settings > Appearance: device / light / dark ------------------------
   Native radios dressed as chips. The radio itself is .sr-only, which is
   `position: absolute` — so the chip is the positioning context, or the
   hidden input would resolve against the initial containing block and could
   drag the page sideways (cloud #28f's one-pixel escape). */
.theme-choice {
  display: flex; flex-wrap: wrap; gap: 8px; margin: 12px 0 0; padding: 0; border: 0;
}
.theme-choice .chip { position: relative; cursor: pointer; font-size: 0.78125rem; }
.theme-choice .chip:hover { color: var(--text); border-color: var(--accent-line); }
.theme-choice .chip:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; }

/* --- phone bottom nav ------------------------------------------------------
   Below 640px the header's section links move to a fixed bar at the bottom
   (MobileNav.tsx) and the header keeps brand, theme and account — one row. The
   wrapped header it replaces was 301px of a 812px phone, and sticky.

   `position: fixed` resolves against the LAYOUT viewport, which a mobile
   browser widens to fit any horizontal overflow — and a fixed bottom bar then
   sinks to the end of the document (core's detached-nav incident, CLAUDE.md).
   Zero horizontal overflow on every page is this bar's precondition.

   The bar is OPAQUE (`--bg`), not the header's translucent tint: content
   scrolls underneath it, and 10px labels over a moving, see-through
   background would have no fixed contrast to measure. */
.mobile-nav, .mobile-nav-spacer { display: none; }
@media (max-width: 639px) {
  .app-nav .app-nav-link { display: none; }
  .mobile-nav {
    display: grid; grid-template-columns: repeat(5, minmax(0, 1fr));
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
    background: var(--bg); border-top: 1px solid var(--border);
    padding-bottom: env(safe-area-inset-bottom);
  }
  .mobile-nav a, .mobile-nav button {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    background: none; border: 0; cursor: pointer;
    gap: 4px; min-width: 0; min-height: 58px; padding: 7px 2px 6px;
    font-family: var(--font-mono); text-transform: uppercase; letter-spacing: .06em;
    /* 10px at the default text size, and it grows with a larger one (cloud
       #92) only until the cell is full. A cell is a fifth of the width, and a
       mono label costs 0.66em a letter with its spacing, so at 3.125vw the
       longest, "WATCHLIST", fills 18.6vw of a 20vw cell at any phone width.
       From 320px up the cap is at least 10px, so the default never moves. */
    font-size: min(0.625rem, 3.125vw); font-weight: 500; color: var(--muted);
  }
  /* Truncate inside the cell rather than widening it: at 320px a cell is
     64px, and a label that set its own width would misalign the icons. */
  .mobile-nav span { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .mobile-nav svg { width: 21px; height: 21px; flex: none; }
  .mobile-nav a[aria-current="page"] { color: var(--accent-ink-soft); }
  .mobile-nav a:focus-visible, .mobile-nav button:focus-visible {
    outline: 2px solid var(--accent); outline-offset: -4px; border-radius: 10px;
  }
  /* The header's search button hands over to the bar's Search cell. */
  .app-nav .qs-trigger { display: none; }
  .mobile-nav-spacer { display: block; height: calc(58px + env(safe-area-inset-bottom)); }
}

@media (max-width: 560px) {
  .board-head { align-items: stretch; }
  .board-stats { display: none; }
  .board-stats-line { display: block; }
  .board-add { grid-template-columns: minmax(0, 1fr); }
  .board-add .btn { justify-self: start; }
  .ticker-price { width: 100%; }
}

/* ===========================================================================
   Parity surface — opportunities, the track record, news and chat.

   Everything here uses the tokens defined at the top of this file. Nothing
   defines a colour of its own, which is what keeps the two themes honest:
   a hard-coded hex is a value that has been measured in one theme and guessed
   in the other.
   =========================================================================== */

/* --- opportunities --------------------------------------------------------- */

.opps-caption {
  color: var(--muted); font-size: 0.84375rem; line-height: 1.55;
  margin: 10px 0 16px; max-width: 68ch;
}
.opps-caption a { color: var(--accent-ink-soft); text-decoration: underline; }
[data-theme="light"] .opps-caption a { color: var(--accent); }
.opps-caption strong { color: var(--text); font-weight: 700; }

.opps-grid { display: grid; gap: 14px; }
@media (min-width: 860px) { .opps-grid { grid-template-columns: 1fr 1fr; } }
/* min-width: 0 on the column is load-bearing, not defensive. A grid item is
   `min-width: auto`, so any wide descendant sets the column's minimum and the
   column then widens the grid — which is how a table inside a scroll box drags
   a whole page sideways. */
.opps-col { min-width: 0; }
.opps-note {
  color: var(--muted); font-size: 0.75rem; margin: -4px 0 12px;
}

.opps-list { list-style: none; display: grid; gap: 12px; }
.opp {
  border: 1px solid var(--border); border-radius: 12px;
  padding: 12px 13px; min-width: 0;
}
.opp-head {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-bottom: 9px;
}
.opp-head .wl-code { display: inline; }
.opp-score {
  font-family: var(--font-mono); font-size: 0.78125rem; color: var(--muted);
  margin-left: auto;
}

.opp-levels {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 7px 14px; margin: 0 0 9px;
}
@media (min-width: 520px) {
  .opp-levels { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
.opp-levels dt {
  font-family: var(--font-mono); text-transform: uppercase;
  letter-spacing: .09em; font-size: 0.59375rem; color: var(--muted);
}
.opp-levels dd {
  font-family: var(--font-mono); font-size: 0.78125rem; margin: 2px 0 0;
}
/* Both legs, never the ratio alone: a big R:R built on a 1% stop is a warning
   dressed as a strength. */
.opp-leg { color: var(--muted); font-size: 0.6875rem; }

.opp-meta, .opp-notes {
  color: var(--muted); font-size: 0.71875rem; margin: 0 0 6px; line-height: 1.5;
}
.opp-notes { color: var(--accent-ink-soft); }
[data-theme="light"] .opp-notes { color: var(--accent); }

.opp-components {
  display: grid; gap: 6px; margin-top: 10px;
  padding-top: 10px; border-top: 1px solid var(--border);
}
.opp-components > div {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 10px;
  align-items: center;
}
.opp-components dt {
  font-size: 0.71875rem; color: var(--muted); text-transform: capitalize;
}
.opp-components dd {
  display: flex; align-items: center; gap: 8px; margin: 0;
  font-family: var(--font-mono); font-size: 0.6875rem; color: var(--muted);
}
.opp-bar {
  display: block; width: 74px; height: 5px; border-radius: 3px;
  background: var(--border); overflow: hidden;
}
.opp-bar > span { display: block; height: 100%; background: var(--accent); }

/* --- track record ---------------------------------------------------------- */

.record-head { align-items: flex-start; margin-bottom: 6px; }
.record-lede {
  color: var(--muted); font-size: 0.84375rem; line-height: 1.55;
  margin-top: 6px; max-width: 72ch;
}
.record-metrics { margin: 18px 0 22px; }
/* Above everything it changes, the glance and the breakdown alike. */
.record-measure { display: grid; gap: 6px; margin: 4px 0 14px; }
.record-grid { display: grid; gap: 14px; }
/* Two columns only from 1100px, not 900. Below that a card is narrower than
   its own table and the last two columns — SAMPLES and DAYS — fall off the
   right into the scroller. Those two are the entire point of this view
   (decisions #67c, #69b): a hit rate whose sample count is out of sight is
   exactly the over-claiming figure this component exists to prevent. Measured
   in a browser at 1280px, where the two-column grid gave each card ~440px
   against a 640px table.

   1160, not 1100: measured at exactly 1100 the wrap came out 464px against a
   table whose CONTENT wants 469, clipping "Days" by 5px. The breakpoint has
   to clear the table's natural width, not merely its declared floor. */
@media (min-width: 1160px) { .record-grid { grid-template-columns: 1fr 1fr; } }
.record-card { min-width: 0; }

/* `table.wl` sets a 640px floor sized for the seven-column watchlist board.
   This table has five short columns and does not need it — and holding it to
   a width it does not want is what pushed its counts out of view. */
.record-table table.wl { min-width: 400px; }

/* A bucket under the sample or distinct-day floor keeps its numbers and loses
   every bit of emphasis. Hiding it was rejected: a corpus that is visibly
   building is useful, one that renders empty looks broken. */
.is-thin { color: var(--muted); }
.is-thin .record-val, .is-thin td, .is-thin th { color: var(--muted); }

/* Stacked below 640, a table above it. The fix for a table a phone cannot fit
   is not to contain it better — a `min-width` inside an `overflow-x` box still
   sets its grid item's minimum and escapes sideways. */
.record-stack { list-style: none; display: grid; gap: 12px; }
.record-stack li {
  display: grid; gap: 3px;
  border-left: 2px solid var(--border); padding-left: 11px;
}
.record-key { font-size: 0.8125rem; font-weight: 700; }
.record-val { font-family: var(--font-mono); font-size: 0.75rem; }
.record-n { color: var(--muted); font-size: 0.6875rem; line-height: 1.5; }
.record-table { display: none; }
@media (min-width: 640px) {
  .record-stack { display: none; }
  .record-table { display: block; }
}
.record-foot {
  color: var(--muted); font-size: 0.78125rem; line-height: 1.6;
  margin-top: 18px; max-width: 74ch;
}

/* --- news ------------------------------------------------------------------ */

.news-card, .sources-card { min-width: 0; }
.sources-card { margin-top: 16px; }

.news-list { list-style: none; display: grid; gap: 0; }
.news-item {
  padding: 13px 0; border-top: 1px solid var(--border); min-width: 0;
}
.news-item:first-child { border-top: none; padding-top: 2px; }
.news-title { font-size: 0.90625rem; line-height: 1.45; font-weight: 600; }
.news-title a { color: var(--text); }
.news-title a:hover { color: var(--accent-ink-soft); }
[data-theme="light"] .news-title a:hover { color: var(--accent); }

.news-meta {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px;
  margin-top: 5px; font-size: 0.71875rem; color: var(--muted);
}
.news-source { font-weight: 600; }
.news-dot { opacity: .5; }
/* Not decoration: the list is ordered on this timestamp, so flagging it as an
   estimate is flagging the ordering as approximate. */
.news-est { font-style: italic; }
.news-also { font-style: italic; }

.news-codes { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 7px; }
.news-code {
  font-family: var(--font-mono); font-size: 0.65625rem; font-weight: 600;
  letter-spacing: .06em;
  padding: 3px 7px; border-radius: 7px;
  background: var(--accent-soft); color: var(--accent-ink-soft);
}
[data-theme="light"] .news-code { color: var(--accent); }
/* Hover swaps the GROUND from --accent-soft (14% accent) to --accent-line
   (34%), which lightens what the ink is read against — so the ink has to move
   with it. Measured before the fix: 4.23:1 dark and 3.63:1 light, both under
   the floor, while the resting state passed at 5.69/4.68. A resting-state
   contrast sweep cannot see this: the colour only exists under the pointer
   (cloud #28g). Same family as --accent-ink-soft and --pos-ink/--neg-ink —
   a fill and an ink are not the same value. */
.news-code:hover { background: var(--accent-line); color: var(--text); }
/* A tilde marks an inferred link (a company-name match) against a definitional
   one (the ticker's own feed). Both are shown; only one is certain. */
.news-basis { opacity: .65; margin-left: 2px; }

.sources-ok, .sources-bad { font-size: 0.8125rem; margin: 0; }
.sources-ok { color: var(--muted); }
.sources-bad { color: var(--neg-ink); }
.sources-list { list-style: none; display: grid; gap: 10px; margin-top: 14px; }
.sources-list li {
  display: grid; gap: 3px; grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  border-top: 1px solid var(--border); padding-top: 10px;
}
.sources-name { font-size: 0.8125rem; font-weight: 600; min-width: 0; }
.sources-cat {
  grid-column: 1; font-size: 0.6875rem; color: var(--muted);
}
.sources-list .badge { grid-column: 2; grid-row: 1; justify-self: end; }
.sources-when {
  grid-column: 1 / -1; font-size: 0.6875rem; color: var(--muted); line-height: 1.5;
}

/* --- the scheduler card ---------------------------------------------------- */

/* 36px, which is the rhythm this page already uses between sections —
   measured, not chosen: the gap from the metric tiles to the "Recent admin
   actions" heading is 36px, and at the 22px this carried from the dashboard
   the card read as attached to the audit table above it rather than as its own
   section. */
.sched-card { margin-top: 36px; min-width: 0; }
.sched-lede {
  color: var(--muted); font-size: 0.84375rem; line-height: 1.55;
  margin: 0 0 14px; max-width: 70ch;
}
.sched-rows {
  display: grid; gap: 10px 18px; margin: 0 0 14px;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
}
.sched-rows dt {
  font-family: var(--font-mono); text-transform: uppercase;
  letter-spacing: .09em; font-size: 0.59375rem; color: var(--muted);
}
.sched-rows dd { font-size: 0.84375rem; margin: 3px 0 0; }
.sched-budget {
  font-size: 0.8125rem; line-height: 1.6; color: var(--muted);
  border: 1px solid var(--border); border-radius: 12px;
  padding: 11px 13px; margin: 0 0 14px;
}
.sched-budget strong { color: var(--text); }
.sched-budget.is-low {
  border-color: color-mix(in srgb, var(--neg) 34%, transparent);
  background: var(--neg-bg); color: var(--neg-ink);
}
.sched-budget.is-low strong { color: var(--neg-ink); }
/* The note follows the BUTTON, and a button carries no bottom margin, so this
   sat flush against it at a measured 0px. 14px matches the card's own internal
   rhythm (lede -> rows -> budget -> button are all 14). */
.sched-note {
  color: var(--muted); font-size: 0.78125rem; line-height: 1.55;
  margin: 14px 0 0; max-width: 70ch;
}

/* --- chat ------------------------------------------------------------------ */

.chat-card { margin-top: 22px; min-width: 0; }
.chat-intro {
  color: var(--muted); font-size: 0.8125rem; line-height: 1.6;
  margin: 0 0 14px; max-width: 72ch;
}
.chat-log { display: grid; gap: 14px; margin-bottom: 14px; }
.chat-q {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 12px; padding: 9px 12px;
  font-size: 0.875rem; font-weight: 600;
}
.chat-a { padding-inline: 2px; }
.chat-a summary {
  cursor: pointer; font-size: 0.71875rem; color: var(--muted);
  margin-bottom: 5px;
}
.chat-a summary:hover { color: var(--text); }
.chat-think {
  white-space: pre-wrap; font-size: 0.71875rem; line-height: 1.6;
  color: var(--muted); border-left: 2px solid var(--border);
  padding-left: 10px; margin: 0 0 8px;
}
.chat-text {
  white-space: pre-wrap; font-size: 0.875rem; line-height: 1.65;
  overflow-wrap: anywhere;
}
.chat-caret {
  display: inline-block; width: 6px; height: 14px; margin-left: 3px;
  background: var(--accent); vertical-align: middle;
  animation: chat-blink 1s steps(2, start) infinite;
}
@keyframes chat-blink { to { visibility: hidden; } }
@media (prefers-reduced-motion: reduce) {
  .chat-caret { animation: none; opacity: .6; }
}
.chat-truncated { color: var(--muted); font-size: 0.71875rem; margin-top: 6px; }

.chat-presets { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
.chat-form { display: flex; align-items: flex-end; gap: 8px; }
.chat-form .field { flex: 1 1 auto; min-width: 0; margin-bottom: 0; }
.chat-foot {
  color: var(--muted); font-size: 0.71875rem; line-height: 1.55;
  margin: 10px 0 0; max-width: 70ch;
}

/* The chat presets reuse .chip, which was written for the chart legend and so
   had no disabled state — a preset must visibly stop being pressable while a
   generation is in flight, or a second click queues a question nobody asked
   twice. Muted with TOKENS rather than `opacity`, which would drag the label's
   contrast down with it (cloud #25g). */
.chip:disabled { cursor: not-allowed; color: var(--muted); border-color: var(--border); }
.chip:not(:disabled) { cursor: pointer; }
.chip:not(:disabled):hover { color: var(--text); border-color: var(--accent-line); }

/* --- glossary ------------------------------------------------------------
   The definition popover, `components/GlossaryTerm.tsx`.

   EVERY COLOUR HERE IS AN EXISTING TOKEN, deliberately. This component's
   resting state IS a hover trigger, and cloud #28g and #29 both record
   hover-only colours that a resting-state contrast sweep cannot see — one of
   them shipped twice before anyone measured it. Introducing no new value
   means there is no new value to get wrong, in either theme.

   The trigger inherits its text colour from whatever it sits in (a muted
   `<dt>`, a table header, body prose), so only the UNDERLINE changes on
   hover. A trigger that recoloured its own text would have to be measured
   separately in every context it appears in. */
.gloss { display: inline; }

.gloss-term {
  /* `color: inherit` is the whole trick — see above. */
  font: inherit; color: inherit; background: none; border: 0; padding: 0;
  cursor: help; text-align: left;
  text-decoration: underline;
  text-decoration-style: dotted;
  text-decoration-color: var(--muted);
  text-underline-offset: 4px;
}
.gloss-term:hover { text-decoration-color: var(--text); }
/* A <button> resets text-transform, so in a column header the term read
   "Entry" beside "TICKER". In headers it takes the header's case and spacing;
   in prose and on cards nothing changes. */
th .gloss-term { text-transform: inherit; letter-spacing: inherit; }
.gloss-term:focus-visible {
  outline: 2px solid var(--accent-line); outline-offset: 2px; border-radius: 3px;
}

/* Fixed, not absolute. These triggers sit inside `.table-wrap`, which is a
   scroller AND a positioning context, so an absolute panel would be clipped by
   its own column. The component measures the trigger and clamps to the
   viewport; nothing here may reintroduce an anchor. */
.gloss-panel {
  position: fixed; z-index: 60;
  width: 288px; max-width: calc(100vw - 16px);
  padding: 12px 13px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
  box-shadow: 0 24px 60px -20px rgba(0, 0, 0, .55);

  /* TYPOGRAPHY RESET, and it is load-bearing rather than defensive. `position:
     fixed` takes the panel out of the LAYOUT flow; it does not take it out of
     the INHERITANCE chain, and the panel is a DOM child of whatever element
     carries its trigger. Mounted in a `table.wl th` — which is where Entry,
     Stop and Target live — it inherited `text-transform: uppercase`,
     `font-family: var(--font-mono)`, the header's letter-spacing AND
     `white-space: nowrap`, so a wrapped 288px definition rendered as one
     unwrapped line of shouting monospace running off the right of the screen.
     Found in a browser; no DOM sweep would have flagged it, because every
     computed value was exactly what the cascade said it should be.

     Every floating panel needs this. Inheriting from an arbitrary mount point
     is not a style. */
  font-family: var(--font-display); font-size: 0.75rem; font-weight: 400;
  font-style: normal; line-height: 1.55;
  text-transform: none; letter-spacing: normal; white-space: normal;
  text-align: left; color: var(--text);
}
.gloss-head { font-size: 0.75rem; font-weight: 700; color: var(--text); margin: 0; }
.gloss-body {
  font-size: 0.75rem; line-height: 1.55; color: var(--muted); margin: 6px 0 0;
}
/* The second paragraph is separated by a rule and a size step, never by a
   paler ink — core's `/80` opacity would be a colour nobody has measured. */
.gloss-detail {
  font-size: 0.71875rem; line-height: 1.55; color: var(--muted); margin: 8px 0 0;
  padding-top: 8px; border-top: 1px solid var(--border);
}

/* "How to read this chart", the daily chart's footer on /ticker/[code]
   (cloud #86; a card of its own until then). `.chart-card .chart-foot-title`
   rather than a bare class, because `.card h3` would otherwise set it at
   1.1rem. */
.chart-foot {
  display: grid; gap: 8px; margin-top: 14px; padding-top: 12px;
  border-top: 1px solid var(--border);
}
.chart-card .chart-foot-title {
  font-size: 0.75rem; font-weight: 800; letter-spacing: -0.01em; margin: 0;
}
/* Side by side where the card is wide enough, so the legend costs the chart
   card one short row rather than three, and the card stays close to the
   thesis beside it in height. */
.chart-foot .chart-legend-note {
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); column-gap: 18px;
}
.chart-legend-note { display: grid; gap: 8px; }
.chart-legend-note p {
  margin: 0; font-size: 0.78125rem; line-height: 1.6; color: var(--muted);
}

/* --- movers (Watch today / Top gainers / Top losers) ---------------------
   Three columns, and they go three-up LATER than `.opps-grid` goes two-up:
   each column carries a ticker, a company name and two numbers on one line,
   so at 860px three of them would put the name into a two-line wrap on every
   row. 1080 clears the natural width of the widest row rather than merely the
   declared floor — the same lesson cloud #29 recorded when a 1100px
   breakpoint clipped "Days" by 5px against a table wanting 469. */
.movers-grid { display: grid; gap: 14px; }
@media (min-width: 700px)  { .movers-grid { grid-template-columns: 1fr 1fr; } }
@media (min-width: 1080px) { .movers-grid { grid-template-columns: 1fr 1fr 1fr; } }

/* min-width: 0 for the reason spelled out on `.opps-col`: a grid item defaults
   to min-width: auto, so a long company name would widen the whole grid. */
.movers-col { min-width: 0; }
.panel-title.is-pos { color: var(--pos-ink); }
.panel-title.is-neg { color: var(--neg-ink); }

.movers-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.mover-row {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 8px; border-radius: 10px; min-width: 0;
}
.mover-row:hover { background: var(--raised); }
.mover-rank {
  flex: none; width: 14px; text-align: right;
  font-size: 0.6875rem; font-weight: 700; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.mover-id { min-width: 0; flex: 1; display: grid; gap: 1px; }
.mover-note { font-size: 0.6875rem; color: var(--muted); }
/* The direction words inside a note. Ink variants, not the fill variants —
   these are TEXT on a card and on a hovered row, and --pos/--neg measure below
   the floor as ink in both themes (see the token block). */
.mover-note .is-pos { color: var(--pos-ink); font-weight: 700; }
.mover-note .is-neg { color: var(--neg-ink); font-weight: 700; }
.mover-note .is-flat { color: var(--muted); }
.mover-figures {
  flex: none; display: flex; align-items: center; gap: 8px;
}
.mover-price {
  font-weight: 700; font-size: 0.875rem; font-variant-numeric: tabular-nums;
}

/* --- ticker combobox -----------------------------------------------------
   The wrapper is a grid item in `.board-add`, so it inherits the min-width: 0
   that `.field` used to carry directly — without it a long company name in the
   list would set the column's minimum and widen the whole form. */
.combo { min-width: 0; }
.combo .field { margin-bottom: 0; }

/* Fixed, and positioned from JS. `.board-card` is `overflow: hidden`, so an
   absolutely-positioned list would be clipped at the card's edge. See the
   component's own note. */
.combo-list {
  position: fixed; z-index: 60;
  /* The same reset, for the same reason — see `.gloss-panel`. This one is
     mounted in a `.field` today and inherits nothing harmful, which is exactly
     why it needs saying: the next caller decides where it mounts. */
  text-transform: none; letter-spacing: normal; white-space: normal;
  text-align: left; color: var(--text);
  list-style: none; margin: 0; padding: 4px;
  max-height: 288px; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
  box-shadow: 0 24px 60px -20px rgba(0, 0, 0, .55);
}
.combo-opt {
  display: flex; align-items: baseline; gap: 8px;
  padding: 7px 9px; border-radius: 8px; cursor: pointer; min-width: 0;
}
/* One class for hover AND keyboard: `aria-activedescendant` moves the
   highlight without moving focus, so a `:hover` rule alone would leave an
   arrowing keyboard user with no visible position. The ground is
   --accent-soft and the ink moves with it — a highlighted row read at
   --accent over its own tint measures 3.89:1 on a card, which is the
   hover-state trap cloud #28g and #29 both recorded. */
.combo-opt.is-active { background: var(--accent-soft); }
.combo-opt.is-active .combo-sym { color: var(--accent-ink-soft); }
.combo-sym {
  flex: none; font-weight: 700; font-size: 0.8125rem;
  font-family: var(--font-mono); letter-spacing: .02em;
}
.combo-name {
  min-width: 0; flex: 1; font-size: 0.75rem; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* --- sector rotation -----------------------------------------------------
   Shares `.movers-list` / `.mover-row` — same shape of row, different subject,
   and a second set of near-identical rules is how two lists drift apart. */
.sector-name { font-size: 0.8125rem; font-weight: 600; }
.sector-score {
  flex: none; font-size: 0.875rem; font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.sector-score.is-pos { color: var(--pos-ink); }
.sector-score.is-neg { color: var(--neg-ink); }
/* An under-measured row loses ALL colour, exactly as core's does. A score
   computed from half its components rendered in confident green is the
   over-claiming the coverage floor exists to prevent. */
.sector-score.is-thin { color: var(--muted); font-weight: 600; }

/* The Earnings placeholder. Prose in a card, at reading width — a page whose
   whole job is to explain something should not be set in the 12px used for
   captions beside numbers. */
.planned-body { display: grid; gap: 12px; max-width: 62ch; }
.planned-body p { font-size: 0.875rem; line-height: 1.65; color: var(--muted); margin: 0; }
.planned-body strong { color: var(--text); }

/* A section label that is not a heading. Used where the cards below already
   carry their own titles and a second one would say the same thing twice. */
.section-eyebrow {
  font-family: var(--font-mono); text-transform: uppercase; letter-spacing: .11em;
  font-size: 0.625rem; font-weight: 500; color: var(--muted);
}

/* --- board search ---------------------------------------------------------
   A filter over the watchlist already in memory — no fetch, no credit. It
   sits between the add form and the table because that is the order of the
   two questions: "add something new" then "find something I have".

   Appears only once the board is big enough for finding a row to be work
   (`SEARCH_FROM` in dashboard/page.tsx). */
.board-search {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 18px; border-bottom: 1px solid var(--border);
  /* `position: relative` is load-bearing, exactly as it is on `.table-wrap`.
     The visually-hidden label inside is `position: absolute`, so without a
     positioning context here its containing block is the INITIAL one — it
     resolves at some document-wide x, escapes `.board-card`'s
     `overflow: hidden` (which only clips descendants whose containing block is
     inside it) and drags the page sideways. That is cloud #28f measured at
     218px from one hidden "Actions" label, and it is invisible by definition. */
  position: relative;
}
.board-search-icon { flex: none; width: 15px; height: 15px; color: var(--muted); }
.board-search input {
  flex: 1; min-width: 0;
  font: inherit; font-size: 0.875rem; color: var(--text);
  background: none; border: 0; padding: 2px 0;
}
.board-search input::placeholder { color: var(--placeholder); }
.board-search input:focus { outline: none; }
/* The focus ring goes on the ROW, not the borderless input: an outline around
   a field with no visible box reads as a rendering fault. */
.board-search:focus-within {
  background: var(--accent-soft);
  box-shadow: inset 0 0 0 1px var(--accent-line);
}
/* Safari paints its own clear affordance on type="search"; ours is a labelled
   button that keyboard users can reach and screen readers can name. */
.board-search input::-webkit-search-cancel-button { display: none; }

/* --- market session strip -------------------------------------------------
   Displayed, never gated on: `session_of` is this strip's SUBJECT, and
   nothing on the page changes because of what it says (decisions #65).

   Quiet by design. It sits directly above the alerts card, which is the only
   surface in this product allowed to demand attention, and a status line that
   competed with it would be the wrong thing shouting. */
.session-strip {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px;
  font-family: var(--font-mono); font-size: 0.6875rem; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted);
  margin: -6px 0 18px;
}
.session-dot {
  width: 7px; height: 7px; border-radius: 50%; flex: none;
  align-self: center; background: var(--muted);
}
.session-strip.is-open   .session-dot { background: var(--pos); }
.session-strip.is-edge   .session-dot { background: var(--accent); }
.session-strip.is-closed .session-dot { background: var(--border); }
.session-state { color: var(--text); font-weight: 700; }
/* The ink variant, not the fill: --pos measures below the floor as TEXT on
   this ground in both themes. Same split the movers rows use. */
.session-strip.is-open .session-state { color: var(--pos-ink); }
.session-count { color: var(--text); }
/* The honesty tag, and it is permanently on screen rather than tooltip-only:
   a caveat that exists only on hover does not exist on a phone. The title
   carries the detail; the visible words carry the claim. */
.session-assumes {
  text-transform: none; letter-spacing: 0; cursor: help;
  border-bottom: 1px dotted var(--border);
}
@media (max-width: 560px) { .session-strip { font-size: 0.625rem; gap: 6px; } }

/* --- valuation calculator --------------------------------------------------
   No `<input type="range">` existed anywhere in this file before this —
   `accent-color` is plain CSS, not a Tailwind utility, so it needs an
   explicit rule here rather than a class on the element. */

.val-mos-field { margin-top: 14px; margin-bottom: 0; }
/* Its one use sits inside `.field`, whose `.field input` (site.css) has always
   set this slider's corner radius and background: like `.card h3` at the top
   of this file, it outranked this rule until the layers of cloud #90. The
   999px radius and border-coloured track this rule asked for never rendered,
   so they are gone, rather than switched on by the layers. */
.val-slider {
  width: 100%; height: 6px; appearance: none;
  accent-color: var(--accent); cursor: pointer;
}

.val-verdict-head {
  display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: 10px; margin-bottom: 4px;
}
.val-verdict-value { font-size: 1.625rem; font-weight: 800; letter-spacing: -0.02em; color: var(--text); }

/* "Under the hood" — a plain <details>, richer than `.chat-a summary`'s
   minimal think-block styling, since this carries a full table of steps
   rather than one line of reasoning trace. */
.val-breakdown {
  border: 1px solid var(--border); border-radius: 12px;
  background: var(--surface); padding: 10px 14px;
}
.val-breakdown summary {
  cursor: pointer; font-size: 0.8125rem; font-weight: 600; color: var(--text);
  list-style: none;
}
.val-breakdown summary::-webkit-details-marker { display: none; }
.val-breakdown summary::before { content: "▸ "; color: var(--muted); }
.val-breakdown[open] summary::before { content: "▾ "; }
.val-steps { margin: 10px 0 0; padding-top: 10px; border-top: 1px solid var(--border); }
.val-step {
  display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between;
  gap: 10px; padding: 3px 0; font-size: 0.78125rem;
}
.val-step-label { color: var(--muted); }
.val-step-expr { font-family: var(--font-mono); font-size: 0.71875rem; }

/* The sensitivity matrix reuses table.wl (see the ticker history table) — the
   only addition needed is a way to mark the one cell matching the form's
   current discount-rate/terminal-growth pair. */
/* Cells are tinted by which side of the price they land on (cloud #52), so the
   current pair is marked by an OUTLINE rather than its own fill — a fill would
   hide the very tint the table is read by. */
table.wl td.val-cell-above { background: color-mix(in srgb, var(--pos-bg) 70%, transparent); }
table.wl td.val-cell-below { background: color-mix(in srgb, var(--neg-bg) 70%, transparent); }
table.wl td.val-cell-current {
  font-weight: 800; outline: 2px solid var(--accent); outline-offset: -2px;
}

.val-error {
  border: 1px solid color-mix(in srgb, var(--neg) 34%, transparent);
  background: var(--neg-bg); color: var(--neg-ink);
  border-radius: 10px; padding: 9px 12px; font-size: 0.8125rem;
}

.val-hint { font-size: 0.71875rem; color: var(--muted); margin-top: 4px; }

.val-chart-legend {
  display: flex; flex-wrap: wrap; gap: 14px; margin-top: 6px;
  font-size: 0.6875rem; color: var(--muted);
}
.val-chart-legend span { display: inline-flex; align-items: center; gap: 6px; }
.val-swatch { width: 9px; height: 9px; border-radius: 2px; display: inline-block; }
.val-swatch-muted { background: var(--muted); opacity: .6; }
.val-swatch-accent { background: var(--accent); }
.val-swatch-pos { background: var(--pos); }

.val-interpret-panel { margin-top: 16px; }
.val-interpretation {
  border: 1px solid var(--border); border-radius: 12px;
  background: var(--surface); padding: 12px 14px; margin-top: 8px;
  font-size: 0.8125rem; line-height: 1.55;
}
.val-interpretation p + p { margin-top: 8px; }


/* --- valuation layout (cloud #52) -----------------------------------------
   Answer first: the verdict banner full width, then assumptions beside what
   they produce, then the arithmetic. Every grid child carries min-width: 0 —
   a min-width escaping a grid item is what dragged core's page sideways
   (CLAUDE.md's scorecard gotcha), and the sensitivity table has one. */

.val-models { margin-bottom: 4px; }
.val-model-blurb { color: var(--muted); font-size: 0.8125rem; margin: 0 0 14px; }
.val-layout { display: grid; gap: 16px; }
.val-layout > * { min-width: 0; }
.val-grid { display: grid; gap: 16px; grid-template-columns: minmax(0, 1fr); }
.val-grid > * { min-width: 0; }
@media (min-width: 1024px) {
  .val-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: start; }
}

.val-banner { display: grid; gap: 14px; }
.val-banner-head {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: 14px 32px;
}
.val-figure { min-width: 0; }
.val-figure .metric-label { margin-bottom: 4px; }
.val-figure-verdict { display: grid; gap: 6px; justify-items: start; margin-left: auto; }
.val-unit { font-size: 0.8125rem; font-weight: 500; color: var(--muted); letter-spacing: 0; }
.val-price-value { font-size: 1.25rem; font-weight: 700; letter-spacing: -0.01em; color: var(--text); }
.val-upside { font-size: 0.8125rem; color: var(--muted); }
.val-upside strong { color: var(--text); }
@media (max-width: 560px) {
  .val-figure-verdict { margin-left: 0; }
}
.val-implied {
  font-size: 0.875rem; line-height: 1.6; color: var(--text);
  border-left: 3px solid var(--accent); padding: 2px 0 2px 12px; max-width: 80ch;
}
.val-note {
  font-size: 0.8125rem; line-height: 1.55; color: var(--muted);
  border: 1px solid var(--border); border-radius: 10px; padding: 9px 12px;
  background: var(--bg);
}

.val-gauge svg { display: block; width: 100%; height: auto; max-height: 64px; }
.val-swatch-text { background: var(--text); width: 4px; height: 12px; }
.val-swatch-round { border-radius: 50%; }
.val-swatch-dash {
  width: 2px; height: 12px; border-radius: 0;
  background: repeating-linear-gradient(var(--pos) 0 3px, transparent 3px 5px);
}
.val-swatch-band { background: var(--accent-soft); border: 1px solid var(--accent-line); }

.val-group-head {
  display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between;
  gap: 8px 16px; margin: 4px 0 10px;
}
.val-group-head .linkish:hover { color: var(--text); }
/* `.card h3` (the marketing cards' heading rule) would otherwise win on
   specificity and set this at 1.1rem/800 — hence the element in the selector. */
.card h3.val-group-title {
  font-family: var(--font-mono); text-transform: uppercase; letter-spacing: .11em;
  font-size: 0.625rem; font-weight: 500; color: var(--muted); margin: 18px 0 10px;
}
.val-group-head h3.val-group-title { margin: 0; }
.val-echo { font-family: var(--font-mono); font-size: 0.71875rem; }
.val-capm { margin-top: 14px; }
.val-capm .admin-form { margin-top: 12px; }

.val-buildup { display: grid; gap: 18px; margin-bottom: 18px; }
.val-figure-chart { margin: 0; display: grid; gap: 8px; }
.val-figure-chart svg { display: block; width: 100%; height: auto; }
.val-list { display: grid; gap: 8px; }

/* --- watched valuations (cloud #56) --------------------------------------- */

/* The card under the verdict banner, and the list in Settings. Built from the
   scheduler card's pieces (.sched-lede, .sched-rows, .sched-budget) so a
   watch reads like the other things that run while you are away. */
.vw-card .sched-lede { margin-bottom: 12px; }
.vw-card .sched-lede strong { color: var(--text); }
.vw-card .field.vw-band { max-width: 22rem; margin: 0 0 12px; }
.vw-card .field.vw-band select { width: 100%; }
.vw-hint {
  color: var(--muted); font-size: 0.78125rem; line-height: 1.55;
  margin: 0 0 10px; max-width: 70ch;
}
.vw-hint a { color: var(--text); text-decoration: underline; text-underline-offset: 2px; }
.vw-card .btn-row { margin-top: 4px; }
.vw-inline-actions { margin-top: 10px; }
.vw-flash { margin: 12px 0 0; }
.vw-after-row { margin-top: 14px; }
.vw-lines dd { font-variant-numeric: tabular-nums; }
.vw-events {
  list-style: none; margin: 0 0 14px; padding: 0; display: grid; gap: 6px;
  font-size: 0.8125rem; line-height: 1.5;
}
.vw-event-date {
  font-family: var(--font-mono); font-size: 0.6875rem; color: var(--muted);
  white-space: nowrap;
}
.vw-event-status { color: var(--muted); }

.vw-list { list-style: none; margin: 0; padding: 0; display: grid; }
.vw-list li {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 8px 14px; padding: 12px 0; border-top: 1px solid var(--border);
}
.vw-list li:first-child { border-top: 0; padding-top: 0; }
.vw-list-main {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 12px;
  min-width: 0; flex: 1 1 16rem;
}
.vw-list-code { font-weight: 700; color: var(--text); }
.vw-list-model {
  font-family: var(--font-mono); text-transform: uppercase; letter-spacing: .09em;
  font-size: 0.625rem; color: var(--muted);
}
.vw-list-figs { font-size: 0.84375rem; font-variant-numeric: tabular-nums; }
.vw-list-status { font-size: 0.78125rem; color: var(--muted); flex-basis: 100%; }
.vw-list-status.is-paused { color: var(--neg-ink); }
.val-bars { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.val-bars li {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 4px 12px;
  font-size: 0.8125rem;
}
.val-bar-label { color: var(--muted); min-width: 0; }
.val-bar-value { font-family: var(--font-mono); font-size: 0.78125rem; text-align: right; }
.val-bar-track {
  grid-column: 1 / -1; height: 8px; border-radius: 999px; background: var(--border);
  overflow: hidden;
}
.val-bar-fill { display: block; height: 100%; border-radius: 999px; }
.val-bar-fill.tone-accent { background: var(--accent); }
.val-bar-fill.tone-pos { background: var(--pos); }
.val-bar-fill.tone-neg { background: var(--neg); }
.val-bar-fill.tone-muted { background: var(--muted); }
.val-bar-fill.tone-text { background: var(--text); }
.val-sens { display: grid; gap: 8px; }
/* table.wl's 640px floor is sized for the seven-column board; five short
   figure columns fit a half-width card without it. */
.val-sens table.wl.sens-matrix { min-width: 0; }
.val-sens .sens-corner { white-space: nowrap; text-transform: none; }
/* 14px side padding on six columns was the last 6px of overflow at half a
   card wide (measured: 483 of content in a 477 box at a 1320 window). */
.val-sens table.wl.sens-matrix th, .val-sens table.wl.sens-matrix td { padding-inline: 9px; }

/* --- streamed model thinking (cloud #53) -----------------------------------
   The "brief me" buttons show the model's reasoning live, in the chat's own
   `.chat-think` block with its blinking caret. What is new is the status
   line: three pulsing dots, the stage, the seconds elapsed and a Stop. The
   global reduced-motion rule already stops the dots; the static fallback
   below keeps them visible rather than blank. */

.val-interpret-head { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; }
.think { display: grid; gap: 8px; margin-top: 10px; }
.think-status {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px;
  border: 1px solid var(--accent-line); background: var(--accent-soft);
  border-radius: 10px; padding: 8px 10px; font-size: 0.8125rem; color: var(--text);
}
.think-label { flex: 1 1 12rem; min-width: 0; }
.think-meta { font-family: var(--font-mono); font-size: 0.71875rem; color: var(--muted); }
.think-stop { margin-left: auto; }
.think-dots { display: inline-flex; gap: 4px; flex: none; }
.think-dots i {
  width: 6px; height: 6px; border-radius: 50%; background: var(--accent);
  animation: think-dot 1.2s ease-in-out infinite;
}
.think-dots i:nth-child(2) { animation-delay: .15s; }
.think-dots i:nth-child(3) { animation-delay: .3s; }
@keyframes think-dot {
  0%, 80%, 100% { opacity: .25; transform: scale(.8); }
  40%           { opacity: 1;   transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .think-dots i { animation: none; opacity: .7; }
}
.think-log .chat-think {
  max-height: 16rem; overflow-y: auto; overscroll-behavior: contain;
}

/* --- track record at a glance (cloud #54) ----------------------------------
   Grey means "too early": every fill below takes its colour only when the
   backend calls the evidence sufficient, the same rule `.is-thin` applies to
   the tables. */

.record-glance { display: grid; gap: 16px; margin: 12px 0 16px; }
.record-horizons { gap: 6px; }
.record-glance-main {
  display: flex; flex-wrap: wrap; align-items: center; gap: 16px 28px;
}
.record-ring { flex: none; max-width: 100%; height: auto; }
.record-glance-text { flex: 1 1 18rem; min-width: 0; display: grid; gap: 10px; }
.record-sentence { font-size: 0.9375rem; line-height: 1.6; color: var(--text); max-width: 60ch; }
.record-caution { font-size: 0.8125rem; color: var(--muted); line-height: 1.55; }

.record-outcome { display: grid; gap: 6px; }
.record-outcome-bar {
  display: flex; height: 12px; border-radius: 999px; overflow: hidden;
  background: var(--border);
}
.record-outcome-bar > span { display: block; height: 100%; }
.record-outcome-legend {
  list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap;
  gap: 4px 14px; font-size: 0.75rem; color: var(--muted);
}
.record-outcome-legend strong { color: var(--text); }
.record-dot { display: inline-block; width: 9px; height: 9px; border-radius: 2px; margin-right: 5px; }
.record-outcome-bar .tone-pos, .record-dot.tone-pos { background: var(--pos); }
.record-outcome-bar .tone-neg, .record-dot.tone-neg { background: var(--neg); }
.record-outcome-bar .tone-muted, .record-dot.tone-muted { background: var(--muted); }
.record-outcome-bar .tone-border, .record-dot.tone-border { background: color-mix(in srgb, var(--muted) 45%, var(--border)); }
.record-outcome-bar .tone-track, .record-dot.tone-track { background: transparent; }
.record-dot.tone-track { border: 1px solid var(--muted); }

.record-sides { display: grid; gap: 16px; grid-template-columns: minmax(0, 1fr); }
@media (min-width: 760px) { .record-sides { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } }
.record-side { min-width: 0; display: grid; gap: 8px; align-content: start; }
.record-side-head { font-size: 0.84375rem; color: var(--muted); }
.record-side-head strong { color: var(--text); }

.rate-bars { display: grid; gap: 8px; min-width: 0; }
.rate-bars ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.rate-bars li {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 3px 10px;
  font-size: 0.8125rem;
}
.rate-label { min-width: 0; }
.rate-value { font-family: var(--font-mono); font-size: 0.78125rem; text-align: right; }
.rate-track {
  grid-column: 1 / -1; position: relative; height: 10px; border-radius: 999px;
  background: var(--border);
}
.rate-fill { display: block; height: 100%; border-radius: 999px; }
.rate-fill.tone-pos { background: var(--pos); }
.rate-fill.tone-neg { background: var(--neg); }
.rate-fill.tone-muted { background: var(--muted); }
.rate-mid {
  position: absolute; left: 50%; top: -3px; bottom: -3px; width: 2px;
  margin-left: -1px; background: var(--text); border-radius: 1px;
}
.rate-track-empty {
  height: auto; background: transparent; border: 1px dashed var(--border);
  padding: 3px 8px;
}
.rate-empty { font-size: 0.71875rem; color: var(--muted); }
.rate-n { grid-column: 1 / -1; font-size: 0.6875rem; color: var(--muted); }
.rate-bars .is-thin .rate-label, .rate-bars .is-thin .rate-value { color: var(--muted); }
.rate-axis {
  display: flex; justify-content: space-between; font-size: 0.65625rem;
  color: var(--muted); font-family: var(--font-mono);
}
/* The 95% range (cloud #73), as a whisker across the track: a thin line with
   end caps, in the text colour so it reads over the fill and the empty track
   alike. Drawn above the 50% line, since where the two cross is the point. */
.rate-range {
  position: absolute; top: 50%; height: 2px; margin-top: -1px;
  background: var(--text); opacity: .7; pointer-events: none;
}
.rate-range::before, .rate-range::after {
  content: ""; position: absolute; top: -4px; width: 2px; height: 10px;
  background: var(--text);
}
.rate-range::before { left: 0; }
.rate-range::after { right: 0; }
.rate-key { margin: 0; font-size: 0.6875rem; color: var(--muted); line-height: 1.5; }
.rate-key-whisker {
  display: inline-block; position: relative; width: 22px; height: 2px;
  margin-right: 4px; vertical-align: middle; background: var(--text); opacity: .7;
}
.rate-key-whisker::before, .rate-key-whisker::after {
  content: ""; position: absolute; top: -4px; width: 2px; height: 10px;
  background: var(--text);
}
.rate-key-whisker::before { left: 0; }
.rate-key-whisker::after { right: 0; }
/* A table cell's range, on its own line so the column stays header-width. */
.record-range { display: block; font-size: 0.65625rem; color: var(--muted); }
.record-breakdown { margin-bottom: 8px; }

/* --- Why this score (components/ThesisEvidence.tsx, cloud #74) -------------
   `evid-`, not `why-`: the landing page already owns `.why-body`, and its
   `.why-body p` rule restyled every paragraph here.
   Sits in the ticker page's narrow rail, so every row is a two-column grid
   that wraps its reading onto its own line rather than squeezing it. Each bar
   is drawn OUT FROM THE MIDDLE: right of centre is for the call, left of it
   against, the length how strongly. No total is drawn anywhere, on purpose:
   a composite beside the conviction score would compete to be "the score". */
.evid-body { display: grid; gap: 10px; min-width: 0; }
.evid-body .metric-label { margin: 6px 0 0; }
.evid-lede { margin: 0; font-size: 0.84375rem; line-height: 1.55; color: var(--text); }
.evid-note, .evid-text { margin: 0; font-size: 0.78125rem; line-height: 1.55; color: var(--muted); }
.evid-text { color: var(--text); }
.evid-text.is-thin { color: var(--muted); }
.evid-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }
.evid-row {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 3px 10px;
  font-size: 0.8125rem; min-width: 0;
}
.evid-label { min-width: 0; font-weight: 700; }
.evid-stance {
  font-family: var(--font-mono); font-size: 0.71875rem; color: var(--text);
  text-align: right;
}
.evid-reading { grid-column: 1 / -1; font-size: 0.75rem; color: var(--muted); min-width: 0; }
.evid-track {
  grid-column: 1 / -1; position: relative; height: 8px; border-radius: 999px;
  background: var(--border);
}
.evid-fill { position: absolute; top: 0; bottom: 0; border-radius: 999px; }
.evid-fill.tone-pos { background: var(--pos); }
.evid-fill.tone-neg { background: var(--neg); }
.evid-fill.tone-flat { background: var(--muted); }
.evid-mid {
  position: absolute; left: 50%; top: -3px; bottom: -3px; width: 2px;
  margin-left: -1px; background: var(--text); border-radius: 1px;
}
.evid-axis {
  display: flex; justify-content: space-between; font-size: 0.65625rem;
  color: var(--muted); font-family: var(--font-mono);
}
.evid-missing { margin: 0; padding-left: 18px; font-size: 0.78125rem; line-height: 1.55; color: var(--text); }
.record-breakdown[open] > .record-lede { margin-bottom: 14px; }

/* --- Start here (components/StarterQuestionnaire.tsx, cloud #70) ----------
   Scoped under `.starter`, for `.onboard`'s reason: the marketing `.card p`
   rule would otherwise restyle the copy. Four interests read as a 2 x 2 grid
   on a wide screen rather than 3 + 1. The review rows are labels, so the
   whole row toggles its checkbox, and they wrap rather than truncate: a
   company's name is the thing being chosen. */
.starter .panel-title:focus { outline: none; }
@media (min-width: 640px) {
  .starter .starter-choices { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.starter .starter-chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 4px 0 18px;
                          padding: 0; list-style: none; }
.starter .starter-chips .chip { cursor: pointer; font-size: 0.78125rem; }
.starter .starter-own .chip { cursor: default; gap: 6px; min-width: 0; overflow-wrap: anywhere; }
.starter .starter-remove { border: 0; background: transparent; color: inherit; cursor: pointer;
                           font-size: 1rem; line-height: 1; padding: 0 2px; }
.starter .starter-search { max-width: 420px; margin-bottom: 12px; }
.starter .starter-count { color: var(--muted); font-size: 0.8125rem; margin: 0 0 8px; }
.starter .starter-list { list-style: none; margin: 0 0 14px; padding: 0; }
.starter .starter-row { border-top: 1px solid var(--border); }
.starter .starter-row:first-child { border-top: 0; }
.starter .starter-row label { display: grid; grid-template-columns: 20px minmax(0, 1fr);
                              gap: 12px; align-items: start; padding: 12px 0; cursor: pointer; }
.starter .starter-row input { margin-top: 3px; width: 16px; height: 16px; accent-color: var(--accent); }
.starter .starter-text { display: grid; gap: 3px; min-width: 0; }
.starter .starter-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; }
.starter .starter-sym { font-family: var(--font-mono); font-size: 0.875rem; color: var(--text); }
.starter .starter-name { color: var(--text); font-size: 0.875rem; overflow-wrap: anywhere; }
.starter .starter-why { color: var(--muted); font-size: 0.8125rem; }
.starter .starter-group { color: var(--muted); font-family: var(--font-mono); font-size: 0.65625rem;
                          text-transform: uppercase; letter-spacing: .08em; }
.starter .starter-scan { display: grid; grid-template-columns: 20px minmax(0, 1fr); gap: 12px;
                         align-items: start; margin: 0 0 16px; color: var(--muted); font-size: 0.8125rem;
                         cursor: pointer; }
.starter .starter-scan input { margin-top: 2px; width: 16px; height: 16px; accent-color: var(--accent); }
.starter .starter-nav { margin-top: 4px; }

/* Getting started's step can now offer two ways in (cloud #70). */
.onboard .onboard-cta { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; }
@media (max-width: 560px) { .onboard .onboard-cta { justify-content: flex-start; } }
/* An empty watchlist: the board's one empty state that asks for an action. */
.board-empty { padding: 22px 4px 8px; display: grid; gap: 8px; justify-items: start; }
.board-card .board-empty .board-empty-title { color: var(--text); font-size: 0.9375rem; font-weight: 700; margin: 0; }
.board-card .board-empty .board-empty-text { color: var(--muted); font-size: 0.84375rem; margin: 0 0 6px; max-width: 60ch; }
.field-hint a { color: var(--accent-ink-soft); text-decoration: underline; text-underline-offset: 3px; }

/* --- Overview heading and captions (cloud #71) ---------------------------
   Jump links are the board's chip, as links. No scroll-margin on the
   sections: `html` already carries scroll-padding-top for the sticky header,
   and adding one doubled it — measured, a jumped-to title landed at 168px
   under a 63px header. */
.overview-head .page-sub a { color: var(--accent-ink-soft); }
.jump-links { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.jump-links .chip { font-size: 0.75rem; text-decoration: none; }
.jump-links .chip:hover { color: var(--text); border-color: var(--accent-line); }
.caption-more { margin: -4px 0 14px; font-size: 0.8125rem; color: var(--muted); }
.caption-more summary { cursor: pointer; color: var(--accent-ink-soft); font-weight: 600;
                        list-style: none; display: inline-block; }
.caption-more summary::-webkit-details-marker { display: none; }
.caption-more summary::before { content: "▸ "; }
.caption-more[open] summary::before { content: "▾ "; }
.caption-more summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
.caption-more p { margin: 6px 0 0; max-width: 72ch; }

} /* @layer app */
