/* ═══════════ SYSTEM STATUS ═══════════ */
.system-status {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  min-width: 70px;
  border: none;
  background: none;
  font-family: var(--font-mono);
}

.system-status.clickable {
  cursor: pointer;
  padding: 8px 12px;
  border-radius: var(--radius-sm);
  transition: background 0.15s;
}

.system-status.clickable:hover {
  background: var(--bg-panel);
}

.system-status.clickable:hover .system-status-name {
  color: var(--text-primary);
}

.system-status.clickable:hover .system-status-level {
  opacity: 1;
}

.system-status-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
}

.system-status-dot.green { background: var(--green); }
.system-status-dot.amber { background: var(--amber); }
.system-status-dot.yellow { background: var(--yellow); }
.system-status-dot.red { background: var(--red); }

.system-status-name {
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0.5px;
  color: var(--text-secondary);
}

.system-status-level {
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 1px;
  text-transform: uppercase;
  opacity: 0.65;
}

.system-status-level.green { color: var(--green); }
.system-status-level.amber { color: var(--amber); }
.system-status-level.yellow { color: var(--yellow); }
.system-status-level.red { color: var(--red); }

/* ═══════════ STATUS TAGS ═══════════ */
.tag {
  font-size: 12px;
  padding: 2px 6px;
  border-radius: 4px;
  font-weight: 600;
  letter-spacing: 0.5px;
  display: inline-block;
  font-variant-numeric: tabular-nums;
}

.tag-high { color: var(--amber); background: var(--amber-bg); }
.tag-critical { color: var(--red); background: var(--red-bg); }
.tag-borderline { color: var(--yellow); background: var(--yellow-bg); }
.tag-nominal { color: var(--green); background: var(--green-bg); }
.tag-low { color: var(--text-dim); background: none; font-weight: 500; }
.tag-mod { color: var(--text-dim); background: none; font-weight: 500; }
.tag-elevated { color: var(--amber); background: var(--amber-bg); }

/* ═══════════ BIOMARKER ROWS ═══════════ */
.biomarker-row {
  display: grid;
  grid-template-columns: 140px 90px 1fr 60px auto;
  align-items: center;
  gap: var(--space-sm);
  padding: 6px 0;
  border-bottom: 1px solid var(--border-subtle);
}

.biomarker-row:last-child {
  border-bottom: none;
}

.biomarker-label {
  font-size: 12px;
  color: var(--text-secondary);
}

.biomarker-value {
  font-size: 13px;
  font-weight: 500;
  color: var(--text-primary);
}

.biomarker-unit {
  font-size: 13px;
  color: var(--text-dim);
  font-weight: 400;
}

.biomarker-detail {
  font-size: 13px;
  color: var(--text-dim);
}

/* ═══════════ RANGE BAR ═══════════ */
.range-bar {
  position: relative;
  height: 8px;
  background: var(--border);
  border-radius: 4px;
  min-width: 120px;
}

.range-bar-normal {
  position: absolute;
  height: 100%;
  background: rgba(5, 150, 105, 0.25);
  border-radius: 4px;
}

.range-bar-marker {
  position: absolute;
  top: -4px;
  width: 4px;
  height: 16px;
  border-radius: 2px;
  transform: translateX(-2px);
  box-shadow: 0 0 3px rgba(0,0,0,0.15);
}

.range-bar-marker.nominal { background: var(--green); }
.range-bar-marker.borderline { background: var(--yellow); }
.range-bar-marker.high { background: var(--amber); }
.range-bar-marker.critical { background: var(--red); }

.range-bar-ref {
  font-size: 12px;
  color: var(--text-secondary);
  white-space: nowrap;
}

/* ═══════════ SCORECARD ═══════════ */
.scorecard-summary {
  padding: var(--space-sm) 0;
  margin-bottom: var(--space-md);
  font-size: 13px;
}

.scorecard-count {
  font-weight: 600;
  letter-spacing: 0.5px;
  color: var(--text-secondary);
}

.scorecard-all-clear .scorecard-count {
  color: var(--green);
}

.scorecard-domain {
  margin-bottom: var(--space-xl);
}

.scorecard-row {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding: 14px 0;
  border-bottom: 1px solid var(--border-subtle);
  font-size: 12px;
  flex-wrap: wrap;
}

.scorecard-row:last-child {
  border-bottom: none;
}

/* First row in a domain sits flush against the header — keeps title +
   first marker visually tied as one block. */
.scorecard-domain .scorecard-item:first-of-type .scorecard-row {
  padding-top: 6px;
}

.scorecard-row.nominal {
  opacity: 0.7;
}

.scorecard-label {
  font-size: 12px;
  color: var(--text-secondary);
  min-width: 130px;
}

.scorecard-value {
  font-size: 13px;
  font-weight: 500;
  color: var(--text-primary);
  min-width: 80px;
}

.scorecard-spark {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-width: 64px;
}

.scorecard-spark svg {
  display: block;
}

.scorecard-spark-empty {
  color: var(--text-dim);
  font-size: 12px;
  opacity: 0.4;
  justify-content: center;
}

.spark-dir {
  font-size: 11px;
  font-weight: 600;
}

.scorecard-nominal-tag {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--green);
  opacity: 0.6;
}

/* Nominal items hidden by default, shown with .show-nominal on container */
.scorecard-item-nominal {
  display: none;
}

.show-nominal .scorecard-item-nominal {
  display: block;
}

.scorecard-domain-nominal-only {
  display: none;
}

.show-nominal .scorecard-domain-nominal-only {
  display: grid;
}

/* "Show all" / "Flagged only" button in the overall bar */
.scorecard-show-all {
  margin-left: auto;
  border: none;
  background: var(--bg-surface);
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-dim);
  cursor: pointer;
  padding: 4px 12px;
  transition: all 0.15s;
}

.scorecard-show-all:hover {
  background: var(--bg-hover);
  color: var(--text-secondary);
}

.show-nominal .scorecard-show-all {
  color: var(--accent);
  background: var(--accent-soft);
}

/* Segmented Flagged ↔ All toggle in the overall bar. Mirrors the look of the
   Scorecard/Analysis mode toggle so the page has one consistent toggle idiom. */
.scorecard-mode-toggle {
  margin-left: auto;
  display: inline-flex;
  background: var(--bg-surface);
  border-radius: var(--radius-sm);
  padding: 2px;
  gap: 2px;
}

.scorecard-mode-toggle .mode-seg {
  border: none;
  background: transparent;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.5px;
  color: var(--text-dim);
  cursor: pointer;
  padding: 4px 10px;
  border-radius: 3px;
  transition: all 0.15s;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.scorecard-mode-toggle .mode-seg:hover {
  color: var(--text-secondary);
}

.scorecard-mode-toggle .mode-seg.active {
  background: var(--bg-panel);
  color: var(--text-primary);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
}

.scorecard-mode-toggle .mode-seg-count {
  font-weight: 400;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}

.scorecard-mode-toggle .mode-seg.active .mode-seg-count {
  color: var(--text-secondary);
}

/* ═══════════ GOOD NEWS GRID ═══════════ */
.good-news-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-xs) var(--space-xl);
}

.good-news-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 6px 0;
  font-size: 12px;
}

.good-news-label {
  color: var(--text-secondary);
}

.good-news-value {
  color: var(--text-primary);
  font-weight: 500;
}

/* ═══════════ LIVE VITALS (bloodwork tab) ═══════════ */
.vitals-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-md);
}

.vital-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.vital-card-header {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
}

.vital-card-label {
  font-size: 13px;
  color: var(--text-dim);
  letter-spacing: 1px;
  font-weight: 600;
}

.vital-card-value {
  font-size: 18px;
  font-weight: 400;
  color: var(--text-primary);
}

.vital-card-unit {
  font-size: 12px;
  color: var(--text-dim);
}

.vital-spark {
  width: 100%;
  height: 32px;
}

/* ═══════════ PROFILE ═══════════ */
.profile-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 var(--space-2xl);
}

.profile-row {
  display: flex;
  justify-content: space-between;
  padding: 6px 0;
  font-size: 12px;
  line-height: 1.6;
}

.profile-label {
  color: var(--text-secondary);
}

.profile-value {
  color: var(--text-primary);
  font-weight: 500;
  text-align: right;
}

.profile-value.positive { color: var(--green); }
.profile-value.negative { color: var(--amber); }
.profile-value.accent { color: var(--accent); font-weight: 600; }

/* ═══════════ GENETIC MARKERS ═══════════ */
.genetic-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--space-sm) 0;
  border-bottom: 1px solid var(--border-subtle);
  font-size: 12px;
}

.genetic-row:last-child { border-bottom: none; }

.genetic-marker { color: var(--text-secondary); }
.genetic-value { font-weight: 500; }
.genetic-value.confirmed { color: var(--red); }
.genetic-value.unknown { color: var(--text-dim); }
.genetic-value.probable { color: var(--amber); }
.genetic-value.investigating { color: var(--yellow); }

.genetic-tag {
  font-size: 12px;
  opacity: 0.6;
  margin-left: 4px;
}

/* ═══════════ BASELINE SURVIVAL SECTION ═══════════ */
.baseline-survival-section {
  margin-top: var(--space-xl);
}

.baseline-empty-soft {
  font-size: 11px;
  letter-spacing: 0.4px;
  color: var(--text-dim);
  padding: var(--space-sm) 0;
}

/* ═══════════ SURVIVAL CHART ═══════════ */
.survival-container {
  height: 220px;
  padding: var(--space-sm) 0;
}

/* ═══════════ PLAN ACTION CARDS ═══════════
   One card chassis used for both hero (ranks 1..3) and backlog (4..N).
   Identical chrome end-to-end so the live plan reads as one ranked
   Pareto. Lift comes from a panel-white background, a real border, and
   a hairline shadow — they need to stand off the page-grey backdrop. */
.view-narrow {
  max-width: 880px;
}

.plan-cards {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  margin-bottom: var(--space-xl);
}

/* Outer chassis: border, background, shadow. The flex layout that used
   to live here moves down to .plan-card-summary / .plan-card-flat so the
   card can be a <details> wrapping a summary row (the expandable variant)
   without nested flex on the details element itself.

   Progressive disclosure: hero cards default collapsed and reveal the
   "briefing" surface (why · target pills · refs) on click. The "picker"
   surface (rank · priority · threats · title · +Xy · Done/Dismiss) stays
   visible at rest, so a user can commit to an action without opening
   the card. */
.plan-card {
  background: var(--bg-panel);
  border-radius: var(--radius-md);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-card);
  transition: border-color 0.15s, box-shadow 0.15s;
}

.plan-card:hover {
  border-color: var(--border);
  box-shadow: var(--shadow-card-hover);
}

/* Layout chrome shared by the expandable summary and the flat (non-
   expanding) variant. Same flex skeleton end to end so the eye reads
   them as the same kind of card regardless of whether there's a
   chevron. align-items:flex-start so a wrapped title doesn't stretch
   the rank gutter; the rank sits at the cap-height of the first line. */
.plan-card-flat,
.plan-card-summary {
  display: flex;
  align-items: flex-start;
  gap: var(--space-lg);
  padding: var(--space-lg) var(--space-xl);
}

.plan-card-summary {
  list-style: none;
  cursor: pointer;
}
.plan-card-summary::-webkit-details-marker { display: none; }
.plan-card-summary::marker { content: ''; }

/* Controls row inside the summary — Done / Dismiss sits below the title
   alongside the Show-details toggle. Sibling layout: Done/Dismiss left,
   toggle right (margin-left:auto). Done/Dismiss preventDefault their
   click so the card doesn't flip open when committing; the toggle does
   NOT preventDefault — its click is meant to bubble to <summary> and
   fire the native disclosure. */
.plan-card-controls-row {
  margin-top: 6px;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 14px;
}

/* The default .action-controls margin-top is meant for a stacked layout
   (when controls sit directly under target pills on the Risks side).
   Inside .plan-card-controls-row they're a flex child centered with the
   toggle, so the extra top margin would offset their vertical centering. */
.plan-card-controls-row > .action-controls {
  margin-top: 0;
}

/* Show / Hide details toggle. Text-button label for the native <summary>
   click — pointer-events:none so clicks bubble to <summary>, tabindex=-1
   so keyboard nav stays on <summary>. The button is purely a visual cue
   that says "click to reveal more." Right-aligned via margin-left:auto
   so it pins to the actual card edge inside the controls row. */
.plan-card-details-toggle {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: inherit;
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0.3px;
  color: var(--text-dim);
  background: none;
  border: 0;
  padding: 4px 0;
  cursor: pointer;
  pointer-events: none;
  transition: color 0.15s;
}

/* Hover affordance lives on the whole card, not the toggle alone —
   the entire summary is the click target. */
.plan-card:hover .plan-card-details-toggle {
  color: var(--text-secondary);
}

.plan-card-details-toggle-label {
  display: inline-flex;
}

/* Both label states render in DOM; CSS swaps visibility off the
   parent <details>'s [open] state. Removes the stale-label bug where
   the toggle says "Show details" while the card is already open. */
.plan-card-details-toggle-label > [data-state] { display: none; }
.plan-card:not([open]) .plan-card-details-toggle-label > [data-state="collapsed"] { display: inline; }
.plan-card[open] .plan-card-details-toggle-label > [data-state="expanded"] { display: inline; }

.plan-card-details-toggle-chevron {
  font-size: 13px;
  line-height: 1;
  color: inherit;
  transition: transform 0.15s;
  display: inline-block;
}

.plan-card[open] .plan-card-details-toggle-chevron {
  transform: rotate(180deg);
}

/* Expanded panel — appears below the summary when the card is open.
   Indented past the rank gutter (24px + space-lg gap) so the why and
   pills align with the action title's left edge. No divider above —
   the disclosure animation + the "Hide details ▴" toggle in the
   summary are enough visual cue that the panel belongs to the card
   above; a border just turned one card into two.

   Grid layout uses the card's horizontal real estate: the why prose
   spans the full width (capped at 70ch for readability), and pills /
   refs share the row below — pills anchored left, refs flush right.
   On narrow viewports refs wrap below pills (grid row 3). */
.plan-card-details {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  padding: var(--space-xs) var(--space-xl) var(--space-md)
           calc(var(--space-xl) + 24px + var(--space-lg));
  animation: expand-in 0.15s ease-out;
}

.plan-card-details > .action-why {
  font-size: 11.5px;
  color: var(--text-dim);
  line-height: 1.6;
  max-width: 70ch;
  margin: 0;
}

.plan-card-rank {
  font-size: 18px;
  font-weight: 300;
  color: var(--text-dim);
  min-width: 24px;
  line-height: 1.4;
  font-variant-numeric: tabular-nums;
  padding-top: 2px;
}

/* Body is a column inside the rank-gutter row. Three stacked sections:
   title-line (the JTBD scan: title + impact), meta-line (priority ·
   threats — secondary categorization in one phrase), controls-row
   (Done/Dismiss + Show details). Same vertical structure as a backlog
   row, just with a bigger title and visible commit controls. */
.plan-card-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* Primary line: title on the left, +Xy on the right (baseline-aligned).
   The +Xy was a right-rail block in the previous design; pulling it
   onto the title line tightens the JTBD scan (what to do · how much it
   matters) into one horizontal sweep, and frees the controls row to
   anchor "Show details" at the actual card edge instead of floating
   inside an artificial body-width column. */
.plan-card-title-line {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
}

.plan-card-title {
  flex: 1;
  min-width: 0;
  font-size: 15px;
  font-weight: 500;
  color: var(--text-primary);
  line-height: 1.45;
}

.plan-card-impact {
  font-size: 22px;
  font-weight: 400;
  color: var(--leverage);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.4px;
  line-height: 1.1;
  flex-shrink: 0;
  cursor: help;
}

/* Meta line: priority · threats — lever-subtitle. Same idiom as the
   backlog row's meta-line so hero and backlog read as the same kind
   of thing, just at different scales. All small-caps dim, priority
   keeps its semantic warm color but at the same size/weight as the
   threats so the row reads as one phrase rather than a colored chip
   stacked next to neutral chips. */
.plan-card-meta-line {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 1.1px;
  text-transform: uppercase;
  line-height: 1.4;
  color: var(--text-dim);
  flex-wrap: wrap;
}

/* Priority is a categorical cue, not an alarm. SOON keeps a warm tint;
   Doctor/Rx/Ongoing inherit neutral meta text so plan rows do not turn
   clinical follow-up into red emergency language.

   Subtle chip background distinguishes priority (a label for the row)
   from the threat tags after it (a list of what the action addresses).
   Without the chip, the middot-separated meta line reads as one phrase;
   the chip turns priority into a leading token + tag-list grammar. */
.plan-card-priority {
  line-height: 1.4;
  background: var(--bg-surface);
  padding: 1.5px 6px;
  border-radius: 3px;
  margin-right: 6px;
}
.plan-card-priority-urgent { color: var(--text-secondary); }
.plan-card-priority-soon { color: var(--amber); }
/* Suppress the first inter-tag middot — the chip itself is the
   separator between priority and the threat list. */
.plan-card-priority + .plan-card-meta-sep { display: none; }

.plan-card-meta-sep {
  color: var(--border);
  font-weight: 400;
}

/* "+N" threat-overflow badge. Hidden on desktop, where every threat tag
   is shown inline; the mobile media query swaps the extra tags out for
   this badge so the meta line stays one row. Shared by hero cards
   (.plan-card-threat-more) and backlog rows (.plan-row-threat-more). */
.plan-card-threat-more,
.plan-row-threat-more {
  display: none;
  color: var(--text-dim);
  letter-spacing: 0.4px;
}

/* Lever-name subtitle ("APOB · LP(A)") sits at the right end of the
   meta line, pushed there by margin-left:auto. Anchors the +Xy above
   to the variable it moves so two cards citing the same lever read
   as alternatives, not additive wins. */
.plan-card-lever {
  margin-left: auto;
  color: var(--text-dim);
  white-space: nowrap;
}

/* Investigation/diagnostic actions don't move a measured target, so the
   subtitle reads "expected" — italicized to mark it as probabilistic
   rather than a lever name. */
.plan-card-lever-expected {
  font-style: italic;
  letter-spacing: 0.8px;
  text-transform: lowercase;
  font-weight: 500;
}

/* Pills + refs in one flex-wrap row, shared across hero / backlog /
   secured / dismissed details panels. Pills flex-grow on the left; refs
   trail with margin-left:auto so they hug the right edge whether they
   share the line with pills or wrap below when pills get tall (multi-pill
   stacks or a wide binary pill). All four panels are flex-column with gap,
   so the meta-row inherits its top spacing for free. */
.action-meta-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px 16px;
}

.action-meta-row > .target-pills {
  flex: 1 1 auto;
  min-width: 0;
  margin-top: 0;
}


/* ═══════════ PLAN HORIZON ═══════════
   Three nodes anchor the line: BASELINE (demographic prior, far left),
   TODAY (your actual current projection, sitting at the banked/open
   handoff), and CEILING (reachable end-age, far right). Solid track
   behind TODAY = ground walked from baseline by being you; dashed
   track ahead = open opportunity. The headline +Xy is the unambiguous
   answer; ages on TODAY and CEILING anchor the journey. */
.plan-horizon { margin-bottom: var(--space-xl); }

/* Stacked-left header with three layers of hierarchy: the eyebrow
   row pairs the metric label with an optional threat tag (capsule
   chip styled like the per-card .plan-threat-tag); the hero number
   stands alone; the anchor sits beneath when filtered. Different
   typographic weights at each level so it reads as structure, not
   a run-on phrase. */
.plan-horizon-head {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: var(--space-lg);
}

.plan-horizon-head-main {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  min-width: 0;
}

/* Composition preview — concentrated-vs-diffuse at a glance; the one fact
   the ranked action list below can't show. Cool-tone donut, empty center,
   no legend. Radial (not a second horizontal bar) so it never reads as the
   age-runway rail below it. The whole thing is the drill into the by-goal
   sheet; the track ring grounds the arcs when the split is lopsided. */
.plan-horizon-mix {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  line-height: 0;
  transition: transform 120ms ease;
}

.plan-horizon-mix:hover,
.plan-horizon-mix:focus-visible {
  transform: scale(1.04);
  outline: none;
}

/* Caption — without it the ring reads as decoration. Same treatment as
   the section/breakdown labels so it parses as "this chart IS the by-goal
   split", and carries the ▾ drill affordance the headline number shed. */
.plan-horizon-mix-label {
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 500;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--text-dim);
  opacity: 0.85;
  line-height: 1;
}

.plan-horizon-mix:hover .plan-horizon-mix-label,
.plan-horizon-mix:focus-visible .plan-horizon-mix-label {
  color: var(--leverage);
}

.plan-horizon-mix-donut {
  width: 52px;
  height: 52px;
  transform: rotate(0deg);
}

.plan-horizon-mix-track {
  fill: none;
  stroke: var(--border-subtle);
  stroke-width: 7;
}

.plan-horizon-mix-slice {
  fill: none;
  stroke-width: 7;
  stroke-linecap: butt;
}

.plan-horizon-eyebrow {
  display: flex;
  align-items: center;
  gap: 10px;
}

/* Threat tag in the header — same vocabulary as `.plan-threat-tag` on
   action cards so the user reads it as the same noun. Filled rather
   than ghosted because here it's a confirmation of active scope, not
   a clickable affordance. The placeholder variant reserves the eyebrow
   row's vertical slot when unfiltered so the hero below doesn't jump
   when a threat is selected. */
.plan-horizon-scope-tag {
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  padding: 3px 8px;
  background: var(--accent-soft);
  border: 1px solid var(--accent);
  border-radius: var(--radius-sm);
  color: var(--accent);
}

.plan-horizon-scope-tag-placeholder {
  visibility: hidden;
}

.plan-horizon-scope {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--text-secondary);
}

/* Subtitle anchoring a filtered headline to the total. Quiet
   typographic treatment — a context cue, not a peer of the headline
   number. The placeholder variant reserves the row's vertical slot
   when no filter is active so toggling the filter doesn't shift the
   timeline below. */
.plan-horizon-anchor {
  font-size: 10px;
  letter-spacing: 0.6px;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}

.plan-horizon-anchor-placeholder {
  visibility: hidden;
}

/* The hero. Single number that answers "what's my opportunity?" — so
   sized to dominate. Anything else competing in the headline area
   makes the user wonder which figure to anchor on. */
.plan-horizon-total {
  font-size: 28px;
  font-weight: 500;
  color: var(--leverage);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.4px;
  line-height: 1;
}

.plan-horizon-total-action {
  display: inline;
  width: fit-content;
  padding: 0;
  border: 0;
  background: none;
  font-family: var(--font-mono);
  text-align: left;
  cursor: pointer;
}

.plan-horizon-total-action:hover,
.plan-horizon-total-action:focus-visible {
  text-shadow: 0 0 18px var(--leverage-bg);
  outline: none;
}

/* Track inset so the ceiling node (translate -50% past left:100%)
   has room for its age label without overflowing the content box.
   Height accommodates three vertical bands stacked around top:50%:
   ages above (22px), rail+markers at center (16px), then names and
   the banked-segment caption sharing the bottom band (22px) — pulled
   tight so the caption reads as part of the horizon rather than a
   detached footnote. */
.plan-horizon-track {
  position: relative;
  height: 82px;
  margin: 0 var(--space-xl);
}

/* One axis, two textures, two colors. Banked and open sit on the same
   horizontal line with no gap between them. Texture (solid vs dashed)
   conveys proportion at a glance; color reinforces the semantic — green
   for ground already held, cyan for leverage still open. Both halves
   muted so the line reads as armature, not data. */
.plan-horizon-rail {
  position: absolute;
  top: 50%;
  height: 2px;
  transform: translateY(-50%);
  z-index: 1;
  border-radius: 1px;
}

.plan-horizon-rail-banked {
  background: var(--green);
  opacity: 0.55;
}

.plan-horizon-rail-open {
  background: repeating-linear-gradient(
    to right,
    var(--leverage) 0,
    var(--leverage) 7px,
    transparent 7px,
    transparent 13px
  );
}

.plan-horizon-rail-action {
  position: absolute;
  top: 50%;
  height: 24px;
  transform: translateY(-50%);
  z-index: 2;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
}

.plan-horizon-rail-action:hover ~ .plan-horizon-node-ceiling .plan-horizon-marker-ceiling,
.plan-horizon-rail-action:focus-visible ~ .plan-horizon-node-ceiling .plan-horizon-marker-ceiling {
  box-shadow: 0 0 0 4px var(--leverage-bg), 0 0 0 9px rgba(0, 153, 179, 0.08);
}

.plan-horizon-rail-action:focus-visible {
  outline: none;
}

.plan-horizon-track:has(.plan-horizon-rail-action:hover) .plan-horizon-rail-open,
.plan-horizon-track:has(.plan-horizon-rail-action:focus-visible) .plan-horizon-rail-open {
  filter: drop-shadow(0 0 6px var(--leverage-bg));
}

/* Scope highlight — solid leverage-cyan band shown when a threat is
   filtered. Anchored at the banked/open handoff, extends right into
   the dashed open region by scope-open / total-reachable, so its
   width visually equals the headline +Xy. Sits above the banked +
   open rails (z-index 2); the bg-page outline gives it a crisp lift
   off the line. Read as: "starting from where you already are,
   solving this threat gives you this much more." */
.plan-horizon-rail-scope {
  position: absolute;
  top: 50%;
  height: 5px;
  transform: translateY(-50%);
  z-index: 2;
  background: var(--leverage);
  border-radius: 2.5px;
  box-shadow: 0 0 0 1px var(--bg-page);
  pointer-events: none;
}

/* Node = vertical stack centered on the line. Translated -50% so the
   marker sits exactly at (left%, 50%) on the track. Three-row grid
   keeps BASELINE, TODAY, and CEILING vertically aligned. BASELINE's
   top cell is an empty spacer (no age); TODAY and CEILING carry an
   age above the marker. */
.plan-horizon-node {
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
  display: grid;
  grid-template-rows: 22px 16px 22px;
  align-items: center;
  justify-items: center;
  row-gap: 4px;
  z-index: 3;
  pointer-events: none;
}

.plan-horizon-node-action {
  border: 0;
  background: none;
  padding: 0;
  font-family: var(--font-mono);
  cursor: pointer;
  pointer-events: auto;
}

.plan-horizon-node-action:hover .plan-horizon-marker-ceiling,
.plan-horizon-node-action:focus-visible .plan-horizon-marker-ceiling {
  box-shadow: 0 0 0 4px var(--leverage-bg), 0 0 0 9px rgba(0, 153, 179, 0.08);
}

.plan-horizon-node-action:focus-visible {
  outline: none;
}

/* End-age readout above a node's marker. Tabular and visible but
   not competing with the +Xy hero — text-secondary, not leverage
   cyan, so it reads as data rather than a second headline. Used by
   BASELINE, TODAY, and CEILING. */
.plan-horizon-age {
  font-size: 14px;
  color: var(--text-secondary);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.1px;
  align-self: end;
}

.plan-horizon-name {
  font-size: 10px;
  color: var(--text-dim);
  letter-spacing: 1.2px;
  text-transform: uppercase;
  align-self: start;
}

/* TODAY and CEILING names share the same prominent treatment — both
   anchor the user's mental model. BASELINE keeps the dim default so
   it reads as a quiet origin, not a peer. */
.plan-horizon-name-today,
.plan-horizon-name-ceiling {
  color: var(--text-secondary);
  font-weight: 600;
  letter-spacing: 1.4px;
}

/* Three marker treatments, three roles:
     BASELINE = small dim pip — demographic prior, quiet origin. It's a
                hypothetical reference ("where you'd be with no
                behaviors"), not a banked position, so it stays
                structural rather than green.
     TODAY    = filled green dot, lifted off the rail with a bg-page
                outline so the banked/open texture handoff doesn't
                visually swallow it. Sits at the apex of the banked
                segment — semantically "where you currently are," which
                IS the result of everything you've already banked.
                Green color reinforces that.
     CEILING  = leverage-cyan ring with halo — the reachable end-age,
                the visual terminus the dashed open segment resolves
                toward. Echoes the +Xy hero number upstairs. */
.plan-horizon-marker {
  border-radius: 50%;
  z-index: 4;
}

.plan-horizon-marker-baseline {
  width: 7px;
  height: 7px;
  background: var(--text-dim);
}

.plan-horizon-marker-today {
  width: 11px;
  height: 11px;
  background: var(--green);
  box-shadow: 0 0 0 2.5px var(--bg-page);
}

.plan-horizon-marker-ceiling {
  width: 14px;
  height: 14px;
  background: var(--bg-page);
  border: 2.5px solid var(--leverage);
  box-sizing: border-box;
  box-shadow: 0 0 0 4px var(--leverage-bg);
}

/* Segment value labels — anchored under the line, centered on each
   span. BANKED sits under the solid segment (always reflects total
   banked, invariant under the filter). OPEN rides with scope: under
   the full dashed segment when unfiltered, sliding under the cyan
   band when filtered, where its value matches the headline +Xy. The
   number gets text-secondary (data weight); the trailing word gets
   text-dim (label weight) — same hierarchy as a tagged readout. */
.plan-horizon-seg {
  position: absolute;
  top: 60px;
  transform: translateX(-50%);
  display: flex;
  gap: 4px;
  align-items: baseline;
  white-space: nowrap;
  pointer-events: none;
  z-index: 3;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.4px;
}

.plan-horizon-seg-num {
  color: var(--text-secondary);
  font-weight: 500;
}

.plan-horizon-seg-name {
  color: var(--text-dim);
}

/* Drag state — the biomarker standing pulls TODAY below the demographic
   baseline (a net lab drag). The today marker and the standing segment shift
   to the warm severity axis; the baseline rides ahead of today as a reference
   tick. No filled rail: the dashed reachable rail spans today → ceiling across
   it, so this is only a recolor, not new geometry. */
.plan-horizon-node-today-drag .plan-horizon-marker-today {
  background: var(--amber);
}

.plan-horizon-seg-drag .plan-horizon-seg-num {
  color: var(--amber);
}

.plan-horizon-empty {
  height: 82px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--text-dim);
}

/* ═══════════ PLAN GOAL SHEET ═══════════
   Reachable-years accounting lives behind the existing horizon
   affordance. The default Plan surface stays focused on action; this
   sheet is for inspection once the user asks "what makes up 4.6y?". */
.plan-goal-sheet-backdrop {
  position: fixed;
  inset: 0;
  z-index: 500;
  background: rgba(20, 26, 36, 0.08);
}

.plan-goal-sheet {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 501;
  width: min(680px, calc(100vw - 36px));
  padding: 34px max(34px, var(--safe-right)) max(28px, calc(28px + var(--safe-bottom))) max(34px, var(--safe-left));
  overflow: auto;
  border-left: 1px solid var(--border);
  background: var(--bg-panel);
  box-shadow: -18px 0 48px rgba(20, 26, 36, 0.12);
}

.plan-goal-sheet-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 18px;
  margin-bottom: 26px;
}

.plan-goal-sheet-title {
  margin: 0;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--text-secondary);
  line-height: 1.35;
}

.plan-goal-sheet-close {
  width: 28px;
  height: 28px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-page);
  color: var(--text-dim);
  font-family: var(--font-mono);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
}

.plan-goal-sheet-close:hover,
.plan-goal-sheet-close:focus-visible {
  color: var(--leverage);
  border-color: var(--leverage);
  outline: none;
}

.plan-goal-sheet-body {
  display: grid;
  grid-template-columns: 190px minmax(0, 1fr);
  gap: 30px;
  align-items: start;
}

.plan-goal-donut-wrap {
  position: sticky;
  top: 0;
  width: 176px;
  height: 176px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.plan-goal-donut {
  width: 176px;
  height: 176px;
  overflow: visible;
}

.plan-goal-donut-track {
  fill: none;
  stroke: var(--bg-surface);
  stroke-width: 16;
}

.plan-goal-donut-slice {
  fill: none;
  stroke-width: 16;
  stroke-linecap: butt;
  cursor: pointer;
  transition: stroke-width 0.15s ease, opacity 0.15s ease;
}

.plan-goal-donut-slice:hover,
.plan-goal-donut-slice:focus,
.plan-goal-donut-slice.is-active {
  stroke-width: 20;
  outline: none;
}

.plan-goal-sheet:has(.plan-goal-donut-slice.is-active) .plan-goal-donut-slice:not(.is-active),
.plan-goal-sheet:has(.plan-goal-disclosure.is-active) .plan-goal-donut-slice:not(.is-active) {
  opacity: 0.32;
}

.plan-goal-donut-center {
  position: absolute;
  inset: 44px;
  border-radius: 50%;
  background: var(--bg-panel);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  box-shadow: 0 0 0 1px var(--border-subtle);
  pointer-events: none;
}

.plan-goal-donut-num {
  font-size: 23px;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.3px;
  line-height: 1;
}

.plan-goal-donut-label {
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--text-dim);
}

.plan-goal-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.plan-goal-list-wrap {
  min-width: 0;
}

.plan-goal-row {
  min-width: 0;
}

.plan-goal-disclosure {
  min-width: 0;
}

.plan-goal-summary {
  display: grid;
  grid-template-columns: 8px minmax(0, 1fr);
  gap: 9px;
  align-items: start;
  min-width: 0;
  padding: 4px 0;
  border-radius: 4px;
  outline: none;
  cursor: pointer;
  transition: opacity 0.15s ease, background 0.15s ease;
}

.plan-goal-summary::-webkit-details-marker {
  display: none;
}

.plan-goal-summary::marker {
  content: "";
}

.plan-goal-disclosure:hover,
.plan-goal-disclosure:focus-within,
.plan-goal-disclosure.is-active {
  opacity: 1;
}

.plan-goal-list:has(.plan-goal-disclosure.is-active) .plan-goal-disclosure:not(.is-active) {
  opacity: 0.42;
}

.plan-goal-disclosure[open] .plan-goal-summary {
  margin: -4px -8px 0;
  padding: 8px;
  background: color-mix(in srgb, var(--leverage-bg) 22%, transparent);
}

.plan-goal-disclosure[open] .plan-goal-name {
  color: var(--text-primary);
}

.plan-goal-swatch {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-top: 5px;
}

.plan-goal-main {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}

.plan-goal-title-line {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto 14px;
  align-items: baseline;
  gap: 9px;
}

.plan-goal-name {
  min-width: 0;
  font-size: 12px;
  font-weight: 500;
  color: var(--text-primary);
  line-height: 1.35;
}

.plan-goal-years {
  flex-shrink: 0;
  font-size: 13px;
  color: var(--leverage);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.2px;
}

.plan-goal-chevron {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  line-height: 1;
  color: var(--text-dim);
  transition: transform 0.15s ease, color 0.15s ease;
}

.plan-goal-summary:hover .plan-goal-chevron,
.plan-goal-disclosure:focus-within .plan-goal-chevron,
.plan-goal-disclosure[open] .plan-goal-chevron {
  color: var(--leverage);
}

.plan-goal-disclosure[open] .plan-goal-chevron {
  transform: rotate(180deg);
}

.plan-goal-meta {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 15px;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.9px;
  text-transform: uppercase;
  color: var(--text-dim);
  line-height: 1.4;
  flex-wrap: wrap;
}

.plan-goal-metric {
  font-weight: 500;
  letter-spacing: 0.4px;
  text-transform: none;
  color: var(--text-dim);
}

.plan-goal-more {
  margin-top: 10px;
  padding: 0;
  border: 0;
  background: none;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.6px;
  color: var(--text-dim);
  cursor: pointer;
  text-align: left;
}

.plan-goal-more:hover {
  color: var(--text-secondary);
}

.plan-goal-receipt {
  margin: 8px 0 8px 0;
  padding: 12px 12px 12px 12px;
  border-left: 2px solid var(--leverage-bg);
  background: color-mix(in srgb, var(--bg-surface) 56%, transparent);
  border-radius: 0 4px 4px 0;
}

.plan-goal-receipt-section + .plan-goal-receipt-section {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--border-subtle);
}

.plan-goal-receipt-label {
  margin-bottom: 8px;
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--text-dim);
}

.plan-goal-outcomes {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.plan-goal-outcome-row {
  display: grid;
  grid-template-columns: minmax(86px, 0.9fr) minmax(54px, 1fr) auto;
  gap: 10px;
  align-items: center;
  font-size: 10px;
  color: var(--text-secondary);
}

.plan-goal-outcome-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.plan-goal-outcome-bar {
  position: relative;
  height: 3px;
  border-radius: 999px;
  background: var(--border-subtle);
  overflow: hidden;
}

.plan-goal-outcome-bar::after {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--w);
  border-radius: inherit;
  background: var(--leverage);
}

.plan-goal-outcome-years {
  color: var(--leverage);
  font-variant-numeric: tabular-nums;
}

/* ═══════════ PLAN FILTER CHIPS ═══════════
   Threat-axis pivot. Same vocabulary as the per-action `.plan-threat-tag`
   chips so the user reads the row as "the threats the cards already
   talk about". Sorted by user's elevation (most-elevated first). The
   "All" chip is always first; counts ride alongside each label. */
.plan-filter {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: var(--space-lg);
}

.plan-filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.9px;
  text-transform: uppercase;
  padding: 6px 10px;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text-secondary);
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
  white-space: nowrap;
}

.plan-filter-chip:hover {
  background: var(--bg-hover);
  color: var(--text-primary);
  border-color: var(--border);
}

.plan-filter-chip.active {
  background: var(--accent-soft);
  color: var(--accent);
  border-color: var(--accent);
}

.plan-filter-chip-count {
  font-size: 9px;
  font-weight: 500;
  letter-spacing: 0.3px;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}

.plan-filter-chip.active .plan-filter-chip-count {
  color: var(--accent);
  opacity: 0.8;
}

/* The filter wrapper is transparent on desktop (`display:contents` drops the
   box) so the pill rail flows exactly as it did before — desktop byte-
   identical. The trigger is the mobile-only collapsed control; hidden here.
   The 720px block swaps them: trigger shows, rail becomes a drop-down menu. */
.plan-filter-wrap { display: contents; }
.plan-filter-trigger { display: none; }

/* ═══════════ PLAN BACKLOG ═══════════
   The active queue, no longer hidden behind a button. Section head
   echoes the `.action-group-head` chassis (label · meta) and the body
   uses the same `.plan-card` chrome as the hero — the divider is the
   only visual break, so the live plan reads as one continuous Pareto. */
.plan-backlog {
  margin-top: var(--space-xl);
}

/* The backlog head is a real disclosure on both viewports — a peer of the
   Secured / Dismissed heads (chevron + interactive summary). Only the
   default differs: desktop renders open, mobile collapsed (see the 720px
   block); either can be toggled and the choice persists. */
.plan-backlog-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
  margin-bottom: var(--space-sm);
  list-style: none;
  cursor: pointer;
}
.plan-backlog-head::-webkit-details-marker { display: none; }
.plan-backlog .plan-disclosure-arrow { display: inline; }

.plan-backlog-label {
  color: var(--text-secondary);
}

/* Section-head count — the inline prefix on Backlog / Secured / Dismissed
   (`14 Backlog`, `4 Secured`), so all three disclosures share one shape:
   chevron · count · label, magnitude pushed right. Inherits the label's
   color (Secured's stays green, Backlog's grey) but resets the heading's
   wide letter-spacing so a multi-digit count reads as a number (`14`), not
   a spaced-out `1 4`. Tabular figures keep the digits aligned. */
.plan-head-count {
  letter-spacing: normal;
  font-variant-numeric: tabular-nums;
}

/* Per-item magnitude hint — the right-side parallel to Secured's banked
   tally. Hidden on desktop (the count alone carries the head there),
   revealed and pushed right on mobile (see the 720px block). */
.plan-backlog-years {
  display: none;
  margin-left: auto;
  color: var(--text-dim);
  font-size: 11px;
  letter-spacing: 0.5px;
  font-variant-numeric: tabular-nums;
}

/* Direct-child selector so the backlog's open state rotates only its own
   head chevron, not the nested tail toggle's (a descendant). */
.plan-backlog[open] > .plan-backlog-head .plan-disclosure-arrow {
  transform: rotate(90deg);
}

/* Sub-resolution tail (desktop only): the "<0.1y each" remainder lives
   INSIDE the backlog panel and appends to the same row list. Deliberately a
   quiet footer toggle (muted, row-sized), NOT a section header — the
   marginal items are a footnote to the ranked list, not a peer section.
   Collapsed it reads as the panel's last row; open, the tail rows append
   below it seamlessly (the toggle grows a divider to separate them). */
/* Flex-reorder so the toggle always renders LAST. Collapsed it's the only
   visible child (sits right after the material rows); open, the tail rows
   (default order 0) stack above it and the toggle is pushed to the bottom of
   the list — the accordion expands in-place rather than dropping below a
   pinned header. */
.plan-backlog-tail {
  display: flex;
  flex-direction: column;
}
.plan-tail-toggle {
  order: 1;
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
  padding: 12px 18px;
  cursor: pointer;
  list-style: none;
  font-size: 11px;
  letter-spacing: 0.5px;
  color: var(--text-dim);
}
.plan-tail-toggle::-webkit-details-marker { display: none; }
.plan-tail-toggle-years {
  margin-left: auto;
  font-variant-numeric: tabular-nums;
}
/* Show-more / show-less: collapsed shows the count + "what's hidden" hint;
   expanded swaps to a bare "Show less" (count + hint are now redundant —
   the rows and their values are visible). */
.plan-tail-less { display: none; }
.plan-backlog-tail[open] .plan-tail-more,
.plan-backlog-tail[open] .plan-tail-toggle-years { display: none; }
.plan-backlog-tail[open] .plan-tail-less { display: inline; }
/* Open: a divider ABOVE the toggle separates it from the last appended row
   (whose own bottom border is dropped by .plan-row:last-child). Collapsed:
   no border — the last material row's divider already sits above it. The
   chevron flips up to read as "collapse," not "expand." */
.plan-backlog-tail[open] .plan-tail-toggle {
  border-top: 1px solid var(--border-subtle);
}
.plan-backlog-tail[open] .plan-tail-toggle .plan-disclosure-arrow {
  transform: rotate(-90deg);
}

/* ═══════════ PLAN BACKLOG ROWS ═══════════
   Ranks 4..N as a unified list inside one container — internal dividers
   between rows, no per-row borders — so the backlog reads as items in a
   list, not a stack of mini-cards. Two-line summary: title + impact
   on the primary line, priority + threat tags as subordinate inline
   text on the secondary line. Everything else (Done / Dismiss / why /
   pills / refs) lives behind the expand. */
.plan-rows {
  display: flex;
  flex-direction: column;
  margin-bottom: var(--space-xl);
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  overflow: hidden;
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.03);
}

.plan-row {
  background: transparent;
  border-bottom: 1px solid var(--border-subtle);
  transition: background 0.15s;
}

.plan-row:last-child {
  border-bottom: 0;
}

.plan-row:hover {
  background: var(--bg-surface);
}

/* Dismissed rows read as deprioritized — faded back, lifting toward legible
   on hover (you're reconsidering one). Secured rows take no extra treatment:
   they're the same chassis as live work, just banked. */
.plan-row-dismissed {
  opacity: 0.6;
}
.plan-row-dismissed:hover {
  opacity: 0.95;
}

.plan-row-summary {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding: 12px 18px;
  list-style: none;
  cursor: pointer;
}
.plan-row-summary::-webkit-details-marker { display: none; }
.plan-row-summary::marker { content: ''; }

.plan-row-rank {
  font-size: 12px;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
  min-width: 18px;
  text-align: right;
  flex-shrink: 0;
  padding-top: 2px;
  line-height: 1.4;
}

/* Two-row body: title-line above, meta-line below. min-width:0 so the
   title can ellipsis-truncate inside the flex parent instead of pushing
   the impact off the right edge on narrow viewports. */
.plan-row-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* Primary line — title on the left, +Xy on the right. The title is the
   identifier the eye lands on; the +Xy is the value that drives ranking.
   Both baseline-aligned so a "Discuss X" sits on the same line as its
   "+1y" without the number floating off the cap height. */
.plan-row-title-line {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}

.plan-row-title {
  flex: 1;
  min-width: 0;
  font-size: 13px;
  font-weight: 500;
  color: var(--text-primary);
  line-height: 1.45;
}

/* "Locked in" check on the secured action sheet title — marks the detail
   view as won so an at-goal item doesn't read like open work. Inline, leading
   the title; green = the banked / leverage-won register. Not on the list rows
   (the SECURED header already labels the whole group). */
.plan-row-check {
  color: var(--green);
  font-weight: 700;
  margin-right: 6px;
}

/* Backlog +Xy stays neutral — the hero cards' cyan +Xy is the headline
   cut; if every backlog row repeats the color, cyan stops signaling and
   becomes wallpaper. Sort order already encodes ranking; the value
   column just needs to be scannable, not loud. */
.plan-row-impact {
  font-size: 15px;
  font-weight: 400;
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.3px;
  flex-shrink: 0;
  line-height: 1.2;
}

/* Secondary line — priority label + threat tags as inline text. All
   small, all dim, middot-separated so it reads as one phrase rather
   than three chips. The row's cursor + hover fill carry the "click to
   expand" affordance; the heroes above teach the pattern. */
.plan-row-meta-line {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 1.1px;
  text-transform: uppercase;
  line-height: 1.4;
  color: var(--text-dim);
  flex-wrap: wrap;
}

/* See .plan-card-priority — chip background + only SOON colored. */
.plan-row-priority {
  line-height: 1.4;
  background: var(--bg-surface);
  padding: 1.5px 6px;
  border-radius: 3px;
  margin-right: 6px;
}
.plan-row-priority-urgent { color: var(--text-secondary); }
.plan-row-priority-soon { color: var(--amber); }
.plan-row-priority + .plan-row-meta-sep { display: none; }

.plan-row-meta-sep {
  color: var(--border);
  font-weight: 400;
}

/* Threat tag as inline text inside a meta line — strip the chip chrome
   so the tag flows as text with the priority label and middot
   separators around it. Both hero cards (.plan-card-threat) and backlog
   rows (.plan-row-threat) use this idiom; the .plan-threat-tag class
   is kept so the existing click handler that navigates to Risks
   matches both contexts. */
.plan-row-threat.plan-threat-tag,
.plan-card-threat.plan-threat-tag {
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  font-size: inherit;
  font-family: inherit;
  font-weight: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: inherit;
  cursor: pointer;
  line-height: inherit;
  border-radius: 0;
}

.plan-row-threat.plan-threat-tag:hover,
.plan-card-threat.plan-threat-tag:hover {
  color: var(--text-secondary);
  background: none;
  border: 0;
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* Expanded panel — same vertical reading order as the hero card's
   expanded state: controls → why → pills + refs. Hero keeps its
   Done/Dismiss in the summary (always visible); backlog can't afford
   that horizontal real estate at rest, so the controls land at the
   top of the panel here — same position the eye lands on for hero
   the moment a card is open. Refs detach from controls and pair with
   pills, mirroring hero. No divider — the [open] state itself is
   the boundary. */
.plan-row-details {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  padding: 4px 18px 14px calc(18px + 18px + 14px);
  animation: expand-in 0.15s ease-out;
}

/* Secured / dismissed rows carry no rank gutter, so their title sits at the
   row's left inset — pull the expanded panel back to align under it (the
   default left padding clears the backlog's rank column, which isn't there). */
.plan-row-secured > .plan-row-details,
.plan-row-dismissed > .plan-row-details {
  padding-left: 18px;
}

.plan-row-details > .action-controls {
  margin-top: 0;
}

.plan-row-details > .action-why {
  font-size: 11.5px;
  color: var(--text-dim);
  line-height: 1.6;
  max-width: 70ch;
  margin: 0;
}

/* Empty-state copy for filter pivots that come up empty. */
.plan-empty {
  padding: var(--space-md);
  margin-bottom: var(--space-lg);
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  font-size: 12px;
  color: var(--text-secondary);
  text-align: center;
}

/* ═══════════ PLAN MOBILE EXTRAS ═══════════
   Elements that only render on mobile (the priority-tier header; duplicated
   action controls inside hero card expanded panels). Hidden by default —
   the @media block below switches them on at narrow widths. */
.plan-card-controls-expanded { display: none; }
/* Priority tier header — the lead action group's label. Hidden on desktop
   (the rich hero cards already read as the headline tier there); the 720px
   block switches it on so the three mobile tiers — Priority / Backlog /
   Secured — read as one consistent labeled ledger instead of leaving the
   top group anonymous. The right-side "+LE" folds in the old (always-hidden)
   column header. `Priority` is a structural role label; owner to finalize. */
.plan-priority-head { display: none; }

/* ═══════════ PLAN — MOBILE ═══════════
   Mirrors the risks-tab mobile pattern: page header drops (the bottom
   tab labels the tab), the two-tier hero/backlog split collapses into
   one continuous ranked list, and per-row chrome shrinks so the eye
   scans a leaderboard rather than a stack of cards.

   Top-action emphasis is carried by three weak cues that stack:
   inline rank prefix ("01 · …"), descending +Xy right column anchored
   by a column-header strip, and a single visual hedge on rank #1
   (cyan +Xy + visible Done/Dismiss). No two-tier chrome — flat list
   throughout, exactly the way risks dropped SIGNIFICANT/MINOR. */
@media (max-width: 720px) {
  /* Page header is redundant with the bottom tab bar's PLAN label —
     same trick risks pulled on mobile. Scoped to .plan-view so other
     tabs' headers stay intact. */
  .plan-view .view-header { display: none; }

  /* Tighten side padding on the view shell so each row uses the full
     viewport width instead of carrying ~24px of empty gutter on each
     side. Matches the mobile feel of the risks rail. */
  .plan-view.view-container {
    padding-left: var(--space-md);
    padding-right: var(--space-md);
  }

  /* Full-bleed status hero — the top "where I stand" zone. Edge-to-edge and
     pulled up under the topbar so it reads as a header region attached to the
     top, not a floating card. The fill is the premium move: NOT flat grey
     (utilitarian / enterprise), but a faint BRAND wash — the cyan of the 2.6y
     hero and the donut — strongest behind the number and fading to transparent
     so the zone dissolves into the white action list. NO bottom rule: the
     gradient fades out before the band's edge, so a hairline there sat in white
     space with nothing to divide and read as an abrupt, orphaned line. The soft
     fade IS the edge — that's the mobile-native, premium read. The tint stays
     light (0.06 top) so it never fights the dim labels / cyan number for
     contrast; raise the top stop for a more saturated hero, lower for whisper. */
  .plan-horizon {
    margin: calc(-1 * var(--space-lg)) calc(-1 * var(--space-md)) var(--space-lg);
    padding: var(--space-lg) var(--space-md) var(--space-lg);
    background: linear-gradient(
      180deg,
      rgba(8, 145, 178, 0.06) 0%,
      rgba(8, 145, 178, 0.02) 58%,
      rgba(8, 145, 178, 0) 100%
    );
  }

  /* One hero, front-loaded. The page's hierarchy lives HERE: the reachable
     number is the single thing the eye should land on, and everything below
     (the rail, the filter, the action list) reads as support. On a phone the
     number had drifted to 24px while the by-goal donut sat at 46px beside it —
     two elements at similar weight, so the header read as a cluster with no
     lead ("random"). Restore the base intent (see .plan-horizon-total: "the
     hero, sized to dominate"): blow the number up, shrink the donut to a
     subordinate drill, and keep a measured gap to the rail — enough to read as
     headline + supporting axis, not so much the number floats away from it. */
  .plan-horizon-head {
    margin-bottom: var(--space-sm);
  }
  .plan-horizon-head-main {
    gap: 6px;
  }
  /* Subordinate drill, not a peer ring — clearly smaller than the 40px hero
     so it reads as "and here's the split" rather than a second focal point. */
  .plan-horizon-mix-donut {
    width: 32px;
    height: 32px;
  }
  .plan-horizon-total {
    font-size: 40px;
  }
  .plan-goal-sheet {
    top: auto;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    max-height: 88vh;
    padding: 24px var(--space-lg) 28px;
    border-left: 0;
    border-top: 1px solid var(--border);
    border-radius: 12px 12px 0 0;
  }
  /* On mobile a goal row drills into its detail sheet rather than expanding
     inline. The whole row is the tap target and the sheet is a known pattern,
     so drop the per-row chevron — a repeated arrow column is just chrome. */
  .plan-goal-chevron {
    display: none;
  }
  .plan-goal-sheet-head {
    margin-bottom: 18px;
  }
  .plan-goal-sheet-title {
    max-width: calc(100vw - 96px);
    font-size: 12px;
  }
  .plan-goal-sheet-body {
    display: flex;
    flex-direction: column;
    gap: 18px;
  }
  .plan-goal-donut-wrap,
  .plan-goal-donut {
    width: 132px;
    height: 132px;
  }
  .plan-goal-donut-wrap {
    position: relative;
    top: auto;
    align-self: center;
  }
  .plan-goal-donut-center {
    inset: 33px;
  }
  .plan-goal-donut-num {
    font-size: 20px;
  }
  .plan-goal-list { gap: 13px; }
  .plan-goal-row {
    grid-template-columns: 8px minmax(0, 1fr);
    gap: 11px;
  }
  .plan-goal-name { font-size: 13px; }
  .plan-goal-years { font-size: 14px; }
  .plan-goal-meta {
    gap: 7px;
    font-size: 10px;
    line-height: 1.45;
  }
  .plan-goal-more { margin-left: 19px; }
  .plan-horizon-scope {
    font-size: 10px;
  }

  /* Horizon track: shorter on mobile. Drops node grid-row heights
     plus the track itself so the BASELINE / TODAY / CEILING band
     packs tighter without overlapping ages or names. Inner side
     margin restores the label-overhang clearance: nodes at left:0%
     and left:100% translate -50% past those anchors, so the labels
     (esp. CEILING) need horizontal room past the rail ends. The
     panel padding alone wasn't enough — CEILING was clipping. */
  .plan-horizon-track {
    /* Inset for the BASELINE / CEILING labels (centered on left:0% / 100%,
       translated -50% past those anchors) to clear the card's inner edge.
       The header card's own 16px padding now does most of that work, so the
       track only needs a modest extra margin — not the 28px the flat,
       card-less layout required to clear the bare screen edge. */
    margin: 0 18px;
    height: 64px;
  }
  .plan-horizon-node {
    grid-template-rows: 18px 14px 18px;
    row-gap: 2px;
  }
  .plan-horizon-age {
    font-size: 13px;
  }
  .plan-horizon-name {
    font-size: 9px;
  }
  /* Banked segment label is anchored to the center of the green banked
     segment — a sliver of the full span. On a phone-width track that
     center lands ~7% in, colliding with the BASELINE/TODAY node names
     and overflowing the rail's left edge. Drop it on mobile: the TODAY
     age vs BASELINE age already encode the banked delta. */
  .plan-horizon-seg {
    display: none;
  }

  /* Filter as a single trigger that opens a native bottom-sheet picker. The
     scrolling rail of 11 chips was heavy chrome above the plan (and clipped
     the last threats off-screen); on mobile the rail is hidden and the
     trigger shows the current filter. The picker list lives in the sheet
     (.plan-sheet-filter-list below). The trigger itself is rendered into the
     Priority header row (renderFilterTrigger), so the standalone wrap holds
     only the now-hidden rail — collapse it entirely so it claims no row. */
  .plan-filter-wrap {
    display: none;
  }
  .plan-filter-trigger {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-family: var(--font-mono);
    font-size: 9px;
    font-weight: 600;
    letter-spacing: 0.9px;
    text-transform: uppercase;
    padding: 8px 12px;
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    color: var(--text-secondary);
    cursor: pointer;
  }
  .plan-filter-trigger-chevron {
    font-size: 8px;
    color: var(--text-dim);
  }
  /* The desktop pill rail is hidden on mobile — the trigger + sheet replace
     it. (Kept in the DOM so the desktop control is one render path.) */
  .plan-filter {
    display: none;
  }

  /* Column header strip stays hidden on mobile (default display:none holds).
     A labeled ACTION/+LE header is the one literal "this is a table" signal;
     dropping it lets the list read as a forward to-do frontier. The
     right-aligned +Xy column still reads as the rank axis by alignment alone
     — same way the risks rail dropped its header. The filter chips'
     margin-bottom already separates the list from the chips above. */

  /* Priority tier header — peer to the Backlog / Secured heads below, so
     the lead group is no longer anonymous. A hidden chevron spacer occupies
     the same lead column the Backlog / Secured chevrons do, so the three
     labels align into one clean column — symmetry without adding yet another
     number to a number-dense screen. Same .action-group-label chassis. */
  .plan-priority-head {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: 2px 0 12px;
  }
  .plan-priority-spacer {
    visibility: hidden;
  }
  .plan-priority-label {
    color: var(--text-secondary);
  }
  /* The compact filter trigger rides this row, pushed to the right edge so it
     fills the space the PRIORITY label leaves — reclaiming the full-width row
     it used to waste on its own. (When there are no priority cards the label
     is omitted and the trigger still sits right, with the row holding just
     it.) margin-left:auto right-aligns it regardless of the label's presence. */
  .plan-priority-head .plan-filter-trigger {
    margin-left: auto;
  }

  /* Container chrome → flat. Hero `.plan-cards` and backlog
     `.plan-rows` both shed their own borders/shadows/radii. The
     priority header above and the per-row border-bottom below carry
     the list's visual structure now. The two containers stack with
     zero gap so hero rows and backlog rows read as one continuous
     list. */
  .plan-cards {
    gap: 0;
    margin-bottom: 0;
  }
  /* Section boundaries are owned by WHITESPACE, not lines — row dividers
     (the per-row border-bottom) already draw hairlines down the list, so a
     section hairline would just read as another row break ("one line means
     one thing"). For the four zones (priority / backlog / secured / footer)
     to read as distinct, the between-zone gap has to clearly out-measure the
     within-zone row rhythm: space-xl (32px) here and on Secured / Dismissed
     gives every section break the same generous, deliberate breath, so the
     three lists parse as parallel zones rather than one long run. */
  .plan-backlog {
    margin-top: var(--space-xl);
  }
  /* Secured / Dismissed shed their section hairline the same way — but
     their border-top is authored in components-views.css, which loads
     AFTER this file, so the override has to live there to win the
     cascade. See the mobile block at the end of that file. */
  /* The backlog tail — the bulk of the "overwhelming list" density — is a
     collapsed disclosure on mobile. The head becomes a tappable summary
     (chevron + count + "≤Xy each" hint); the hero card above it keeps its
     border-bottom as the divider, so this reads as "the few that matter,
     then everything else, one tap away." */
  /* Edge-aligned (padding-left:0) so the chevron + label line up with the
     Secured / Dismissed heads below — the three disclosures read as peers,
     not as a row indented under the hero list. Inherits the base flex /
     baseline / space-sm gap so the typographic chassis matches too. */
  .plan-backlog-head {
    margin-bottom: 0;
    padding: 14px 0;
    cursor: pointer;
    pointer-events: auto;
  }
  .plan-backlog .plan-disclosure-arrow { display: inline; }
  .plan-backlog-years { display: inline; }
  /* Backlog rows shed their card chrome → flat hairline-divided rows. The
     secured / dismissed `.plan-disclosure-list` flattens the same way, but its
     base border is authored in components-views.css (loaded AFTER this file),
     so that override has to live there to win the cascade — see the mobile
     block at the end of that file. */
  .plan-rows {
    background: transparent;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    overflow: visible;
  }

  /* Hero-card chrome (border, radius, shadow, card padding) sheds so
     each card reads as a flat row. Bottom-border divider lines up
     with the backlog rows below for one continuous list. */
  .plan-card {
    background: transparent;
    border: 0;
    border-bottom: 1px solid var(--border-subtle);
    border-radius: 0;
    box-shadow: none;
  }
  /* No hover fill on touch. A phone has no real pointer, so :hover is the
     "sticky tap" state — after tapping a row to collapse it, the browser
     leaves :hover applied until you tap elsewhere, stranding a grey
     background on a now-collapsed row. Strip the fill so collapsed rows
     return to flat. (Desktop keeps its hover fill — base rules above.) */
  .plan-card:hover,
  .plan-row:hover {
    background: transparent;
    border-color: var(--border-subtle);
    box-shadow: none;
  }
  /* Hero list never closes with its own trailing divider on mobile: when a
     backlog disclosure follows, its border-top is the section rule; when
     nothing follows, a dangling hairline just floats. Either way the last
     hero card drops its bottom border. */
  .plan-cards .plan-card:last-child {
    border-bottom: 0;
  }
  .plan-rows .plan-row:last-child,
  .plan-disclosure-list .plan-row:last-child {
    border-bottom: 0;
  }

  /* Summary padding tightens — the left/right padding shrinks
     because the rank gutter is gone and rows should hug the
     column-header strip's bleed. */
  /* Side padding gives the open-row left accent rail (3px inset shadow,
     below) clearance from the title without shifting content between
     collapsed and open states — both states share this inset. */
  .plan-card-flat,
  .plan-card-summary {
    padding: 14px 10px;
    gap: 0;
    align-items: stretch;
  }
  .plan-row-summary {
    padding: 14px 10px;
    gap: 0;
  }

  /* Rank gutter → inline prefix. The desktop rank gutter is wasted
     vertical real estate on mobile and drives the wasted-left-margin
     problem in expanded view. Hide the gutter; inject the rank as a
     tiny dim text prefix on the title via ::before with attr().

     Only the HERO cards get the numeric prefix. The backlog is a
     collapsed disclosure below its own header, so continuing the
     01·02·03 sequence into it (05·06·…) made the BACKLOG header read as
     an interruption mid-list. Leaving backlog rows unnumbered makes them
     a clean, unranked tail under the header — a peer of the Secured /
     Dismissed lists, which are likewise unnumbered. (Desktop keeps the
     rank in its gutter; the ::before is mobile-only, so desktop is
     untouched.) */
  .plan-card-rank,
  .plan-row-rank { display: none; }

  .plan-card-title::before {
    content: attr(data-rank) "  ";
    color: var(--text-dim);
    font-weight: 400;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.3px;
    margin-right: 2px;
  }

  /* Hero body takes full row width (no gutter sibling). Internal
     gap matches the backlog row's 4px so hero and backlog read at
     the same vertical density. */
  .plan-card-body {
    gap: 4px;
  }

  /* One type scale top to bottom: hero and backlog titles share 14px so
     the whole list reads as one leaderboard. Now that the collapsed row
     is a single line, 14px buys readability and a comfortable tap target
     without re-inflating row height. */
  .plan-card-title,
  .plan-row-title {
    font-size: 14px;
  }

  .plan-card-lever { display: none; }

  /* ── Collapsed row = one clean line ────────────────────────────────
     Scan mode needs only "what is it" (title) and "worth it" (+Xy). The
     whole meta line — priority chip + threat tags — is understanding-
     layer, so a collapsed row hides it entirely; it returns on [open].
     The one signal we keep at scan level is a non-ongoing priority, and
     it rides a small colored dot on the title line (below) rather than a
     second-line chip — one line, no height cost. */
  .plan-card:not([open]) .plan-card-meta-line,
  .plan-row:not([open]) .plan-row-meta-line { display: none; }

  /* Ongoing priority is uniform across nearly every row — pure noise.
     Never render the chip on mobile, even when the meta line returns on
     [open]; the non-ongoing chips are the only ones worth showing. */
  .plan-card-priority-ongoing,
  .plan-row-priority-ongoing { display: none; }

  /* "+N" threat badge is retired on mobile: collapsed shows no meta,
     [open] shows the full inline threat list, so a count has nothing to
     stand in for. */
  .plan-card-threat-more,
  .plan-row-threat-more { display: none; }

  /* Threat tags are navigate-to-Risks buttons — inert on mobile so a
     scroll-tap can't fling the user off the Plan. */
  .plan-card-threat,
  .plan-row-threat { pointer-events: none; }

  /* No per-row priority marker in the collapsed scan: every row is a
     uniform, flush-left "title … +Xy". A leading dot/chip either indents
     the title (breaking the left edge) or costs a line. Priority is rare
     and behaviour-changing, so it surfaces on [open] — the meta line shows
     the Doctor / Rx / Soon chip there — not in the scan. */

  /* +LE column carries the leverage cyan (the years gained ARE the
     leverage axis — cool tone per the ETHOS), but graded by magnitude via
     the per-row --impact-shade custom property: the cyan-to-dim mix tracks
     each lever's size against the top of the column. Big levers glow; the
     sub-year tail recedes toward grey. So the colour MEANS something
     instead of painting every number the same — and the gradient quietly
     reinforces the Pareto the list is built on. The shade is set inline in
     JS (impactShadeStyle); the 100% fallback keeps any un-graded row full
     cyan. */
  .plan-cards .plan-card .plan-card-impact,
  .plan-rows .plan-row .plan-row-impact,
  .plan-disclosure-list .plan-row .plan-row-impact {
    font-size: 15px;
    font-weight: 500;
    color: color-mix(in srgb, var(--leverage) var(--impact-shade, 100%), var(--text-dim));
    letter-spacing: -0.3px;
    line-height: 1.2;
  }

  /* The action list is deliberately UNIFORM — no per-rank emphasis. The
     page's one focal point is the reachable hero up top; rank here is carried
     by order plus the cyan +Xy column (magnitude-graded via --impact-shade),
     which is enough. An earlier pass bolded #1 to make it a "spine", but a
     second strong element mid-page just competed with the hero and read as
     random emphasis. Hierarchy is front-loaded (big hero → calm list), not
     distributed. */

  /* Controls visibility per rank. Row #1 keeps Done/Dismiss visible
     in the collapsed summary so the most likely commit stays zero-
     tap. Rows #2..N hide the summary controls and surface them in
     the expanded panel instead — scanning a leaderboard shouldn't
     have to step over a wall of commit chips. */
  .plan-card-details-toggle { display: none; }
  /* Collapsed = pure scan mode: no commit chrome under the title. The
     summary Done/Dismiss row is suppressed for every expandable card —
     including rank #1, which earlier iterations kept inline. That made
     the top row look already-expanded against the flat list below;
     hiding it everywhere keeps the collapsed list a clean leaderboard.
     :not(.plan-card-flat) keeps flat (non-expandable) cards visible —
     they have no expanded panel to host the controls instead. */
  .plan-cards .plan-card:not(.plan-card-flat) .plan-card-controls-summary {
    display: none;
  }
  /* Decision mode: controls live in the expanded body, pushed to the end
     (order:5) so the block reads title → RATIONALE → GOALS → SOURCES →
     commit. The closing Done/Dismiss pairs with the stronger divider
     below to mark where one action ends and the next begins. Backlog
     rows render .action-controls directly into .plan-row-details, so
     they get the same end-ordering. */
  .plan-cards .plan-card .plan-card-controls-expanded {
    display: flex;
    margin-bottom: 0;
    order: 5;
  }
  .plan-row-details > .action-controls {
    order: 5;
  }

  /* Expanded panels reflow full-width. Desktop indents past the
     rank gutter to align with the title's left edge; the gutter is
     gone on mobile, so the indent becomes wasted left margin.
     Flush both panels to the row's own left padding. */
  .plan-card-details {
    padding: 6px 10px 18px;
  }
  .plan-row-details {
    padding: 6px 10px 18px;
  }

  /* ── Active action = a contained task block ──────────────────────
     The flat list is scan mode; tapping a row switches it to a decision
     block. The open <details> (summary + body) gets a surface fill, a
     left accent rail down its full height, and a stronger closing
     divider — so everything from the tapped header to the next divider
     visibly belongs to this one action. The inset shadow draws over the
     row's left padding, so it costs no layout shift. */
  .plan-card[open],
  .plan-row[open] {
    background: var(--bg-surface);
    box-shadow: inset 3px 0 0 var(--accent);
    border-bottom-color: var(--border);
  }
  /* The priority chip is itself bg-surface; on an open (also bg-surface)
     row it would dissolve into the fill. Lift it to panel-white so
     Doctor / Soon still reads as a token. */
  .plan-card[open] .plan-card-priority,
  .plan-row[open] .plan-row-priority {
    background: var(--bg-panel);
  }

  /* Predictable expanded anatomy: RATIONALE / GOALS / SOURCES — three
     parallel labeled sections. GOALS and SOURCES already carry mobile
     eyebrows (.target-pills-label / .action-refs-label); this gives the
     why-prose the matching RATIONALE eyebrow so the body reads as one
     three-part block instead of a bare paragraph leading the rest.
     Injected via ::before, inside the existing .action-why element, so
     desktop markup and the desktop `> .action-why` rules are untouched. */
  .plan-card-details > .action-why::before,
  .plan-row-details > .action-why::before,
  .plan-sheet .action-why::before {
    content: "Rationale";
    display: block;
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 1.2px;
    text-transform: uppercase;
    color: var(--text-secondary);
    margin-bottom: 8px;
  }

  /* Citations: the collapsed "SOURCES (N)" eyebrow + the expanded source
     list are already viewport-agnostic (one quiet tap target, full-width
     stacked rows), so no phone-specific override is needed here. */

  /* Goals readout. On a phone the wrapping pill strip went ragged — a
     wide binary goal claimed a full row, then narrow ones doubled up on
     the next, and the strip sat unlabeled right after the why-text. The
     job here is scan-down-and-compare ("what does this move, where do I
     stand?"), so restack it as a labeled, one-per-row readout: each goal
     on its own line, label left, current → goal right-aligned into a
     column. Borderless with hairline row dividers — a gauge cluster, not
     a stack of chips. Scoped to Plan; Risks uses target-chips instead. */
  .plan-card .target-pills,
  .plan-row .target-pills,
  .plan-sheet .target-pills,
  .risks-sheet .target-pills {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
  }
  .plan-card .target-pills-label,
  .plan-row .target-pills-label,
  .plan-sheet .target-pills-label,
  .risks-sheet .target-pills-label {
    display: block;
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 1.2px;
    text-transform: uppercase;
    /* Header color — matches the SOURCES eyebrow so the two sub-sections
       stay parallel, and aligns with the section-header convention. */
    color: var(--text-secondary);
    /* margin-top separates GOALS as a section from the RATIONALE prose above
       (parallel to the gap before SOURCES); margin-bottom is the tight
       label→first-row gap (the first row also zeroes its own top padding
       below, so this margin solely owns the caption gap — without that, the
       two stacked and left ~21px of dead air under the header). */
    margin-top: var(--space-md);
    margin-bottom: var(--space-sm);
  }
  .plan-card .target-pill,
  .plan-row .target-pill,
  .plan-sheet .target-pill,
  .risks-sheet .target-pill {
    display: flex;
    /* flex-start (not space-between): the pill has THREE flex children
       — icon, label, value. space-between pins icon left and value right
       but floats the label in the middle of the leftover space, so every
       row centers its name at a different x (ragged). flex-start hugs
       icon+label as a left group; the value gets margin-left:auto below
       to claim the right edge. */
    justify-content: flex-start;
    align-items: baseline;
    gap: 12px;
    width: 100%;
    max-width: none;
    background: none;
    border: 0;
    border-bottom: 1px solid var(--border-subtle);
    border-radius: 0;
    padding: 11px 0;
    white-space: normal;
  }
  .plan-card .target-pill:last-of-type,
  .plan-row .target-pill:last-of-type,
  .plan-sheet .target-pill:last-of-type,
  .risks-sheet .target-pill:last-of-type {
    border-bottom: 0;
    padding-bottom: 0;
  }
  /* First row hugs the GOALS label (its margin-bottom owns that gap) — the
     row's own top padding would double it into dead air under the header. */
  .plan-card .target-pill:first-of-type,
  .plan-row .target-pill:first-of-type,
  .plan-sheet .target-pill:first-of-type,
  .risks-sheet .target-pill:first-of-type {
    padding-top: 0;
  }
  /* Label keeps its left column; the value group rejoins as a real flex
     box (overriding the desktop `contents`) so current → goal stays one
     right-aligned unit, wrapping under itself only if a long binary goal
     needs it. */
  .plan-card .target-pill-label,
  .plan-row .target-pill-label,
  .plan-sheet .target-pill-label,
  .risks-sheet .target-pill-label {
    flex: 0 1 auto;
    min-width: 0;
    /* Darker than the desktop pill (text-secondary): in the restacked
       readout the goal name is a data row label, so it should outweigh
       the quiet GOALS caption above it — the contrast is what separates
       the eyebrow from the rows. */
    color: var(--text-primary);
  }
  .plan-card .target-pill-value,
  .plan-row .target-pill-value,
  .plan-sheet .target-pill-value,
  .risks-sheet .target-pill-value {
    display: inline-flex;
    /* margin-left:auto claims all the free space, pushing the value to
       the right edge so the goal numbers form a clean right-aligned
       column while icon+label stay pinned left. */
    margin-left: auto;
    flex: 0 1 auto;
    flex-wrap: wrap;
    justify-content: flex-end;
    align-items: baseline;
    gap: 6px;
    min-width: 0;
    text-align: right;
  }
  /* Binary / textual goals (no current value, a goal phrase rather than a
     number). The numeric right-aligned column can't hold a sentence like
     "annual screen, in care if positive" — it wrapped into a ragged
     right-hung block beside an empty middle. Instead, let the label keep
     the first line and drop the goal phrase onto its own full-width line
     below it, left-aligned and indented under the label (past the icon). */
  .plan-card .target-pill-binary,
  .plan-row .target-pill-binary,
  .plan-sheet .target-pill-binary,
  .risks-sheet .target-pill-binary {
    flex-wrap: wrap;
  }
  .plan-card .target-pill-binary .target-pill-value,
  .plan-row .target-pill-binary .target-pill-value,
  .plan-sheet .target-pill-binary .target-pill-value,
  .risks-sheet .target-pill-binary .target-pill-value {
    flex: 1 1 100%;
    margin-left: 0;
    margin-top: 3px;
    padding-left: 25px; /* 13px icon + 12px gap → align under the label */
    justify-content: flex-start;
    text-align: left;
    white-space: normal;
  }
  /* A binary goal has no current value, so the "current → goal" arrow points
     from nothing — drop it and let the goal phrase stand on its own under
     the label. */
  .plan-card .target-pill-binary .target-pill-arrow,
  .plan-row .target-pill-binary .target-pill-arrow,
  .plan-sheet .target-pill-binary .target-pill-arrow,
  .risks-sheet .target-pill-binary .target-pill-arrow {
    display: none;
  }

  /* Overflow toggle sits as its own left-aligned row under the readout. */
  .plan-card .target-pills-toggle,
  .plan-row .target-pills-toggle,
  .plan-sheet .target-pills-toggle,
  .risks-sheet .target-pills-toggle {
    align-self: flex-start;
    padding-left: 0;
    margin-top: 6px;
  }

  /* SOURCES section break inside the action sheets. The sheet body is a plain
     block container, so `.cite` only gets its own 8px top margin — too tight
     against the goal row above. The inline `.plan-*-details` panels are
     flex-column (gap + margin → already ~16px), so this only lifts the sheet
     to the same section rhythm GOALS uses above it. */
  .plan-sheet .cite,
  .risks-sheet .cite {
    margin-top: var(--space-md);
  }
}

/* ═══════════ ACTION ITEMS ═══════════ */
.action-group + .action-group {
  margin-top: var(--space-lg);
}

.action-group-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
  margin-bottom: var(--space-sm);
}

.action-group-label {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 1.5px;
  text-transform: uppercase;
}

.action-group-label.urgent { color: var(--red); }
.action-group-label.soon { color: var(--amber); }
.action-group-label.ongoing { color: var(--green); }
.action-group-label.pharmaceutical { color: var(--accent); }
.action-group-label.secured { color: var(--green); }
.action-group-label.dismissed { color: var(--text-dim); }

/* Group total — sum of per-row leverage across actions in this priority
   group. Pushed right; tabular-nums so the column aligns down the page. */
.action-group-leverage {
  margin-left: auto;
  font-size: 11px;
  font-weight: 600;
  color: var(--leverage);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.3px;
}

.action-why {
  font-size: 13px;
  color: var(--text-secondary);
  margin-top: 2px;
  line-height: 1.5;
}

/* ═══════════ 3D SCHEMATIC ═══════════ */
.schematic-canvas {
  position: fixed !important;
  top: 56px;
  left: 0;
  width: 100vw !important;
  height: calc(100vh - 56px - var(--app-bottom-nav-safe-h)) !important;
  height: calc(100dvh - 56px - var(--app-bottom-nav-safe-h)) !important;
  z-index: 0;
  display: block;
}

/* ═══════════ TOOLTIP ═══════════
   Reusable hover tooltip. Add `data-tooltip="..."` to any element.
   Light panel, JBM mono, fades on hover/focus. CSS-only, no JS. */
[data-tooltip] {
  position: relative;
}

[data-tooltip]::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%) translateY(4px);
  background: var(--bg-panel);
  color: var(--text-primary);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 8px 12px;
  font-family: var(--font-mono);
  font-size: 11.5px;
  font-weight: 400;
  letter-spacing: 0.2px;
  line-height: 1.45;
  width: max-content;
  max-width: 280px;
  white-space: normal;
  text-align: left;
  text-transform: none;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.18s ease, transform 0.18s ease, visibility 0.18s;
  z-index: 100;
}

[data-tooltip]:hover::after,
[data-tooltip]:focus-visible::after {
  opacity: 1;
  visibility: visible;
  transform: translateX(-50%) translateY(0);
}

[data-tooltip-anchor="left"]::after  { left: 0;     transform: translateY(4px); }
[data-tooltip-anchor="left"]:hover::after,
[data-tooltip-anchor="left"]:focus-visible::after  { transform: translateY(0); }

[data-tooltip-anchor="right"]::after { left: auto;  right: 0; transform: translateY(4px); }
[data-tooltip-anchor="right"]:hover::after,
[data-tooltip-anchor="right"]:focus-visible::after { transform: translateY(0); }

/* ═══════════ LE TREE ═══════════ */
.le-tree-row {
  display: flex;
  justify-content: space-between;
  padding: 4px 0;
  font-size: 12px;
  line-height: 1.6;
}

.le-tree-label {
  color: var(--text-dim);
}

.le-tree-value {
  font-weight: 500;
}

.le-tree-value.positive { color: var(--green); }
.le-tree-value.negative { color: var(--amber); }

/* ═══════════ EXPANDABLE DETAIL PANELS ═══════════ */
.expandable {
  transition: background 0.15s;
  border-radius: var(--radius-sm);
  padding-left: var(--space-xs);
  margin-left: calc(-1 * var(--space-xs));
}

.expandable:hover {
  background: var(--bg-hover);
}

.expandable.expanded {
  background: var(--bg-surface);
}

.expand-panel {
  padding: var(--space-sm) var(--space-md);
  margin: 0 0 var(--space-sm) var(--space-md);
  border-left: 2px solid var(--border);
  font-size: 13px;
  line-height: 1.6;
  animation: expand-in 0.15s ease-out;
}

@keyframes expand-in {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: translateY(0); }
}

.expand-source {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-dim);
  letter-spacing: 0.3px;
  margin-bottom: 4px;
}

.expand-link {
  color: var(--accent);
  text-decoration: none;
  font-weight: 500;
}

.expand-link:hover {
  text-decoration: underline;
}

.expand-detail {
  color: var(--text-secondary);
}

/* Contextual explore button inside expand panels */
.expand-explore-btn {
  display: inline-flex;
  align-items: center;
  margin-top: 8px;
  border: none;
  background: none;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 500;
  color: var(--accent);
  cursor: pointer;
  padding: 3px 0;
  letter-spacing: 0.3px;
  opacity: 0.75;
  transition: opacity 0.15s;
}

.expand-explore-btn:hover {
  opacity: 1;
}

.expand-explore-btn.open {
  opacity: 1;
}

.expand-modifiers {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-bottom: 6px;
}

.modifier-pill {
  font-size: 13px;
  padding: 1px 6px;
  border-radius: 3px;
  background: var(--amber-bg);
  color: var(--amber);
  font-weight: 500;
  letter-spacing: 0.3px;
}

/* ═══════════ LIFE PHASES ═══════════ */
.life-phases {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-sm);
  margin-top: var(--space-md);
}

.life-phase {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--space-sm) var(--space-md);
  background: var(--bg-surface);
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-subtle);
  font-size: 13px;
}

.phase-label {
  font-weight: 600;
  font-size: 12px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--text-dim);
}

.phase-ages {
  font-weight: 500;
  color: var(--text-primary);
}

.phase-risk {
  color: var(--amber);
  font-size: 12px;
}

.phase-threats {
  color: var(--text-secondary);
  font-size: 12px;
}

/* ═══════════ CITATIONS ═══════════
   Unified evidence block shared by Plan action cards, the Plan goal modal,
   and the Risks lever drill (renderCitations → js/views/_citations.js). Same
   row vocabulary everywhere; the wrapper is payload-scaled (see that file):

     .cite-inline      — thin payload (≤2 sources, no prose). Always visible,
                         under a quiet "SOURCES" eyebrow that now has content,
                         so it doesn't read as an empty/broken section.
     .cite-collapsible — thick payload (prose, or ≥3 sources). Folded behind a
                         CONTROL-styled trigger (sentence case, link-toned),
                         not a section eyebrow, so it never impersonates a
                         dormant section.

   Rows lead with the venue as a LIGHT recognition anchor — color, not weight,
   separates it from the dim detail (a bold uppercase column shouted). NO
   journal logos: wordmarks turn to mud at this scale and clash with the
   severity/leverage palette. */
.cite {
  margin-top: var(--space-sm);
}

/* Inline (drill surfaces + thin Plan payloads) — a section eyebrow that
   matches its LOCAL peers on each surface, so "Sources" reads at the same
   weight as the labels it sits beside (not louder, not quieter). Base = the
   Plan action card scale (RATIONALE / GOALS); the lever drill and goal modal
   override below to match their own peer labels. */
.cite-inline-label {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--text-secondary);
  margin-bottom: 6px;
}
/* Risks lever drill — peers are TACTICS / STRETCH (9px / 1.4px / dim). */
.lever-exp-body .cite-inline-label {
  font-size: 9px;
  letter-spacing: 1.4px;
  color: var(--text-dim);
}
/* Plan goal modal — peer is "Modeled from" (9px / 1px / dim). */
.plan-goal-receipt .cite-inline-label {
  font-size: 9px;
  letter-spacing: 1px;
  color: var(--text-dim);
}

/* Collapsible (thick) — the trigger reads as a control, echoing the Trend ↗
   tertiary links: sentence case, no eyebrow letter-spacing, chevron-led. */
.cite-summary {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  cursor: pointer;
  list-style: none;
  padding: 2px 0;
  user-select: none;
}
.cite-summary::-webkit-details-marker {
  display: none;
}

.cite-summary-label {
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.3px;
  color: var(--text-secondary);
  transition: color 0.12s;
}
.cite-summary:hover .cite-summary-label {
  color: var(--leverage);
}

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

.cite-chevron {
  font-size: 9px;
  color: var(--text-dim);
  transition: transform 0.15s, color 0.12s;
}
.cite-summary:hover .cite-chevron {
  color: var(--leverage);
}
.cite[open] .cite-chevron {
  transform: rotate(180deg);
}

.cite-body {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 8px 0 2px;
}

.cite-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.cite-item {
  display: flex;
  align-items: baseline;
  gap: 6px;
  font-size: 10px;
  line-height: 1.4;
  text-decoration: none;
  color: var(--text-dim);
  transition: color 0.12s;
}

/* Light recognition anchor — natural case, normal weight; only the brighter
   color sets it apart from the dim detail. (NEJM / Lancet / AHA-ACC.) */
.cite-item-venue {
  flex-shrink: 0;
  color: var(--text-secondary);
}

.cite-item-detail {
  letter-spacing: 0.2px;
}

a.cite-item:hover {
  color: var(--leverage);
}
a.cite-item:hover .cite-item-venue {
  color: var(--leverage);
}

.cite-item-plain {
  cursor: default;
}

/* ═══════════ SECTION SOURCES ═══════════ */
.section-sources {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: var(--space-md);
}

.source-note {
  font-size: 13px;
  color: var(--text-dim);
  letter-spacing: 0.3px;
}

/* ═══════════ ACTIONS SUMMARY (overview badge) ═══════════ */
.alert-badge {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 12px;
  border-radius: var(--radius-sm);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.5px;
}

.alert-badge.amber {
  color: var(--amber);
  background: var(--amber-bg);
}

/* ═══════════ SECTION DIVIDER ═══════════ */
.section-divider {
  border: none;
  border-top: 1px solid var(--border-subtle);
  margin: var(--space-lg) 0;
}

/* ═══════════ VIEW HEADER ROW ═══════════ */
.view-header-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

/* ═══════════ TRENDS SIDEBAR LAYOUT ═══════════ */
.trends-layout {
  display: grid;
  grid-template-columns: 320px 1fr;
  grid-template-rows: auto 1fr;
  height: 100%;
  max-width: 1240px;
  margin: 0 auto;
  padding: var(--space-lg) var(--space-lg) 0;
  overflow: hidden;
  min-height: 0;
}

/* Phone-only picker chrome — inert on desktop, where the sidebar renders
   inline. Activated inside the 720px query below. */
.trends-mobile-trigger,
.trends-picker-backdrop,
.trends-sheet-head,
.trends-sheet-foot {
  display: none;
}

/* Mobile: the 320px sidebar + chart split collapses the chart to a
   useless sliver on a phone. Instead of permanently stacking the picker
   above the chart (and eating ~40% of the viewport), the same sidebar DOM
   is repositioned as a summoned bottom sheet — mirroring the help-tool
   trace/years pickers. A sticky summary trigger stands in for it; the
   chart owns the full column. No-op on desktop. */
@media (max-width: 720px) {
  .trends-layout {
    grid-template-columns: 1fr;
    grid-template-rows: auto auto 1fr;  /* title · trigger · chart */
    height: auto;
    min-height: 100%;
    overflow: visible;
    padding: var(--space-md) var(--space-md) 0;
  }

  /* ── Selection trigger ── */
  .trends-mobile-trigger {
    display: grid;
    position: sticky;
    top: 0;
    z-index: 4;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    gap: 10px;
    width: 100%;
    min-height: 48px;
    margin-bottom: var(--space-sm);
    padding: 9px 12px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--bg-surface);
    font-family: inherit;
    text-align: left;
    cursor: pointer;
  }
  .trends-mobile-trigger-kicker {
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 1.2px;
    text-transform: uppercase;
    color: var(--accent);
  }
  .trends-mobile-summary {
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 8px;
  }
  .trends-mobile-swatches {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    flex: 0 0 auto;
  }
  .trends-mobile-dot {
    width: 9px;
    height: 9px;
    border-radius: 50%;
  }
  .trends-mobile-more {
    font-size: 10px;
    color: var(--text-dim);
  }
  .trends-mobile-trigger-name {
    font-size: 13px;
    font-weight: 600;
    color: var(--text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .trends-mobile-trigger-name.is-empty {
    color: var(--text-dim);
    font-weight: 500;
  }
  .trends-mobile-trigger-caret {
    font-size: 16px;
    color: var(--text-dim);
  }

  /* ── Backdrop ── */
  .trends-picker-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    /* Above the bottom-nav (z-index 100) so the sheet + its Done footer
       cover it rather than being clipped behind it. */
    z-index: 120;
    background: rgba(15, 20, 30, 0.22);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s ease;
  }
  .trends-layout.is-picker-open .trends-picker-backdrop {
    opacity: 1;
    pointer-events: auto;
  }

  /* ── Sidebar → bottom sheet ── */
  .trends-sidebar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 121;
    max-height: min(82vh, 680px);
    border: 1px solid var(--border);
    border-bottom: none;
    border-radius: 12px 12px 0 0;
    background: var(--bg-panel);
    box-shadow: 0 -12px 32px rgba(15, 20, 30, 0.16);
    transform: translateY(100%);
    transition: transform 0.24s cubic-bezier(0.4, 0, 0.2, 1);
    overflow: hidden;
  }
  .trends-layout.is-picker-open .trends-sidebar {
    transform: translateY(0);
  }

  /* ── Sheet chrome ── */
  .trends-sheet-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-md);
    flex: 0 0 auto;
    padding: 14px 14px 10px;
    border-bottom: 1px solid var(--border-subtle);
  }
  .trends-sheet-kicker {
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 1.4px;
    text-transform: uppercase;
    color: var(--text-dim);
  }
  .trends-sheet-title {
    margin: 2px 0 0;
    font-size: 14px;
    font-weight: 600;
    letter-spacing: 1.5px;
    text-transform: uppercase;
    color: var(--text-primary);
  }
  .trends-sheet-close {
    width: 34px;
    height: 34px;
    flex: 0 0 auto;
    border: 0;
    border-radius: var(--radius-sm);
    background: var(--bg-surface);
    color: var(--text-secondary);
    font-family: inherit;
    font-size: 18px;
    cursor: pointer;
  }
  /* The desktop fade mask reads as a cutoff against the sticky Done
     footer inside the sheet — drop it here. */
  .trends-sidebar-scroll {
    mask-image: none;
    -webkit-mask-image: none;
  }
  .trends-sheet-foot {
    display: block;
    flex: 0 0 auto;
    padding: 10px 14px max(14px, env(safe-area-inset-bottom));
    border-top: 1px solid var(--border-subtle);
    background: var(--bg-panel);
  }
  .trends-sheet-done {
    width: 100%;
    min-height: 46px;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    border: 0;
    border-radius: var(--radius-sm);
    background: var(--accent);
    color: #fff;
    font-family: inherit;
    font-size: 13px;
    font-weight: 600;
    letter-spacing: 0.5px;
    cursor: pointer;
  }
  .trends-sheet-done-count {
    opacity: 0.82;
    font-weight: 500;
  }

  /* ── Chart owns the full column ── */
  .trends-chart-area {
    overflow: visible;
    min-height: 60vh;
  }
  .trends-charts {
    overflow-y: visible;
    /* The fade mask reads as a cut-off on a naturally-growing column. */
    mask-image: none;
    -webkit-mask-image: none;
  }
}

.trends-title {
  grid-column: 1 / -1;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--text-secondary);
  margin-bottom: var(--space-lg);
}

.trends-sidebar {
  display: flex;
  flex-direction: column;
  border-right: 1px solid var(--border-subtle);
  overflow: hidden;
}

.trends-sidebar-header {
  display: flex;
  align-items: center;
  border-bottom: 1px solid var(--border-subtle);
  background: var(--bg-surface);
}

.trends-search {
  flex: 1;
  padding: var(--space-sm) var(--space-md);
  border: none;
  background: transparent;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-primary);
  outline: none;
  min-width: 0;
}

.trends-search::placeholder {
  color: var(--text-dim);
}

.trends-sidebar-header:focus-within {
  background: var(--bg-panel);
  border-bottom-color: var(--accent);
}

.trends-clear {
  border: none;
  background: none;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-dim);
  cursor: pointer;
  padding: var(--space-xs) var(--space-sm);
  margin-right: var(--space-xs);
  border-radius: var(--radius-sm);
  transition: all 0.15s;
}

.trends-clear:hover {
  color: var(--text-secondary);
  background: var(--bg-hover);
}

.trends-optimal-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: none;
  background: none;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-dim);
  cursor: pointer;
  padding: var(--space-xs) var(--space-sm);
  border-radius: var(--radius-sm);
  transition: color 0.15s, background 0.15s;
}

.trends-optimal-toggle:hover {
  color: var(--text-secondary);
  background: var(--bg-hover);
}

.trends-optimal-dot {
  width: 10px;
  height: 6px;
  border-radius: 2px;
  background: var(--text-dim);
  opacity: 0.35;
  transition: background 0.15s, opacity 0.15s;
}

.trends-optimal-toggle.on {
  color: var(--text-secondary);
}

.trends-optimal-toggle.on .trends-optimal-dot {
  background: var(--accent, #4f7cff);
  opacity: 0.55;
}

.trends-sidebar-scroll {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-sm) 0 var(--space-2xl);
  min-height: 0;
  mask-image: linear-gradient(to bottom, black 0, black calc(100% - 32px), transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom, black 0, black calc(100% - 32px), transparent 100%);
}

.trends-sidebar-scroll::-webkit-scrollbar { width: 4px; }
.trends-sidebar-scroll::-webkit-scrollbar-track { background: transparent; }
.trends-sidebar-scroll::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }

.trends-marker-list {
  padding: 0;
}

/* ═══════════ TRENDS SIDEBAR — INSIGHTS SECTION ═══════════
   Curated patterns rendered in the sidebar above the marker checkbox list.
   Both are "what to chart" pickers — a click on an insight row writes its
   pair into the marker section's checkboxes and into the chart.

   Top-N matched priors are shown by default; "show more" expands to all 10.
   Active row expands inline with the prior detail prose.
*/

.trends-insights {
  padding: 4px 0 6px;
  font-family: var(--font-mono);
}

/* Insights is a peer of the marker groups in layout, but visually it's the
 * curated/featured section. Tint its header chevron and title in the
 * agency accent (cool tone) to mark it as "the recommended picks" without
 * adding chrome that would break alignment with the marker groups. */
.trends-insights > .ti-header .ti-chevron,
.trends-insights > .ti-header .ti-title {
  color: var(--accent);
}

.ti-header {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 6px 4px;
  border: none;
  background: none;
  font-family: var(--font-mono);
  cursor: pointer;
  text-align: left;
  border-radius: var(--radius-sm);
  transition: background 0.12s;
}

.ti-header:hover {
  background: var(--bg-hover);
}

.ti-chevron {
  font-size: 10px;
  color: var(--text-dim);
  transition: transform 0.15s;
  width: 10px;
  display: inline-flex;
  justify-content: center;
}

.trends-insights.collapsed .ti-chevron,
.trends-group.collapsed .ti-chevron {
  transform: rotate(-90deg);
}

.ti-title {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--text-secondary);
  flex: 1;
}

.ti-count {
  font-size: 10px;
  color: var(--text-dim);
  letter-spacing: 0.5px;
  font-variant-numeric: tabular-nums;
}

.ti-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: 4px;
}

.trends-insights.collapsed .ti-list,
.trends-insights.collapsed .ti-more,
.trends-group.collapsed .trends-group-list {
  display: none;
}

.ti-row {
  width: 100%;
  display: grid;
  grid-template-columns: 3px minmax(0, 1fr);
  gap: 9px;
  align-items: center;
  padding: 8px 10px 8px 8px;
  border: 1px solid transparent;
  background: transparent;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-family: var(--font-mono);
  text-align: left;
  transition: background 0.12s, border-color 0.12s;
}

.ti-row:hover {
  background: var(--bg-hover);
}

/* Leading selection bar — drawn as a filled block in the reserved 3px
 * column. Transparent on inactive rows so the layout never shifts. */
.ti-mark {
  width: 3px;
  height: 28px;
  border-radius: 2px;
  background: transparent;
  transition: background 0.12s;
  align-self: center;
}

.ti-row.tier-exploratory .ti-marker-name {
  color: var(--text-secondary);
}

.ti-row.active {
  background: var(--bg-surface);
  border-color: var(--border-subtle);
}

.ti-row.active .ti-mark {
  background: var(--accent);
}

/* Anomaly: recolor the direction arrow only. The arrow IS the direction,
 * so colouring it amber says "this direction is unexpected" without adding
 * new chrome or breaking the row's layout rhythm. */
.ti-row.tier-conflict .ti-arrow {
  color: var(--amber);
}

/* Two-column row content: marker pair on the left, meta stack on the right. */
.ti-row-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 10px;
  align-items: center;
}

.ti-pair {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.ti-marker {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.ti-swatch {
  width: 8px;
  height: 8px;
  border-radius: 2px;
  flex-shrink: 0;
}

.ti-marker-name {
  font-size: 12px;
  font-weight: 500;
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ti-meta {
  display: flex;
  flex-direction: column;
  gap: 2px;
  text-align: right;
  flex-shrink: 0;
  font-variant-numeric: tabular-nums;
  /* Reserved width — fits "UNEXPECTED" so the meta column is the same width
   * on every row, conflict or not. Non-conflict rows show their `Nmo`
   * right-aligned with empty space to the left. */
  min-width: 78px;
}

.ti-meta-top {
  display: inline-flex;
  align-items: baseline;
  justify-content: flex-end;
  gap: 6px;
  font-size: 11px;
  color: var(--text-secondary);
}

.ti-arrow {
  font-size: 10px;
  color: var(--text-dim);
}

.ti-r {
  font-weight: 500;
  color: var(--text-secondary);
}

.ti-r.strong {
  color: var(--text-primary);
  font-weight: 600;
}

.ti-r.moderate {
  color: var(--text-secondary);
}

.ti-r.weak {
  color: var(--text-dim);
  font-weight: 400;
}

.ti-meta-bottom {
  display: inline-flex;
  align-items: baseline;
  justify-content: flex-end;
  gap: 4px;
  font-size: 10px;
  color: var(--text-dim);
  letter-spacing: 0.3px;
}

.ti-n {
  white-space: nowrap;
}

/* Anomaly label — replaces the duration on conflict rows. Same slot, so the
 * row's grid stays identical to non-conflict rows. The duration moves to
 * the row tooltip and the context band when selected. */
.ti-unexpected {
  color: var(--amber);
  white-space: nowrap;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  font-weight: 600;
}

/* Drift tier — single-marker watch list rows. Visually quiet: warm-amber
 * arrow signals "moving the wrong way" without alarming; the DRIFTING tag
 * sits in the same bottom slot as UNEXPECTED so the row grid is invariant
 * across all three tiers (matched / drift / exploratory). */
.ti-row.tier-drift .ti-drift-arrow {
  color: var(--amber);
  font-size: 12px;
}
.ti-drift-eta {
  font-size: 11px;
  font-weight: 500;
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}
.ti-drift-tag {
  color: var(--amber);
  white-space: nowrap;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  font-weight: 600;
}

/* Optimizer promo card — fake-front pinned above the Insights list.
 * Compact CTA: glyph + headline + sub + arrow. Soft accent-tinted fill
 * marks it as agency / opportunity (cool axis), distinct from real
 * insight rows (which are data) and severity tags (warm axis). Lives
 * inside `.ios-notify-wrap` so the global submit listener in app.js
 * strips it; `.trends-promo-wrap` neutralizes the wrap's plan-tab
 * defaults (top margin, centered flex) which would otherwise leave a
 * gap below the Insights header.
 *
 * Copy is intentionally placeholder — owner finalizes. */
/* Specificity bump: `.ios-notify-wrap` (declared later in the file)
 * applies `margin-top: var(--space-lg)` and `justify-content: center`
 * for the Plan tab's full-width delivery card. Equal-specificity loses
 * to source order, so we anchor under `.trends-insights` to win. */
.trends-insights .trends-promo-wrap {
  margin-top: 4px;
  margin-bottom: 6px;
  display: block;
}
.trends-promo {
  width: 100%;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--accent-soft);
  background: var(--accent-soft);
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-family: var(--font-mono);
  text-align: left;
  transition: background 0.15s, border-color 0.15s, transform 0.15s;
}
.trends-promo:hover {
  background: color-mix(in oklab, var(--accent) 12%, transparent);
  border-color: color-mix(in oklab, var(--accent) 22%, transparent);
}
.trends-promo:active {
  transform: scale(0.99);
}
.trends-promo-glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  color: var(--accent);
}
.trends-promo-glyph svg {
  width: 18px;
  height: 18px;
}
.trends-promo-title {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-primary);
  letter-spacing: 0.2px;
  min-width: 0;
  /* Wrap on narrow widths rather than truncating — the headline IS the
   * card. */
  white-space: normal;
  line-height: 1.3;
}
.trends-promo-arrow {
  font-size: 14px;
  color: var(--accent);
  align-self: center;
  transition: transform 0.15s;
}
.trends-promo:hover .trends-promo-arrow {
  transform: translateX(2px);
}

/* Drift context band — same chrome as the pair band, distinguished by a
 * subtler tag treatment (no cluster pill, no confidence chip). The
 * `.tcb-cluster.drift` lives here so its color reads as amber/watch-list
 * rather than the cyan of matched clusters. */
.tcb-cluster.drift {
  color: var(--amber);
  border-color: var(--amber);
}

.ti-more {
  display: block;
  width: 100%;
  margin-top: 4px;
  padding: 6px 10px 6px 20px;
  border: none;
  background: none;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-dim);
  cursor: pointer;
  text-align: left;
  border-radius: var(--radius-sm);
  letter-spacing: 0.3px;
  transition: color 0.12s, background 0.12s;
}

.ti-more:hover {
  color: var(--text-secondary);
  background: var(--bg-hover);
}

/* Visual divider between the insights section and the marker checkbox list. */
.trends-section-divider {
  height: 1px;
  background: var(--border-subtle);
  margin: 8px 12px 4px;
}

/* ── Sparse-data footer ──────────────────────
   A single adaptive sentence-CTA under the marker list, naming the most-
   impactful missing slice of the user's inventory and opening Your Data
   on click. Designed to read as a distinct prompt, not a row — soft fill,
   no icon, wrapping sentence with the arrow inline at the end. Disappears
   once the inventory is doing useful work.
*/
.trends-expand {
  padding: 16px var(--space-md) 8px;
}
.trends-expand-cta {
  display: block;
  width: 100%;
  padding: 11px 14px;
  border: none;
  border-radius: var(--radius-sm);
  background: var(--bg-surface);
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-secondary);
  text-align: left;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.trends-expand-cta:hover {
  background: var(--accent-soft);
  color: var(--accent);
}
.trends-expand-arrow {
  display: inline-block;
  margin-left: 2px;
  color: var(--text-dim);
  transition: color 0.15s, transform 0.15s;
}
.trends-expand-cta:hover .trends-expand-arrow {
  color: var(--accent);
  transform: translateX(2px);
}

/* ═══════════ TRENDS CONTEXT BAND ═══════════
   Lives above the chart when an insight is active. The sidebar row is the
   selector; this band is the *content*. Full chart-area width gives the prose
   room to breathe in 2–3 lines instead of 6 cramped sidebar lines.
*/

.trends-context {
  flex-shrink: 0;
}

.trends-context:empty {
  display: none;
}

.trends-context-band {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px 18px;
  margin: var(--space-md) var(--space-md) 0;
  background: var(--bg-panel);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
}

/* Exploratory pairs don't carry a clinical explanation — the band shrinks
   to a thin breadcrumb so it doesn't claim more attention than it earns. */
.trends-context-band.exploratory {
  gap: 6px;
  padding: 10px 18px;
  background: var(--bg-surface);
}

/* Header row: pair on the left, meta in the middle, dismiss on the right */
.tcb-head {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}

.tcb-pair {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}

.tcb-marker {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.tcb-swatch {
  width: 10px;
  height: 10px;
  border-radius: 2px;
  flex-shrink: 0;
}

.tcb-name {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-primary);
  white-space: nowrap;
}

.tcb-arrow {
  font-size: 12px;
  color: var(--text-dim);
}

.tcb-meta {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  font-size: 11px;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.3px;
  flex: 1;
  min-width: 0;
}

.tcb-r {
  font-weight: 500;
  color: var(--text-secondary);
}

.tcb-r.strong { color: var(--text-primary); font-weight: 600; }
.tcb-r.moderate { color: var(--text-secondary); }
.tcb-r.weak { color: var(--text-dim); font-weight: 400; }

.tcb-sep { color: var(--border); }

.tcb-cluster {
  font-weight: 500;
}

.tcb-cluster.exploratory {
  font-style: italic;
}

.tcb-cluster.conflict {
  color: var(--amber);
  font-style: italic;
}

.tcb-dismiss {
  margin-left: auto;
  width: 22px;
  height: 22px;
  border: none;
  background: transparent;
  font-family: var(--font-mono);
  font-size: 16px;
  line-height: 1;
  color: var(--text-dim);
  cursor: pointer;
  border-radius: var(--radius-sm);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background 0.12s, color 0.12s;
  flex-shrink: 0;
}

.tcb-dismiss:hover {
  background: var(--bg-hover);
  color: var(--text-primary);
}

/* Body: prose, conflict callout, footnote */
.tcb-prose {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--text-secondary);
  max-width: 72ch; /* keep line length readable on wide chart areas */
}

.tcb-conflict {
  font-size: 12px;
  color: var(--amber);
  background: rgba(245, 158, 11, 0.06);
  border: 1px solid rgba(245, 158, 11, 0.2);
  border-radius: var(--radius-sm);
  padding: 9px 12px;
  line-height: 1.5;
}

.tcb-conflict-label {
  font-weight: 600;
  margin-right: 4px;
}

.tcb-foot {
  display: flex;
  align-items: baseline;
  gap: 0;
  font-size: 10px;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--text-dim);
}

.tcb-confidence {
  font-weight: 600;
}

.tcb-confidence.high { color: var(--text-secondary); }
.tcb-confidence.moderate { color: var(--text-dim); }

.trends-group {
  margin-bottom: var(--space-sm);
}

.trends-group-list {
  display: flex;
  flex-direction: column;
  margin-top: 4px;
}

.trends-marker {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px var(--space-md);
  font-size: 12px;
  cursor: pointer;
  transition: background 0.1s;
}

.trends-marker:hover {
  background: var(--bg-hover);
}

.trends-marker input[type="checkbox"] {
  width: 13px;
  height: 13px;
  margin: 0;
  accent-color: var(--accent);
  flex-shrink: 0;
}

.trends-swatch {
  width: 8px;
  height: 8px;
  border-radius: 2px;
  flex-shrink: 0;
  opacity: 0.3;
  transition: opacity 0.15s;
}

.trends-marker input:checked ~ .trends-swatch {
  opacity: 1;
}

.trends-marker-name {
  color: var(--text-secondary);
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: color 0.15s;
}

.trends-marker input:checked ~ .trends-marker-name {
  color: var(--text-primary);
}

.trends-marker-latest {
  font-size: 11px;
  color: var(--text-dim);
  white-space: nowrap;
  flex-shrink: 0;
}

.trends-chart-area {
  position: relative;
  min-height: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.trends-charts {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: var(--space-md) var(--space-md) var(--space-2xl);
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  mask-image: linear-gradient(to bottom, black 0, black calc(100% - 32px), transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom, black 0, black calc(100% - 32px), transparent 100%);
}

.trends-charts::-webkit-scrollbar { width: 4px; }
.trends-charts::-webkit-scrollbar-track { background: transparent; }
.trends-charts::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }

.trends-multiple {
  flex: 0 0 220px;
  display: grid;
  grid-template-rows: auto 1fr;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  background: var(--bg-panel);
  overflow: hidden;
}

.trends-charts.single-group .trends-multiple {
  flex: 1 1 auto;
}

.trends-multiple-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-sm) var(--space-md);
  border-bottom: 1px solid var(--border-subtle);
  background: var(--bg-surface);
}

.trends-multiple-markers {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md);
  align-items: center;
  min-width: 0;
}

.trends-multiple-marker {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  color: var(--text-primary);
}

.trends-multiple-swatch {
  width: 8px;
  height: 8px;
  border-radius: 2px;
  flex-shrink: 0;
}

.trends-multiple-name {
  font-weight: 500;
}

.trends-multiple-unit {
  font-size: 10px;
  color: var(--text-dim);
  letter-spacing: 0.5px;
  flex-shrink: 0;
  white-space: nowrap;
}

.trends-multiple-meta {
  display: inline-flex;
  align-items: center;
  gap: var(--space-md);
  flex-shrink: 0;
}

.trends-multiple-header .trends-optimal-toggle {
  padding: 2px 6px;
  font-size: 10px;
  letter-spacing: 0.3px;
  color: var(--text-dim);
}

.trends-multiple-header .trends-optimal-toggle .trends-optimal-dot {
  width: 8px;
  height: 4px;
}

.trends-multiple-chart {
  position: relative;
  padding: var(--space-sm) var(--space-md);
  min-height: 0;
}

/* ─── Active-insight temporal rail (in context band) ────────────
   The window for the active insight is shown once, in the context band
   above the charts. The rail's left/right margins are set in JS so the
   bar inside aligns with the first chart's plot area below. */
.tcb-rail-row {
  display: grid;
  grid-template-columns: max-content 1fr max-content;
  align-items: center;
  gap: 12px;
  margin-top: 12px;
  font-size: 10px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--text-dim);
  font-weight: 600;
}

.tcb-rail-label {
  display: flex;
  align-items: center;
  gap: 6px;
}
.tcb-rail-label::before {
  content: "▸";
  color: rgb(99, 102, 241);
  font-size: 9px;
  letter-spacing: 0;
}

.tcb-rail {
  position: relative;
  height: 8px;
  /* margin-left/right set by JS to align with first chart's plot area */
}

.tcb-rail::before,
.tcb-rail::after {
  content: "";
  position: absolute;
  top: 50%;
  width: 1px;
  height: 8px;
  background: var(--text-dim);
  opacity: 0.45;
  transform: translateY(-50%);
}
.tcb-rail::before { left: 0; }
.tcb-rail::after  { right: 0; }

.tcb-rail-line {
  position: absolute;
  top: 50%;
  left: 1px;
  right: 1px;
  height: 1px;
  background: var(--border);
  transform: translateY(-50%);
}

.tcb-rail-bar {
  position: absolute;
  top: 50%;
  height: 7px;
  transform: translateY(-50%);
  background: rgba(99, 102, 241, 0.85);
  /* left/right percentages set by JS */
}
.tcb-rail-bar::before,
.tcb-rail-bar::after {
  content: "";
  position: absolute;
  top: -3px;
  bottom: -3px;
  width: 1px;
  background: rgb(99, 102, 241);
}
.tcb-rail-bar::before { left: 0; }
.tcb-rail-bar::after  { right: 0; }

.tcb-rail-dates {
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}

.trends-empty {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  color: var(--text-dim);
  letter-spacing: 0.3px;
  pointer-events: none;
}

/* ═══════════ TREND CHART ═══════════ */
.trend-chart-wrap {
  height: 320px;
  margin-bottom: var(--space-md);
}

/* ═══════════ TREND LEGEND ═══════════ */
.trend-legend {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.trend-legend-item {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: 6px 0;
  border-bottom: 1px solid var(--border-subtle);
  font-size: 12px;
}

.trend-legend-item:last-child {
  border-bottom: none;
}

.trend-legend-swatch {
  width: 12px;
  height: 3px;
  border-radius: 2px;
  flex-shrink: 0;
}

.trend-legend-name {
  color: var(--text-secondary);
  min-width: 120px;
}

.trend-legend-latest {
  font-weight: 500;
  color: var(--text-primary);
}

.trend-legend-latest.out-of-range {
  color: var(--amber);
}

.trend-legend-unit {
  font-size: 12px;
  color: var(--text-dim);
  font-weight: 400;
}

.trend-legend-range {
  font-size: 12px;
  color: var(--text-dim);
  margin-left: auto;
}

/* ═══════════ RED FLAGS STRIP ═══════════ */
.red-flags-strip {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-xs) var(--space-md);
  padding: var(--space-sm) var(--space-md);
  margin-bottom: var(--space-xl);
  border-radius: var(--radius-sm);
  background: var(--amber-bg);
  border-left: 3px solid var(--amber);
}

.red-flags-count {
  font-size: 13px;
  font-weight: 600;
  color: var(--amber);
  white-space: nowrap;
}

.red-flags-list {
  font-size: 13px;
  color: var(--text-secondary);
}

/* ═══════════ BIOMARKER CATEGORY ═══════════ */
.biomarker-category {
  transition: background 0.3s;
}

.biomarker-category.category-highlight {
  background: var(--accent-soft);
  border-radius: var(--radius-sm);
  padding: var(--space-md);
  margin-left: calc(-1 * var(--space-md));
  margin-right: calc(-1 * var(--space-md));
}

.category-flag-dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--amber);
  margin-left: 6px;
  vertical-align: middle;
}

/* Nominal markers in category — more compact */
.biomarker-row.nominal-row {
  opacity: 0.7;
}

.biomarker-row.nominal-row .biomarker-value {
  font-weight: 400;
}

/* ═══════════ MARKER → ACTION LINKS ═══════════ */
.marker-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  grid-column: 1 / -1;
  padding-top: 2px;
}

.marker-action-link {
  font-size: 12px;
  padding: 2px 8px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  text-decoration: none;
  transition: background 0.15s;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  color: var(--text-secondary);
}

.marker-action-link:hover {
  background: var(--bg-hover);
  border-color: var(--accent);
  color: var(--accent);
}

.marker-action-link.red { color: var(--red); border-color: var(--red-bg); }
.marker-action-link.red:hover { background: var(--red-bg); }
.marker-action-link.amber { color: var(--amber); border-color: var(--amber-bg); }
.marker-action-link.amber:hover { background: var(--amber-bg); }
.marker-action-link.green { color: var(--green); border-color: var(--green-bg); }
.marker-action-link.green:hover { background: var(--green-bg); }
.marker-action-link.accent { color: var(--accent); border-color: var(--accent-soft); }
.marker-action-link.accent:hover { background: var(--accent-soft); }

.watch-name {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--text-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.watch-value {
  font-size: 14px;
  font-weight: 500;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.watch-unit {
  font-size: 10px;
  color: var(--text-dim);
  margin-left: 4px;
  font-weight: 400;
}

/* ═══════════ OVERVIEW READOUT GROUP LABELS ═══════════ */
.readouts-group-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--text-dim);
  padding: 0 4px;
  margin-bottom: 4px;
  text-align: left;
}

/* ═══════════ FLAGGED BIOMARKER ROW (wrap for action links) ═══════════ */
.biomarker-row.flagged {
  flex-wrap: wrap;
}

/* ═══════════ ACTION HIGHLIGHT (cross-nav from bloodwork) ═══════════ */
/* The cross-nav target is any plan row by id — hero card, backlog row, or a
   secured / dismissed row (all `.plan-row` / `.plan-card` now). A transient
   2s background flash on arrival; scoped so the row's own borders/padding are
   untouched (the inset shift is only safe on the standalone .action-item that
   no longer exists, so plan rows just take the soft fill). */
.plan-row.action-highlight,
.plan-card.action-highlight {
  background: var(--accent-soft);
  transition: background 0.3s;
}

/* ═══════════ WELCOME / UPLOAD SCREEN ═══════════ */
/* Two-column layout: the figure occupies the left column (rendered
   via the `.welcome-model-stage` canvas with a laterally-panned
   camera; see `csv` / `apple` / `genetic` / `complete` presets in
   wireframe-model-scene.js) and the card occupies the right column.
   The grid only positions the card — `.welcome-model-stage` stays
   absolute and ignores it. `max-width` caps the layout so on wide
   external monitors the card doesn't drift to the far right and the
   gap between figure and card doesn't sprawl: both are anchored to
   a centered 1400px box, with empty viewport space on either side
   on screens wider than that. */
.welcome {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  height: 100%;
  max-width: 1400px;
  margin: 0 auto;
  padding: var(--space-xl);
  transition: opacity 320ms ease;
  overflow: hidden;
  /* `--w-open` offsets the card's whole entrance (panel + the staggered
     chrome below) so on desktop it begins only after the wireframe model
     has had its solo materializing beat — model first, then the setup card.
     Mobile (model hidden) overrides this to 0 so the chrome staggers from
     the start, the timing we want there. Set per-breakpoint, not here. */
  --w-open: 0ms;
}
.welcome.is-leaving { opacity: 0; }

/* Back / escape hatch. Anchored to the top-left CORNER OF THE CARD (not the
   viewport) so it travels with the flow and reads as a step-back affordance,
   adjacent to the content it acts on — on desktop the card sits right-of-center,
   so a viewport-corner button landed on the opposite side and felt divorced.
   Step 1 has nowhere to step back to, so there it exits to the landing page
   (wireBack). Absolute within the relatively-positioned card; fades in with the
   rest of the chrome. */
.welcome-back {
  position: fixed;
  top: clamp(16px, 3vh, 28px);
  left: clamp(16px, 3vw, 32px);
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  background: transparent;
  border: 0;
  padding: 6px 4px;
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.6px;
  text-transform: lowercase;
  color: var(--text-dim);
  z-index: 2;
  opacity: 0;
  animation: welcome-fade-rise 900ms cubic-bezier(.2, .7, .2, 1) calc(var(--w-open) + 1900ms) forwards;
  transition: color 200ms ease;
}
.welcome-back:hover { color: var(--text-secondary); }
.welcome-back:hover .welcome-back-arrow { transform: translateX(-2px); }
.welcome-back-arrow {
  font-size: 13px;
  letter-spacing: 0;
  transition: transform 200ms ease;
}

.welcome-card {
  position: relative;
  z-index: 2;
  grid-column: 2;
  justify-self: center;
  text-align: center;
  max-width: 440px;
  width: 100%;
  min-height: 560px;
  max-height: min(80vh, 660px);
  display: flex;
  flex-direction: column;
  background: rgba(250, 252, 255, 0.52);
  border: 1px solid rgba(255, 255, 255, 0.55);
  border-radius: 20px;
  box-shadow: 0 18px 54px rgba(8, 24, 40, 0.07);
  backdrop-filter: blur(14px) saturate(120%);
  -webkit-backdrop-filter: blur(14px) saturate(120%);
  padding: 34px 24px 22px;
  overflow: hidden;
}

/* Desktop opening beat: the wireframe model materializes alone first (its
   solo moment — the role the AESCLE logo plays on phones, where the figure
   is hidden), then the setup card arrives as a distinct second beat. We
   give the card its own entrance and offset the whole chrome stagger by
   `--w-open` so the two never overlap. Scoped to >720px because the model
   only exists there; the phone path keeps its original stagger untouched.
   `backwards` fill (no static opacity) holds the card hidden through the
   delay but releases to its natural opacity afterward — so reduced-motion
   (animation:none) leaves the card fully visible rather than stuck at 0. */
@media (min-width: 721px) {
  .welcome { --w-open: 1550ms; }
  .welcome-card {
    animation: welcome-card-in 700ms cubic-bezier(.2, .7, .2, 1) var(--w-open) backwards;
  }
}

/* Cinematic open: logo settles its letter-spacing as it fades in,
   subtitle + progress thread + slot stagger in below. The card stays
   anchored across acts — only the inner slot swaps later. */
.welcome-logo {
  font-size: 28px;
  font-weight: 700;
  letter-spacing: 8px;
  color: var(--accent);
  margin-bottom: var(--space-sm);
  opacity: 0;
  animation: welcome-logo-in 1500ms cubic-bezier(.2, .7, .2, 1) calc(var(--w-open) + 350ms) forwards;
}

.welcome-subtitle {
  font-size: 12px;
  color: var(--text-dim);
  letter-spacing: 1px;
  margin-bottom: 16px;
  opacity: 0;
  animation: welcome-fade-rise 900ms cubic-bezier(.2, .7, .2, 1) calc(var(--w-open) + 1300ms) forwards;
}

.welcome-progress {
  display: flex;
  justify-content: center;
  gap: 12px;
  margin-bottom: 20px;
  opacity: 0;
  animation: welcome-fade-rise 900ms cubic-bezier(.2, .7, .2, 1) calc(var(--w-open) + 1900ms) forwards;
}

.welcome-progress-tick {
  position: relative;
  width: 34px;
  height: 6px;
  background: rgba(153, 164, 180, 0.35);
  border: none;
  border-radius: 999px;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: default;
  transition: background 280ms ease, box-shadow 280ms ease, transform 280ms ease, opacity 280ms ease;
  opacity: 0.8;
}
.welcome-progress-tick::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: var(--green);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 360ms cubic-bezier(.2, .7, .2, 1);
  pointer-events: none;
}
.welcome-progress-tick.is-active {
  background: rgba(255, 255, 255, 0.18);
  box-shadow: 0 0 0 1.5px rgba(140, 154, 172, 0.72);
  transform: translateY(-1px);
  opacity: 1;
}
.welcome-progress-tick.is-complete {
  /* No background fill — the green is the ::before overlay, grown to full on
     complete. A partial pill then fills from its current point STRAIGHT to
     full instead of retracting to zero first (which read as a reset). */
  box-shadow: 0 0 0 1px rgba(16, 185, 129, 0.22);
  opacity: 1;
}
.welcome-progress-tick.is-complete::before {
  transform: scaleX(1);
}
/* Completed by accepting the defaults / skipping the upload — still forward
   progress, but quieter than a data-backed step. The green fill grows to full
   length (the step IS done) but at reduced intensity, and the confirming ring
   softens. Keeps the pill honest about how much the user actually contributed
   without reading as "unfinished". */
.welcome-progress-tick.is-complete.is-default {
  box-shadow: 0 0 0 1px rgba(16, 185, 129, 0.10);
}
.welcome-progress-tick.is-complete.is-default::before {
  opacity: 0.4;
}
/* Branch-ask in progress: the step isn't done, but the user is mid-way
   through its reconciliation ask — a partial green fill keeps the pill
   reading as forward motion rather than a stall. The fill fraction is set
   per-call via --tick-fill (see markTickPartial): ~half when the user
   uploaded a file and is only missing a key value, less when they skipped
   and provided nothing. A partial pill is always the CURRENT step, so it
   keeps the active track (don't override `background` back to gray — the
   green ::before fill overlays the active track) and the active ring. */
.welcome-progress-tick.is-partial {
  opacity: 1;
}
.welcome-progress-tick.is-partial::before {
  transform: scaleX(var(--tick-fill, 0.5));
}

/* Revisit affordance: any pill the user has been at-or-past becomes
   clickable. Lifts subtly on hover so the user knows it's hot, and a
   small floating chip surfaces the destination — `↶ Bloodwork`,
   `↶ Apple Health`, `↶ Genome` — drawn from the data-name attribute
   set in the welcome HTML. The current step's pill is never
   `is-clickable` even though it has `is-active` — you can't navigate
   to where you already are. */
.welcome-progress {
  /* Allow tooltip overflow above the pills. */
  overflow: visible;
}
.welcome-progress-tick.is-clickable {
  cursor: pointer;
}
.welcome-progress-tick.is-clickable:hover,
.welcome-progress-tick.is-clickable:focus-visible {
  transform: translateY(-2px);
  box-shadow: 0 0 0 1px rgba(8, 145, 178, 0.45), 0 4px 10px rgba(8, 145, 178, 0.18);
  outline: none;
}
.welcome-progress-tick.is-clickable.is-complete:hover,
.welcome-progress-tick.is-clickable.is-complete:focus-visible {
  box-shadow: 0 0 0 1px rgba(5, 150, 105, 0.55), 0 4px 10px rgba(5, 150, 105, 0.18);
}
.welcome-progress-tick.is-clickable::after {
  content: '↶ ' attr(data-name);
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%) translateY(2px);
  font-size: 9px;
  letter-spacing: 0.6px;
  color: var(--text-secondary);
  background: rgba(255, 255, 255, 0.94);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 3px 7px;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 180ms ease, transform 180ms ease;
  text-transform: uppercase;
  box-shadow: 0 4px 10px rgba(10, 28, 44, 0.06);
}
.welcome-progress-tick.is-clickable:hover::after,
.welcome-progress-tick.is-clickable:focus-visible::after {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}
@media (prefers-reduced-motion: reduce) {
  .welcome-progress-tick.is-clickable:hover,
  .welcome-progress-tick.is-clickable:focus-visible {
    transform: none;
  }
  .welcome-progress-tick.is-clickable::after { transition: none; }
}

.welcome-slot {
  /* Resting state is fully visible. The slot is the "content" — it
     fades in straight (no rise) so it feels like it's appearing rather
     than arriving from somewhere. Chrome above (logo, subtitle,
     progress) keeps its rise; the content does not. `backwards` fill
     holds opacity:0 during the delay so the slot doesn't flash before
     its turn. We intentionally do NOT use `forwards` — the post-fill
     would override the .is-leaving rule below and block the crossfade. */
  animation: welcome-slot-fade-in 1000ms ease calc(var(--w-open) + 2400ms) backwards;
  transition: opacity 500ms ease;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Top-aligned content with a consistent gap under the progress
     thread. We used to `safe center` the act, but with the card's
     560px min-height the centering left a 100px hole between the
     progress and the prompt whenever the act was short (every upload
     act). Pinning to the top + a fixed padding gives every act the
     same vertical rhythm; tall acts (lifestyle) scroll from the top
     instead of getting trapped above the viewport. */
  justify-content: flex-start;
  padding-top: var(--space-md);
  /* Slim, low-contrast scrollbar. `stable both-edges` reserves room on
     both sides so the content stays centered whether or not the bar is
     actually visible — no shifting between scrolling and non-scrolling
     acts. */
  scrollbar-gutter: stable both-edges;
  scrollbar-width: thin;
  scrollbar-color: rgba(140, 154, 172, 0.32) transparent;
}
.welcome-slot::-webkit-scrollbar {
  width: 6px;
}
.welcome-slot::-webkit-scrollbar-track {
  background: transparent;
}
.welcome-slot::-webkit-scrollbar-thumb {
  background: rgba(140, 154, 172, 0.32);
  border-radius: 3px;
}
.welcome-slot::-webkit-scrollbar-thumb:hover {
  background: rgba(140, 154, 172, 0.52);
}

/* Crossfade between acts: pure opacity, no transform. The chrome stays
   anchored; only the content swaps in place. */
.welcome-slot.is-leaving {
  opacity: 0;
}

/* ── Welcome act layout ── */

.welcome-act-prompt {
  font-size: 13px;
  line-height: 1.55;
  /* Softer than a heading — reads as a calm description/intro for the step,
     supporting the inputs below rather than competing with the age hero above. */
  color: var(--text-dim);
  /* Shared "space below the description" for every data-setup act — the single
     source of the prompt→content gap, so every step matches (no per-act
     margin-top additions). Bumped from 14px so inputs sit lower under it. */
  margin: 0 0 22px;
  letter-spacing: 0.2px;
  text-align: center;
}

.welcome-act {
  width: 100%;
  max-width: 100%;
}
.welcome-act-prompt-aside {
  color: var(--text-dim);
  font-size: 12px;
  letter-spacing: 0.4px;
}

.welcome-act-footer {
  margin-top: var(--space-sm);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-md);
}

.welcome-act-footer-apple {
  gap: var(--space-xl);
}

/* Quiet variant for the secondary footer actions on each act —
   smaller, dimmer, no visual weight competing with the dropzone. */
.upload-help-quiet {
  margin-top: 0;
  text-align: center;
}
.upload-help-quiet > summary {
  font-size: 9px;
  letter-spacing: 0.6px;
  color: var(--text-dim);
  text-transform: uppercase;
  list-style: none;
  opacity: 0.75;
}
.upload-help-quiet > summary:hover { opacity: 1; }
.upload-help-quiet > summary::-webkit-details-marker { display: none; }
.upload-help-quiet > summary::before {
  content: '+ ';
  color: var(--text-dim);
  margin-right: 2px;
}
.upload-help-quiet[open] > summary::before { content: '− '; }

.welcome-btn-quiet {
  font-size: 9px;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  padding: 2px 6px;
  opacity: 0.75;
}
.welcome-btn-quiet:hover { opacity: 1; }

@keyframes welcome-logo-in {
  from { opacity: 0; letter-spacing: 12px; }
  to   { opacity: 1; letter-spacing: 8px; }
}
@keyframes welcome-fade-rise {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes welcome-slot-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes welcome-card-in {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .welcome-card,
  .welcome-logo,
  .welcome-subtitle,
  .welcome-progress,
  .welcome-readout-card,
  .welcome-slot,
  .welcome-back {
    animation: none;
    opacity: 1;
    transform: none;
  }
  /* Readout-stage too, but keep its translateX(-50%) centering transform. */
  .welcome-readout-stage {
    animation: none;
    opacity: 1;
  }
  .welcome-slot,
  .welcome {
    transition: none;
  }
}

/* ── Mobile shell ──
   The desktop layout is a 2-col grid (wireframe figure left, glass card
   right) anchored to a 1400px box. On a phone there's no room for two
   columns: the figure is decorative and the 440px card overflows. Drop
   to a single column, hide the figure (also skips the three.js cost),
   and let the card fill the viewport — no glass effect, no shadow, no
   max-height. Demographics rows switch from label/value side-by-side
   to label-above-input so values stop clipping, and inputs go to 16px
   to suppress iOS focus-zoom. */
@media (max-width: 720px) {
  /* Scroll container for the phone flow. The app shell locks `.app`/body to
     the viewport and `#welcome-pane` (.tab-content) clips overflow, so on
     DESKTOP the inner `.welcome-slot` scrolls. On phones the slot goes
     overflow:visible and the full-bleed card grows downward — which left a
     tall act (a long manual-entry act, or one carrying the iOS provider rail
     + mode-switch links below the tuners) with nowhere to scroll: its tail
     was trapped under the fixed footer. Make the pane itself the scroll
     container here and let `.welcome` grow past the viewport. Desktop is
     untouched (the pane keeps .tab-content's overflow:hidden there). */
  #welcome-pane {
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
  }
  .welcome {
    grid-template-columns: 1fr;
    padding: 0;
    overflow: visible;
    /* height:100% (base) → auto here so the grid grows with the card rather
       than capping at one viewport; min-height keeps a short act full-bleed. */
    height: auto;
    min-height: 100%;
    /* Anchor the card to the top, not center. The card is min-height:100dvh
       here; once its content (e.g. an expanded family-history disclosure)
       outgrows the grid track, the inherited align-items:center would center
       the overflow in BOTH directions, shoving the logo/readout up off-screen.
       Top-aligning keeps everything above pinned and grows only downward. */
    align-items: start;
  }
  .welcome-model-stage {
    display: none;
  }
  /* (Readout stage/card phone swap lives co-located with its base rules in
     the .welcome-readout section, so it wins the cascade.) */
  .welcome-card {
    grid-column: 1;
    max-width: none;
    width: 100%;
    min-height: 100dvh;
    max-height: none;
    background: transparent;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    padding: 56px 20px 24px;
  }
  .welcome-subtitle {
    display: none;
  }
  /* The subtitle owns the 1300ms entrance beat on desktop. It's hidden on
     phones, so pull the rest of the stagger up by one beat — otherwise the
     logo→progress handoff has a dead ~600ms gap where the subtitle used to
     fade in. Keeps an even cadence: logo settle → progress → readout → slot.
     (Readout-card's matching mobile delay is set in its own block below.) */
  .welcome-progress,
  .welcome-back {
    animation-delay: 1300ms;
  }
  /* The card goes full-viewport here (min-height:100dvh, transparent bg) and is
     a later-in-DOM sibling sharing the back button's z-index:2 — so it paints
     over the fixed button's top-left hit area. Lift the button above the card so
     taps land. (Desktop is unaffected: the card is centered/narrow and never
     overlaps it.) */
  .welcome-back {
    z-index: 3;
  }
  .welcome-slot {
    overflow-y: visible;
    scrollbar-gutter: auto;
    /* Top-align (not center) so every act's prompt pins to the same Y under
       the age hero. Centering keyed the prompt's position to each act's total
       height, so a short act (genome = one dropzone) dropped the prompt far
       lower than a tall one (demographics). Mirrors the desktop decision — see
       the base .welcome-slot comment. */
    justify-content: flex-start;
    padding-top: var(--space-xl);
    padding-bottom: calc(80px + var(--safe-bottom));
    animation-delay: 2100ms;
  }
  .welcome-slot.is-branch-transition {
    transition-duration: 720ms;
  }
  .welcome-progress-tick::before {
    transition-duration: 520ms;
  }
  .welcome-act-footer {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    padding: 12px max(20px, var(--safe-right)) max(12px, var(--safe-bottom)) max(20px, var(--safe-left));
    align-items: center;
    background: rgba(250, 252, 255, 0.92);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border-top: 1px solid var(--border);
  }
  .welcome-act-footer:not(.welcome-act-controls) .welcome-btn-continue {
    width: 100%;
    max-width: 280px;
    min-height: 44px;
    font-size: 12px;
  }
  /* Terms act only: the footer is fixed at the viewport bottom (above) and the
     slot top-pins, so without this the short gate strands at the top with a
     void below. Grow the act to fill the slot and center the gate between the
     progress pills and the pinned button. */
  .welcome-act-terms {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: center;
  }
  .welcome-act-footer.welcome-act-controls .welcome-btn-continue,
  .welcome-act-footer.welcome-act-controls .welcome-btn-skip {
    flex: 1;
    min-height: 44px;
    font-size: 12px;
  }

  .demographics-row {
    grid-template-columns: 1fr;
    gap: 6px;
  }
  .demographics-row-input-wrap {
    justify-content: flex-start;
    width: 100%;
  }
  .welcome input,
  .welcome select,
  .welcome textarea {
    font-size: 16px;
  }
  .demographics-row-input-wrap input[type="number"],
  .demographics-row-input-wrap input[type="date"],
  .demographics-row-input-wrap select {
    min-height: 44px;
    padding: 8px 10px;
    border-color: var(--border);
    background: rgba(255, 255, 255, 0.6);
  }
  .demographics-row-input-wrap input[type="date"] {
    width: 100%;
  }
  .demographics-row-input-wrap select {
    width: 100%;
    min-width: 0;
  }
  .demographics-row-input-wrap input[type="number"] {
    flex: 1;
    width: auto;
    text-align: left;
  }
  .demographics-row-input-wrap-pair {
    gap: 8px;
  }
  /* Roomier tap targets for the chip groups on touch; the wrap already
     left-aligns via the .demographics-row-input-wrap flex-start override.
     Family-history chips share the same wrapping-cloud grammar, so they get
     the identical touch sizing — otherwise they read smaller than the
     sex/ethnicity chips right above them. */
  .demographics-row-chips .demo-chip,
  .demographics-row-fh .fh-chip {
    min-height: 38px;
    padding: 8px 14px;
    font-size: 12px;
  }
  /* Comfortable tap target for the disclosure toggle on touch. */
  .fh-summary {
    min-height: 44px;
  }
  /* Lifestyle (step 2) pills are fused segmented bars (nowrap), so they take
     the same touch HEIGHT + font for cross-step consistency but keep tighter
     horizontal padding — the wider 14px would overflow a 2–3 segment bar plus
     its trailing unit on the narrowest phones. */
  .calibrate-row-pills .calibrate-pill {
    min-height: 38px;
    padding: 8px 10px;
    font-size: 12px;
  }

  /* Phone-primary bloodwork / vitals acts (welcome-act-manual-first). Two
     mutually-exclusive modes occupy the same space — the key-field tuner rows
     (manual, the lead: phone users have a number in their head) and the file
     dropzone (a PDF / export carries every value the tuners ask for, so it
     REPLACES them, never sits below). A quiet centered switch door toggles
     between them. These rules only ever apply at this breakpoint — the markup
     is JS-gated to isMobileOnboarding() — so desktop can't see them. Tuner-row
     rhythm matches the branch ask. */
  .welcome-act-manual-first .welcome-act-links {
    margin-top: var(--space-sm);
  }
  /* Mobile manual-first leads with the plain-language dek and drops the "i"
     tooltip — it's a hover affordance with no hover here, and the dek already
     does the demystifying. Desktop (the branch-ask) keeps both. */
  .welcome-act-manual-first .tuner-info {
    display: none;
  }

  /* The mode-toggle door — centered quiet link on a hairline boundary, the
     "or, the other way" affordance shared by both modes. */
  .welcome-act-manual-first .welcome-act-switch {
    /* No divider anymore, so the secondary door leans on whitespace alone for
       separation from the last input — push it well clear. */
    margin-top: var(--space-2xl);
    text-align: center;
  }
  .welcome-act-manual-first .welcome-act-switch .welcome-link-quiet {
    text-decoration: underline;
    text-decoration-color: rgba(107, 114, 128, 0.42);
    text-underline-offset: 3px;
    font-weight: 500;
    letter-spacing: 0.2px;
  }
}

/* ─── CALIBRATION transition ─────────────────────────────────────
   Final beat of onboarding: welcome card → status readout → hairline
   that settles at the topbar's separator Y, where the topbar's own
   border-bottom takes over. Driven by welcome.js#runCalibration. */

.welcome-slot.is-leaving-final {
  opacity: 0;
  transform: scale(0.985);
  transition: opacity 240ms ease, transform 240ms ease;
}
.welcome-back.is-leaving-final {
  opacity: 0;
  transition: opacity 200ms ease;
}
/* The LE hero (desktop, figure-anchored) fades on the same beat as the 3D
   model — scene.fadeOut(760, 220) — so the stage clears as one before the
   calibration manifest streams in. */
.welcome-readout-stage.is-leaving-final {
  opacity: 0;
  transition: opacity 340ms ease;
}
/* Mobile only (the card readout is desktop-hidden): hold the already-receded
   LE figure receded through the terms→dashboard closeout. The agree handler
   adds this before dropping .is-terminal so the recede never reverses — no
   transition here, so it pins the hidden state instantly rather than springing
   the figure back in. */
.welcome-readout-card.is-leaving-final {
  opacity: 0;
  max-height: 0;
  margin: 0;
  overflow: hidden;
  pointer-events: none;
}

/* Subtitle becomes the instrument's status readout. Same spacing/weight
   as a section header so it reads as a label, not a tagline. The caret
   is the tell — this is being typed live. */
.welcome-subtitle.welcome-status {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 1.5px;
  color: var(--text-secondary);
  text-transform: uppercase;
  /* Cancel the welcome-fade-rise animation in case it hasn't finished. */
  animation: none;
  opacity: 1;
  transform: none;
}
.welcome-subtitle.welcome-status::after {
  content: '_';
  display: inline-block;
  margin-left: 3px;
  color: var(--accent);
  animation: cal-cursor-blink 700ms steps(2) infinite;
}
.welcome-subtitle.welcome-status.is-typed::after {
  animation: cal-cursor-fade 280ms ease 120ms forwards;
}
@keyframes cal-cursor-blink {
  50% { opacity: 0; }
}
@keyframes cal-cursor-fade {
  to { opacity: 0; }
}

/* Thread pulse — single beat that confirms the three layers landed.
   Each tick briefly thickens vertically and shifts to green (the
   instrument's "all systems nominal" tone) before settling back to
   accent. Staggered so it reads as a sweep, not a flash. */
.welcome-progress.is-pulsing .welcome-progress-tick {
  animation: cal-tick-pulse 480ms cubic-bezier(.2, .7, .2, 1) both;
}
.welcome-progress.is-pulsing .welcome-progress-tick[data-step="1"] { animation-delay: 0ms;  }
.welcome-progress.is-pulsing .welcome-progress-tick[data-step="2"] { animation-delay: 70ms; }
.welcome-progress.is-pulsing .welcome-progress-tick[data-step="3"] { animation-delay: 140ms; }
@keyframes cal-tick-pulse {
  0%   { transform: scaleY(1); box-shadow: 0 0 0 1.5px rgba(140, 154, 172, 0.72); }
  35%  { transform: scaleY(2.2); box-shadow: 0 0 0 1px rgba(16, 185, 129, 0.24); }
  100% { transform: scaleY(1); box-shadow: 0 0 0 1.5px rgba(140, 154, 172, 0.72); }
}

/* Desktop closeout: the card slides from its right column to viewport center
   as the figure + hero fade, so the manifest + collapse play center-stage. The
   shift is measured in JS (finish) into --center-shift; the collapse transform
   below keeps it so the card converges into the (already viewport-centered)
   hairline rather than collapsing off to the right. */
.welcome-card.is-centering {
  transition: transform 620ms cubic-bezier(.4, 0, .2, 1);
  transform: translateX(var(--center-shift, 0px));
}

.welcome-card.is-collapsing {
  transition: opacity 320ms ease 80ms,
              transform 360ms cubic-bezier(.4, 0, 0.2, 1) 80ms;
  opacity: 0;
  /* Keep the centering shift so the vertical collapse stays put at center. */
  transform: translateX(var(--center-shift, 0px)) scaleY(0.04);
  /* Origin at horizontal center so the card converges into the same
     Y where we placed the hairline. */
  transform-origin: center center;
  pointer-events: none;
}

/* The hairline that becomes the topbar separator. JS sets initial
   top + width to match the card's resting geometry; toggling
   .is-extended runs the transition to viewport-wide at y=56px. */
.calibration-rule {
  position: fixed;
  left: 50%;
  height: 1px;
  background: var(--border);
  transform: translateX(-50%);
  transition: width 460ms cubic-bezier(.4, 0, 0.2, 1),
              top 460ms cubic-bezier(.4, 0, 0.2, 1);
  z-index: 200;
  pointer-events: none;
}
.calibration-rule.is-extended {
  width: 100vw;
  top: 56px;
}

/* Calibration manifest — replaces the slot contents after CALIBRATED
   types in. Reads as a printed receipt of what the engine just composed:
   citation rows (LIFE TABLE / SURVIVAL CURVE / …) plus live cohort and
   loaded-file counts. Each row streams in from below; the final
   PROFILE READY row is the cue that the dashboard is about to mount. */
.welcome-manifest {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding-top: 12px;
  text-align: left;
  font-size: 11px;
  letter-spacing: 1px;
  color: var(--text-dim);
  text-transform: uppercase;
}
.welcome-manifest-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 220ms ease, transform 220ms cubic-bezier(.2,.7,.2,1);
}
.welcome-manifest-row.is-in {
  opacity: 1;
  transform: none;
}
.welcome-manifest-label {
  color: var(--text-dim);
  white-space: nowrap;
  font-weight: 600;
}
.welcome-manifest-dots {
  flex: 1;
  height: 1px;
  margin: 0 2px 3px;
  background-image: linear-gradient(
    to right,
    rgba(140, 154, 172, 0.35) 0 1px,
    transparent 1px 4px
  );
  background-size: 4px 1px;
  background-repeat: repeat-x;
  align-self: end;
}
.welcome-manifest-value {
  color: var(--text-secondary);
  font-weight: 600;
  white-space: nowrap;
}
.welcome-manifest-row.is-ready {
  margin-top: 10px;
  padding-top: 8px;
  border-top: 1px solid var(--border);
}
.welcome-manifest-row.is-ready .welcome-manifest-label {
  color: var(--accent);
  letter-spacing: 1.5px;
}
.welcome-manifest-row.is-ready .welcome-manifest-value {
  color: var(--accent);
}
.welcome-manifest-row.is-ready .welcome-manifest-dots {
  background-image: linear-gradient(
    to right,
    rgba(16, 185, 129, 0.35) 0 1px,
    transparent 1px 4px
  );
}

@media (prefers-reduced-motion: reduce) {
  .welcome-subtitle.welcome-status::after,
  .welcome-progress.is-pulsing .welcome-progress-tick {
    animation: none;
  }
  .welcome-card.is-collapsing,
  .calibration-rule,
  .welcome-manifest-row {
    transition: none;
  }
}

.upload-zone {
  border: 1px solid rgba(153, 164, 180, 0.28);
  border-radius: var(--radius-lg);
  padding: var(--space-2xl) var(--space-xl);
  min-height: 210px;
  width: 100%;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: all 0.2s;
  background: rgba(255, 255, 255, 0.38);
  border-color: rgba(153, 164, 180, 0.28);
  backdrop-filter: blur(10px) saturate(115%);
  -webkit-backdrop-filter: blur(10px) saturate(115%);
  box-shadow: 0 10px 28px rgba(10, 28, 44, 0.06);
}
.welcome-act .upload-zone,
.welcome-act .upload-zone-success {
  width: 100%;
  min-width: 100%;
  max-width: 100%;
}

.upload-zone:hover,
.upload-zone-active {
  border-color: var(--accent);
  background: rgba(235, 247, 253, 0.7);
}

.upload-icon {
  color: var(--text-dim);
  margin-bottom: var(--space-md);
}

.upload-zone:hover .upload-icon,
.upload-zone-active .upload-icon {
  color: var(--accent);
}

.upload-label {
  font-size: 13px;
  font-weight: 500;
  color: var(--text-primary);
  margin-bottom: var(--space-xs);
}

.upload-hint {
  font-size: 13px;
  color: var(--text-dim);
}

.upload-help {
  margin-top: var(--space-lg);
  text-align: left;
}

.upload-help summary {
  font-size: 13px;
  color: var(--text-dim);
  cursor: pointer;
  letter-spacing: 0.5px;
}

.upload-format {
  font-size: 12px;
  color: var(--text-secondary);
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  padding: var(--space-md);
  margin-top: var(--space-sm);
  overflow-x: auto;
  line-height: 1.6;
}

.welcome-actions {
  margin-top: var(--space-lg);
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  align-items: center;
}

.welcome-btn {
  font-family: var(--font-mono);
  font-size: 12px;
  border: none;
  border-radius: var(--radius-sm);
  padding: var(--space-sm) var(--space-xl);
  cursor: pointer;
  letter-spacing: 0.5px;
}
.welcome-btn-skip {
  background: none;
  color: var(--text-dim);
  font-size: 13px;
  padding: var(--space-xs) var(--space-md);
}
.welcome-btn-skip:hover { color: var(--text-secondary); }

.upload-zone-secondary {
  padding: var(--space-md) var(--space-lg);
  min-height: 210px;
}
.upload-zone-secondary .upload-icon { margin-bottom: var(--space-xs); }
.upload-zone-content {
  width: 100%;
  min-height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  opacity: 1;
  transform: scale(1);
  transition: opacity 220ms ease, transform 220ms ease;
}
.upload-zone-content.is-exiting {
  opacity: 0;
  transform: scale(0.985);
}

/* Apple Health success: dropzone contents are replaced with a
   centered checkmark — single, calm confirmation. The zone keeps
   its frame so the swap reads as "this slot resolved" rather than
   a new element appearing. */
.upload-zone-success {
  border-style: solid;
  border-color: var(--green);
  background: rgba(52, 168, 83, 0.06);
  cursor: default;
  pointer-events: none;
  display: flex;
  width: 100%;
  box-sizing: border-box;
  align-items: center;
  justify-content: center;
  min-height: 210px;
}
.upload-success-check {
  color: var(--green);
  display: flex;
  align-items: center;
  justify-content: center;
  animation: upload-success-enter 360ms cubic-bezier(.2, .7, .2, 1) both;
}
@keyframes upload-success-enter {
  0%   { opacity: 0; transform: scale(0.9); }
  70%  { opacity: 1; transform: scale(1.02); }
  100% { opacity: 1; transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .upload-success-check { animation: none; }
}

/* Revisit / loaded state — same green frame as `.upload-zone-success`,
   but the zone is interactive again so drop-to-replace works without
   a trip through the picker. The checkmark sits above filename + a
   one-line summary; the explicit replace + continue controls live in
   `.welcome-act-controls` below. */
.upload-zone-loaded {
  flex-direction: column;
  cursor: default;
  pointer-events: auto;
  padding: var(--space-lg) var(--space-xl);
}
.upload-zone-loaded.upload-zone-active {
  background: rgba(235, 247, 253, 0.7);
  border-color: var(--accent);
}
.upload-zone-loaded .upload-success-check {
  margin-bottom: var(--space-sm);
}
.upload-loaded-filename {
  font-size: 13px;
  font-weight: 500;
  color: var(--text-primary);
  margin: 0;
  word-break: break-all;
  text-align: center;
}
.upload-loaded-summary {
  font-size: 11px;
  color: var(--text-dim);
  margin-top: 2px;
  letter-spacing: 0.3px;
  text-align: center;
}

/* Footer for revisited (loaded-state) acts. Two affordances laid out
   inline: a quiet `↻ replace` link and a primary `continue →` button.
   Replace stays small/dim — the dropzone itself is the primary swap
   surface; the link is just a keyboard-accessible alias for the same
   intent. Continue is the act of intent the user came back for. */
.welcome-act-controls {
  margin-top: var(--space-md);
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  gap: var(--space-lg);
}
.welcome-link-quiet {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--text-dim);
  background: none;
  border: none;
  padding: var(--space-xs) var(--space-sm);
  cursor: pointer;
  border-radius: var(--radius-sm);
  transition: color 160ms ease, background 160ms ease;
}
.welcome-link-quiet:hover,
.welcome-link-quiet:focus-visible {
  color: var(--text-secondary);
  background: var(--bg-surface);
  outline: none;
}
/* The one forward action, everywhere. There's only one way through setup —
   forward — so every "continue / save / finish" button gets the same strong,
   filled primary. Skippability is carried structurally by .welcome-btn-skip
   (a same-sized neutral peer beside it), never by softening this button. */
.welcome-btn-continue {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 32px;
  min-width: 126px;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.9px;
  text-transform: uppercase;
  color: #fff;
  background: var(--accent);
  border: 1px solid var(--accent);
  border-radius: var(--radius-sm);
  padding: 9px 18px;
  cursor: pointer;
  box-shadow: 0 1px 2px rgba(8, 145, 178, 0.14);
  transition: color 160ms ease, background 160ms ease, border-color 160ms ease, transform 160ms ease, box-shadow 160ms ease;
}
.welcome-btn-continue:hover,
.welcome-btn-continue:focus-visible {
  color: #fff;
  background: #087f9a;
  border-color: #087f9a;
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(8, 145, 178, 0.18);
  outline: none;
}

/* Unengaged state — the step has no input yet, so advancing records nothing
   (the honest skip). Soft-tinted accent: unmistakably a clickable accent
   action, never disabled-looking, just lower energy than the solid fill it
   becomes once the user enters a value. */
.welcome-btn-continue.is-unengaged {
  color: #03677f;
  background: rgba(8, 145, 178, 0.08);
  border-color: rgba(8, 145, 178, 0.42);
  box-shadow: 0 1px 2px rgba(8, 145, 178, 0.06);
}
.welcome-btn-continue.is-unengaged:hover,
.welcome-btn-continue.is-unengaged:focus-visible {
  color: #034f63;
  background: rgba(8, 145, 178, 0.12);
  border-color: var(--accent);
  box-shadow: 0 4px 12px rgba(8, 145, 178, 0.12);
}
@media (prefers-reduced-motion: reduce) {
  .welcome-btn-continue:hover,
  .welcome-btn-continue:focus-visible { transform: none; }
}

/* .welcome-btn-finish is now visually identical to .welcome-btn-continue (the
   forward action is unified) — it always co-occurs with it and stays only as a
   semantic hook for the terminal step, so it needs no style of its own. */

/* Skip in the branch ask is a true PEER of save, not a de-emphasized quiet
   link — when the user genuinely doesn't know a value, opting out shouldn't
   feel like the lesser path. Neutral coloring (not the accent "continue"
   semantics) but shares .welcome-btn-continue's box so the two read as equals. */
.welcome-btn-skip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 32px;
  min-width: 126px;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.9px;
  text-transform: uppercase;
  color: var(--text-secondary);
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 9px 18px;
  cursor: pointer;
  transition: color 160ms ease, background 160ms ease, border-color 160ms ease;
}
.welcome-btn-skip:hover,
.welcome-btn-skip:focus-visible {
  color: var(--text-primary);
  border-color: var(--text-dim);
  outline: none;
}

/* How-to / help sub-screens are dead ends — back is the only way forward.
   A faint quiet-link there reads as a non-action and strands the user, so
   the back control on these screens gets a clear bordered-button treatment.
   Neutral (not the accent "continue" semantics) since it points backward;
   shares .welcome-btn-continue's dimensions so the two read as one system. */
.welcome-btn-back {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  color: var(--text-secondary);
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: var(--space-sm) var(--space-lg);
  cursor: pointer;
  transition: color 160ms ease, background 160ms ease, border-color 160ms ease;
}
.welcome-btn-back:hover,
.welcome-btn-back:focus-visible {
  color: var(--text-primary);
  border-color: var(--text-dim);
  outline: none;
}
.welcome-btn-back-arrow {
  font-size: 13px;
  letter-spacing: 0;
  transition: transform 160ms ease;
}
.welcome-btn-back:hover .welcome-btn-back-arrow,
.welcome-btn-back:focus-visible .welcome-btn-back-arrow {
  transform: translateX(-2px);
}
@media (prefers-reduced-motion: reduce) {
  .welcome-btn-back:hover .welcome-btn-back-arrow,
  .welcome-btn-back:focus-visible .welcome-btn-back-arrow { transform: none; }
}

/* ───────────────────────────────────────────
   Terms gate — final onboarding screen, before the dashboard reveal.
   A concise disclaimer above a bordered consent panel; the checkbox inside
   ungates the (otherwise disabled) forward button. No back button by here —
   the user is committed. The header chrome enters a terminal state (see
   `.welcome.is-terminal` below) to signal the final beat.
   ─────────────────────────────────────────── */

/* Disabled forward button — "agree & continue" before the box is checked.
   Clearly not-yet-actionable (muted, no hover lift, not-allowed) without
   going fully dead-grey, so it's obvious what lights up on check. Removing
   the `disabled` attribute restores the solid-accent .welcome-btn-continue. */
.welcome-btn-continue:disabled,
.welcome-btn-continue:disabled:hover,
.welcome-btn-continue:disabled:focus-visible {
  color: #6b7280;
  background: rgba(8, 145, 178, 0.06);
  border-color: rgba(8, 145, 178, 0.24);
  box-shadow: none;
  transform: none;
  cursor: not-allowed;
}

/* The terms act is a single short content block. On desktop it top-aligns
   like every other step (the slot's flex-start); the footer margin below
   gives the CTA the same breathing room the other sparse acts get. The
   center-in-slot treatment is mobile-only — see the 720px block — because
   only there does the fixed footer + top-pinned slot strand the gate up top. */
.welcome-act-terms .welcome-act-footer {
  /* Match the sparse upload acts (bloodwork / vitals / genome) — the base
     8px reads as cramped under a lone consent panel. Desktop only; the
     footer is position:fixed on phones, so margin-top is inert there. */
  margin-top: var(--space-xl);
}
.terms-gate {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  text-align: left;
}
/* Left-aligned by intent: a centered monospace paragraph reads as a ragged
   wall — flush-left gives the body and consent line a clean edge. */
.terms-gate-body {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.7;
  color: var(--text-secondary);
  text-align: left;
}
/* The consent control: a subtle-fill panel (interactive-affordance grammar)
   clickable anywhere to toggle. The whole label is the hit target. */
.terms-gate-ack {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 12px 14px;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: border-color 160ms ease;
}
.terms-gate-ack:hover {
  border-color: var(--text-dim);
}
.terms-gate-check {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  margin: 1px 0 0;
  accent-color: var(--accent);
  cursor: pointer;
}
.terms-gate-ack-text {
  font-size: 12px;
  line-height: 1.6;
  color: var(--text-secondary);
  text-align: left;
}
.terms-gate-ack-text a {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.terms-gate-ack-text a:hover,
.terms-gate-ack-text a:focus-visible {
  color: #087f9a;
  outline: none;
}

/* ── Terminal header state ──
   While the consent gate holds, the chrome signals "final beat / imminent":
   the five (already-complete, green) progress pills pulse in a staggered
   left-to-right wave, and the LE readout recedes so nothing competes with the
   single forward action. The pills stay SEPARATE — fusing them into one bar
   fought the renderer (seams, dark overlap dots); a wave is the KISS read.
   Cleared the instant the user agrees / on Back, so finish()'s own closeout
   (and the un-merge) run from a clean baseline. */
.welcome.is-terminal .welcome-progress-tick {
  opacity: 1;
  /* Animation lives on the ticks, NOT on `.welcome-progress`, which owns the
     one-shot `welcome-fade-rise` entrance — putting it on the parent meant
     removing `is-terminal` re-ran fade-rise (opacity 0 → 1), so the bar faded
     back in on Back. Ticks have no base animation, so toggling restores clean. */
  animation: terms-pill-pulse 1.5s ease-in-out infinite;
}
.welcome.is-terminal .welcome-progress-tick:nth-child(1) { animation-delay: 0ms; }
.welcome.is-terminal .welcome-progress-tick:nth-child(2) { animation-delay: 130ms; }
.welcome.is-terminal .welcome-progress-tick:nth-child(3) { animation-delay: 260ms; }
.welcome.is-terminal .welcome-progress-tick:nth-child(4) { animation-delay: 390ms; }
.welcome.is-terminal .welcome-progress-tick:nth-child(5) { animation-delay: 520ms; }
.welcome.is-terminal .welcome-readout-card,
.welcome.is-terminal .welcome-readout-stage {
  opacity: 0;
  max-height: 0;
  margin: 0;
  overflow: hidden;
  pointer-events: none;
  transition: opacity 360ms ease, max-height 360ms ease, margin 360ms ease;
}
/* Dim + a subtle vertical swell at the crest — reads as a live "scanning"
   beat, not a blink. transform-origin centers the swell so it grows both
   ways from the pill's midline. */
@keyframes terms-pill-pulse {
  0%, 100% { opacity: 0.4; transform: scaleY(1); }
  50%      { opacity: 1;   transform: scaleY(1.5); }
}
@media (prefers-reduced-motion: reduce) {
  .welcome.is-terminal .welcome-progress-tick { animation: none; }
}

.apple-health-steps {
  padding: var(--space-sm) 0;
  font-size: 12px;
  line-height: 1.7;
  color: var(--text-secondary);
}
.apple-health-steps ol {
  margin: 0;
  padding-left: var(--space-lg);
}
.apple-health-steps code {
  font-size: 13px;
  padding: 1px 4px;
  border-radius: 3px;
  background: rgba(0,0,0,0.04);
}
.apple-health-steps a {
  color: var(--blue);
  text-decoration: none;
}
.apple-health-steps a:hover { text-decoration: underline; }

.upload-error {
  margin-top: var(--space-md);
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-sm);
  background: var(--red-bg);
  color: var(--red);
  font-size: 13px;
}

/* ═══════════════════════════════════════════
   SOURCE DATA EXPLORERS (inline panels)
   ═══════════════════════════════════════════ */

/* ── Explore toggle button ── */
.src-explore-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: 100%;
  padding: 8px 0;
  margin-top: var(--space-md);
  border: none;
  border-radius: var(--radius-sm);
  background: var(--bg-surface);
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.5px;
  color: var(--text-dim);
  cursor: pointer;
  transition: all 0.15s;
}

.src-explore-toggle:hover {
  color: var(--accent);
  background: var(--accent-soft);
}

.src-explore-toggle.open {
  color: var(--accent);
  background: var(--accent-soft);
}

.src-explore-arrow {
  transition: transform 0.2s;
  font-size: 14px;
}

.src-explore-toggle.open .src-explore-arrow {
  transform: rotate(180deg);
}

/* ── Slide-over header & components ── */
.slide-over-header {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0;
  padding: var(--space-md) var(--space-lg);
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}

.slide-over-title {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--text-secondary);
  white-space: nowrap;
}

.slide-over-tabs {
  display: flex;
  gap: 2px;
  margin-left: var(--space-md);
}

.slide-over-tab {
  padding: 4px 12px;
  border: none;
  background: none;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.5px;
  color: var(--text-dim);
  cursor: pointer;
  border-radius: var(--radius-sm);
  transition: all 0.12s;
}

.slide-over-tab:hover {
  color: var(--text-secondary);
  background: var(--bg-hover);
}

.slide-over-tab.active {
  color: var(--accent);
  background: var(--accent-soft);
}

.slide-over-spacer { flex: 1; }

.slide-over-close {
  width: 28px;
  height: 28px;
  border: none;
  background: none;
  font-size: 15px;
  color: var(--text-dim);
  cursor: pointer;
  border-radius: var(--radius-sm);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: all 0.12s;
}

.slide-over-close:hover {
  color: var(--text-primary);
  background: var(--bg-hover);
}

.slide-over-body {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-lg);
}

.slide-over-compact .slide-over-header {
  padding: 18px 20px 16px;
  background: rgba(255, 255, 255, 0.94);
}
.slide-over-compact .slide-over-title {
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 2px;
  color: var(--text-primary);
}
.slide-over-compact .slide-over-close {
  width: 30px;
  height: 30px;
  font-size: 16px;
}
.slide-over-compact .slide-over-body {
  padding: 20px;
}

.slide-over-body-inline-close {
  position: relative;
}

.slide-over-inline-close {
  position: absolute;
  top: 14px;
  right: 14px;
  z-index: 2;
  background: var(--bg-panel);
}

.slide-over-compact .slide-over-body-inline-close {
  padding-top: 28px;
}

/* Hide scrollbar on slide-over body */
.slide-over-body::-webkit-scrollbar { width: 4px; }
.slide-over-body::-webkit-scrollbar-track { background: transparent; }
.slide-over-body::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }

/* ═══════════ TOPBAR DATA FRESHNESS BUTTON ═══════════ */
.topbar-data {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-dim);
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
}

.topbar-data:hover {
  color: var(--accent);
  border-color: var(--accent);
}

.topbar-data-pulse {
  position: absolute;
  top: -2px;
  right: -2px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--green);
  border: 1.5px solid var(--bg-panel);
  opacity: 0;
  transition: opacity 0.15s;
}

.topbar-data.tier-fresh .topbar-data-pulse {
  background: var(--green);
  opacity: 0.7;
}

.topbar-data.tier-aging .topbar-data-pulse {
  background: var(--amber);
  opacity: 1;
}

.topbar-data.tier-stale .topbar-data-pulse {
  background: var(--red);
  opacity: 1;
}

/* ═══════════ DATA PANEL ═══════════ */
.data-panel-backdrop {
  position: fixed;
  top: 56px;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.08);
  z-index: 300;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
}

.data-panel-backdrop.visible {
  opacity: 1;
  pointer-events: auto;
  cursor: pointer;
}

.data-panel {
  position: fixed;
  top: 56px;
  right: 0;
  bottom: 0;
  width: 400px;
  max-width: 90vw;
  background: var(--bg-panel);
  border-left: 1px solid var(--border);
  box-shadow: -6px 0 24px rgba(0, 0, 0, 0.06);
  z-index: 310;
  transform: translateX(100%);
  transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
  display: flex;
  flex-direction: column;
  padding-bottom: var(--safe-bottom);
}

.data-panel.open {
  transform: translateX(0);
}

/* Mobile full-screen — cover the topbar too. Wins over the desktop
   .data-panel { top: 56px } above by virtue of source order; the
   layout.css 720px block can't (it loads first). */
@media (max-width: 720px) {
  .data-panel,
  .data-panel-backdrop { top: 0; }
}

/* Privacy reassurance — sits under the panel title in the header */
.dp-privacy {
  font-size: 11px;
  color: var(--text-dim);
  margin-top: 6px;
  letter-spacing: 0.2px;
}

/* Panel body */
.dp-body {
  flex: 1;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
}

.dp-body::-webkit-scrollbar { width: 4px; }
.dp-body::-webkit-scrollbar-track { background: transparent; }
.dp-body::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }

/* Sections */
.dp-section {
  padding: 14px var(--space-lg);
}

.dp-section-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--text-dim);
  margin-bottom: var(--space-md);
}

/* Label sits inside the category header now — kill its bottom margin
   so the header row hugs the provider list above the divider. */
.dp-cat-head .dp-section-label {
  margin-bottom: 0;
}

/* Divider */
.dp-divider {
  height: 1px;
  background: var(--border);
}

/* Category header — section label + optional inline status + the
   header-level Upload affordance. All three categories share the same
   shape; symmetry across sections beats per-category cleverness. */
.dp-cat-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  margin-bottom: 4px;
}

.dp-cat-title {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  min-width: 0;
}

.dp-cat-icon {
  width: 14px;
  height: 14px;
  color: var(--text-dim);
  flex-shrink: 0;
}

.dp-cat-status {
  font-size: 11px;
  font-weight: 500;
  color: var(--green);
  letter-spacing: 0.2px;
}

/* Provider rows — name + (optional) loaded status on the left, action
   buttons (Upload / Sync) on the right. Two affordances max per row;
   list separator is a hairline, not a card. */
.dp-prov-list {
  display: flex;
  flex-direction: column;
}

/* The row is the affordance, not the trailing pill — a full-width row-button
   (mirrors .dp-sr-open). Content stays aligned at the list edge while the
   hover fill bleeds 6px each side via the negative margin. */
.dp-prov-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  width: calc(100% + 12px);
  margin: 0 -6px;
  padding: 7px 6px;
  background: none;
  border: none;
  border-top: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition: background 0.15s;
}

.dp-prov-row:first-child {
  border-top: none;
}

.dp-prov-row:hover,
.dp-prov-row:focus-visible {
  background: var(--bg-surface);
  outline: none;
}

/* Sync is a quiet trailing cue; the row owns the click. It only brightens —
   border + text — when the row is hovered/focused, signaling the row acts. */
.dp-prov-sync {
  cursor: inherit;
}

.dp-prov-row:hover .dp-prov-sync,
.dp-prov-row:focus-visible .dp-prov-sync {
  border-color: var(--border);
  color: var(--text-secondary);
}

/* Provider brand mark — the same logo.dev tile the onboarding rail renders
   (.prov-logo base lives in components-onboarding.css), sized for the panel's
   compact row. Degrades to the mono monogram when a logo can't load. */
.dp-prov-logo {
  width: 28px;
  height: 28px;
  border: 1px solid var(--border-subtle);
  border-radius: 7px;
}
.dp-prov-logo .prov-logo-fallback {
  font-size: 12px;
}

.dp-prov-name {
  flex: 1;
  min-width: 0;
  font-size: 12.5px;
  font-weight: 400;
  color: var(--text-primary);
  letter-spacing: 0.1px;
}

/* Action buttons — small pill + leading icon. Two variants drive the
   visual hierarchy:
     .dp-action-primary  → Upload (real action). Solid border, body
                           text color, faint surface fill. Reads as
                           "click this to do the thing."
     .dp-action-ghost    → Sync (fake-door). Borderless, dim text,
                           reveals border on hover. Reads as "here's
                           something you could ask us about." */
.dp-action-btn {
  font-family: inherit;
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  padding: 5px 10px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
}

.dp-btn-icon {
  width: 10px;
  height: 10px;
  flex-shrink: 0;
}

.dp-action-primary {
  border: 1px solid var(--border);
  background: var(--bg-surface);
  color: var(--text-secondary);
}

.dp-action-primary:hover,
.dp-action-primary:focus-visible {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--accent-soft);
  outline: none;
}

.dp-action-ghost {
  border: 1px solid transparent;
  background: transparent;
  color: var(--text-dim);
}

.dp-action-ghost:hover,
.dp-action-ghost:focus-visible {
  border-color: var(--border);
  color: var(--text-secondary);
  outline: none;
}

/* Quiet meta lines used by the self-report block. Empty state intentionally
   sits at body-text size in dim/secondary — it's information, not content. */
.dp-empty {
  font-size: 12px;
  color: var(--text-dim);
  letter-spacing: 0.2px;
}

.dp-stored {
  font-size: 12px;
  color: var(--text-secondary);
  letter-spacing: 0.2px;
}

/* ── Self-report: summary row → nested detail ── */

/* The summary becomes a full-width row-button into the detail view:
   stored inventory on the left, a Review affordance + chevron on the right.
   Borderless and quiet on the main view; reveals a hairline on hover. */
.dp-sr-open {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  width: 100%;
  padding: 8px 10px;
  margin: 0 -10px;
  background: none;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s;
}

.dp-sr-open:hover,
.dp-sr-open:focus-visible {
  border-color: var(--border);
  background: var(--bg-surface);
  outline: none;
}

.dp-sr-open-cta {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  flex-shrink: 0;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--text-dim);
  transition: color 0.15s;
}

.dp-sr-open:hover .dp-sr-open-cta,
.dp-sr-open:focus-visible .dp-sr-open-cta {
  color: var(--accent);
}

.dp-sr-chevron {
  width: 12px;
  height: 12px;
}

/* Back button in the detail header — mirrors slide-over-close geometry. */
.dp-back {
  width: 28px;
  height: 28px;
  border: none;
  background: none;
  color: var(--text-dim);
  cursor: pointer;
  border-radius: var(--radius-sm);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  margin-right: 2px;
  transition: all 0.12s;
}

.dp-back svg { width: 16px; height: 16px; }

.dp-back:hover {
  color: var(--text-primary);
  background: var(--bg-hover);
}

/* Detail view — list of recorded self-reports. */
.dp-sr-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

/* One reported value: name above its inline editor. */
.dp-sr-item {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.dp-sr-item-name {
  font-size: 12.5px;
  font-weight: 500;
  color: var(--text-primary);
  letter-spacing: 0.1px;
}

/* The reused lever editor sits flush inside the item — its own "Your status"
   sub-label provides the context, so kill any extra top spacing. */
.dp-sr-item .lever-exp-section { gap: 6px; }

/* Dismissed / completed action rows: text + Undo on one line. */
.dp-sr-item-action {
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  padding: 7px 0;
  border-top: 1px solid var(--border-subtle);
}

.dp-sr-item-action:first-child { border-top: none; padding-top: 0; }

.dp-sr-action-text {
  flex: 1;
  min-width: 0;
  font-size: 12.5px;
  color: var(--text-secondary);
  letter-spacing: 0.1px;
}

/* "+ N more →" — fake-door link that funnels straight to the iOS
   waitlist modal. No inline expansion; the modal owns the long tail. */
.dp-more-link {
  margin-top: var(--space-sm);
  padding: 4px 0;
  background: transparent;
  border: none;
  font-family: inherit;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--text-dim);
  cursor: pointer;
  transition: color 0.15s;
}

.dp-more-link:hover,
.dp-more-link:focus-visible {
  color: var(--accent);
  outline: none;
}

/* Feedback messages */
.dp-feedback {
  font-size: 11px;
  padding: 4px 8px;
  border-radius: var(--radius-sm);
  margin-top: 4px;
}

.dp-feedback-success {
  color: var(--green);
  background: var(--green-bg);
}

.dp-feedback-error {
  color: var(--red);
  background: var(--red-bg);
}

/* Clear section — pinned to bottom */
.dp-clear-wrap {
  margin-top: auto;
  padding: var(--space-md) var(--space-lg);
  border-top: 1px solid var(--border-subtle);
  display: flex;
  justify-content: center;
}

.dp-clear-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: none;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-dim);
  cursor: pointer;
  padding: 6px 14px;
  letter-spacing: 0.3px;
  transition: color 0.15s, border-color 0.15s, background 0.15s;
}

.dp-clear-btn:hover {
  color: var(--red);
  border-color: var(--red);
}
.dp-clear-btn:hover .dp-clear-icon { color: var(--red); }

.dp-clear-icon {
  color: var(--text-dim);
  flex-shrink: 0;
  transition: color 0.15s;
}

.dp-clear-btn-armed {
  color: var(--red) !important;
  border-color: var(--red) !important;
  background: var(--red-bg) !important;
  font-weight: 500;
}

.dp-clear-btn-armed .dp-clear-icon { color: var(--red); }

.dp-clear-btn-secondary {
  font-size: 11px;
  letter-spacing: 0.3px;
}

/* Phone widths: bump just the tappable affordances to comfortable touch
   targets (~32–36px tall). The list itself stays the same density —
   we're not scaling the panel up, just making fingers reliably hit the
   pills that already exist. iOS HIG / Material both ask for ~44pt, and
   the prior 22px-tall pills sat well under that. */
@media (max-width: 600px) {
  .dp-prov-row { padding: 10px 6px; }
  .dp-action-btn { padding: 8px 12px; font-size: 11px; }
  .dp-more-link { padding: 10px 0; font-size: 11px; }
  .dp-clear-btn { padding: 10px 18px; }
}

/* ── Data section header (inside explore panel) ── */
.src-data-section {
  margin-bottom: var(--space-sm);
}

.src-data-hd {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  margin-bottom: var(--space-sm);
  gap: var(--space-md);
}

.src-data-title {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--text-secondary);
  margin-bottom: 2px;
}

.src-data-meta {
  font-size: 12px;
  color: var(--text-dim);
  line-height: 1.6;
  margin-bottom: 2px;
}

.src-data-meta em {
  font-style: normal;
  color: var(--text-primary);
  font-weight: 500;
}

.src-ext-link {
  font-size: 12px;
  font-weight: 500;
  color: var(--accent);
  text-decoration: none;
  opacity: 0.7;
  white-space: nowrap;
  flex-shrink: 0;
  transition: opacity 0.15s;
}

.src-ext-link:hover {
  opacity: 1;
}

.src-ext-links {
  display: flex;
  gap: var(--space-sm);
  flex-shrink: 0;
}

/* ── Filter bar ── */
.src-filters {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-sm);
  margin-bottom: var(--space-md);
}

.filter-group {
  display: flex;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.filter-btn {
  padding: 5px 12px;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.5px;
  border: none;
  background: none;
  color: var(--text-dim);
  cursor: pointer;
  transition: all 0.12s;
  white-space: nowrap;
}

.filter-btn + .filter-btn {
  border-left: 1px solid var(--border);
}

.filter-btn:hover {
  color: var(--text-secondary);
  background: var(--bg-hover);
}

.filter-btn.active {
  background: var(--bg-panel);
  color: var(--accent);
}

/* ── Table wrapper ── */
.src-table-wrap {
  overflow-x: auto;
}

.src-table-label {
  font-size: 11px;
  font-weight: 500;
  color: var(--text-dim);
  letter-spacing: 0.5px;
  text-transform: uppercase;
  margin-bottom: 6px;
}

.src-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

.src-table th {
  text-align: left;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--text-dim);
  padding: 6px 12px 6px 0;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}

.src-table th.src-th-num {
  text-align: right;
  padding-right: 20px;
}

.src-table th.src-th-center {
  text-align: center;
  padding-right: 8px;
}

.src-table td {
  padding: 7px 12px 7px 0;
  border-bottom: 1px solid var(--border-subtle);
  vertical-align: middle;
  color: var(--text-primary);
}

.src-table tr:last-child td {
  border-bottom: none;
}

.src-table tr:hover td {
  background: var(--bg-surface);
}

/* Highlighted row for user's age / selected population */
.src-row-you td {
  background: var(--accent-soft) !important;
  color: var(--accent);
}

.src-row-you td.src-td-num,
.src-row-you td.src-td-age {
  font-weight: 600;
}

/* Base row (middle class, native-born) */
.src-row-base td {
  color: var(--text-secondary);
  font-style: italic;
}

/* Numeric cells */
.src-td-age,
.src-td-num,
.src-td-pct {
  font-variant-numeric: tabular-nums;
  text-align: right;
  padding-right: 20px;
  white-space: nowrap;
}

.src-td-age {
  color: var(--text-dim);
  font-size: 12px;
  width: 60px;
}

.src-td-rank {
  color: var(--text-dim);
  font-size: 12px;
  width: 32px;
  text-align: center;
  padding-right: 8px;
}

.src-td-cause {
  font-weight: 500;
}

.src-td-tier {
  font-weight: 500;
  white-space: nowrap;
}

.src-td-desc {
  color: var(--text-secondary);
  font-size: 12px;
}

.src-td-adj {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  white-space: nowrap;
  padding-right: 16px;
  width: 80px;
}

.src-td-adj.positive { color: var(--green); }
.src-td-adj.negative { color: var(--red); }

.src-td-range {
  font-size: 12px;
  color: var(--text-dim);
  white-space: nowrap;
  padding-right: 16px;
}

.src-td-detail-text {
  font-size: 12px;
  color: var(--text-secondary);
  line-height: 1.6;
  max-width: 400px;
}

/* ── Bar visualizations ── */
.src-td-bar {
  width: 200px;
  padding-right: 0;
}

.src-bar-track {
  height: 6px;
  background: var(--bg-surface);
  border-radius: 3px;
  overflow: hidden;
  position: relative;
  border: 1px solid var(--border-subtle);
}

.src-bar-fill {
  height: 100%;
  border-radius: 3px;
  background: var(--accent);
  transition: width 0.3s ease;
}

.src-bar-fill.src-bar-high     { background: var(--red); }
.src-bar-fill.src-bar-elevated { background: var(--amber); }
.src-bar-fill.src-bar-mod      { background: var(--yellow); }
.src-bar-fill.src-bar-low      { background: var(--green); }

/* Centered deviation bar (SES section) */
.src-bar-track-centered {
  overflow: visible;
  background: var(--bg-surface);
}

.src-bar-center-line {
  position: absolute;
  left: 50%;
  top: 0;
  width: 1px;
  height: 100%;
  background: var(--border);
}

.src-bar-deviation {
  position: absolute;
  top: 0;
  height: 100%;
  border-radius: 2px;
}

/* ── COD expandable rows ── */
.src-cod-row {
  cursor: default;
}

.src-cod-row[data-expandable] {
  cursor: pointer;
}

.src-cod-row[data-expandable]:hover td {
  background: var(--bg-surface);
}

.src-cod-row.src-row-open td {
  background: var(--bg-surface);
  color: var(--accent);
}

.src-cod-detail-cell {
  font-size: 12px;
  color: var(--text-secondary);
  line-height: 1.7;
  padding: 8px 16px 12px 16px !important;
  background: var(--bg-surface);
  border-bottom: 1px solid var(--border-subtle);
}

/* ── Misc ── */
.src-table-note {
  font-size: 11px;
  color: var(--text-dim);
  letter-spacing: 0.3px;
  margin-top: 6px;
}

.src-empty {
  font-size: 13px;
  color: var(--text-dim);
  padding: var(--space-md) 0;
}

.src-table-imm td.src-td-tier {
  min-width: 160px;
}

/* ── Table inner wrap (for lazy-rendered content) ── */
.src-table-inner {
  overflow-x: auto;
}

/* ═══════════ SLIDE-OVER HEADER STRUCTURE ═══════════
   Header is two rows now: title row on top, optional controls strip
   below (filters that scope the entire panel). Controls row hides
   itself when empty so panels that don't use it don't get extra chrome. */
.slide-over-header-row {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

.slide-over-header-controls:empty {
  display: none;
}

.slide-over-header-controls {
  margin-top: var(--space-md);
  padding-top: var(--space-md);
  border-top: 1px solid var(--border-subtle);
}

/* Header filter row — label + group, label + group. Wraps at narrow
   widths so the slide-over still works at min-width. */
.slide-over-filter-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-sm);
  row-gap: 8px;
}

.slide-over-filter-label {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--text-dim);
  margin-left: var(--space-xs);
}

.slide-over-filter-row .slide-over-filter-label:first-child {
  margin-left: 0;
}

/* Allow the eth filter group to wrap onto a second row if drawer is narrow */
.filter-group.filter-group-wrap {
  flex-wrap: wrap;
}

/* ═══════════ PANEL A — POPULATION BASE RATES ═══════════
   Content max-width constrains the table; bars don't need to be longer
   to be more readable, they need to be the right ratio of label to bar. */
.src-cod-section {
  max-width: 720px;
}

/* Active-filter readout strip — confirms what was just clicked in the
   header, in instrument-readout style. Visually replaces the old
   src-table-label. */
.src-cod-readout {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-sm);
  padding: 10px 0;
  margin-bottom: var(--space-sm);
  border-bottom: 1px solid var(--border);
  font-size: 11px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
}

.src-cod-readout-label {
  color: var(--text-dim);
  font-weight: 500;
}

.src-cod-readout-pop {
  color: var(--text-primary);
  font-weight: 600;
  letter-spacing: 1px;
}

.src-cod-readout-vs {
  color: var(--text-secondary);
  font-weight: 500;
}

.src-cod-readout-spacer { flex: 1; }

.src-cod-readout-source {
  color: var(--text-dim);
  font-weight: 500;
  font-size: 10px;
}

/* Δ column — directional glyph + magnitude. Color is muted; the glyph
   carries direction without screaming. */
.src-td-delta {
  font-variant-numeric: tabular-nums;
  text-align: right;
  padding-right: 16px;
  white-space: nowrap;
  font-size: 12px;
  font-weight: 500;
}

.src-cod-delta-glyph {
  display: inline-block;
  width: 11px;
  margin-right: 4px;
  font-size: 9px;
  position: relative;
  top: -1px;
}

.src-cod-delta-num {
  font-variant-numeric: tabular-nums;
}

.src-td-delta-flat .src-cod-delta-glyph,
.src-td-delta-flat .src-cod-delta-num {
  color: var(--text-dim);
}

.src-td-delta-up {
  color: var(--text-primary);
}
.src-td-delta-up .src-cod-delta-glyph {
  color: var(--amber);
}

.src-td-delta-down {
  color: var(--text-primary);
}
.src-td-delta-down .src-cod-delta-glyph {
  color: var(--text-secondary);
}

/* Ghost bar — the all-US baseline rendered behind the filtered fill.
   When the fill is shorter than the ghost, the ghost extends past it
   and the eye reads "your population gets this less than national." */
.src-bar-ghost {
  position: absolute;
  left: 0;
  top: 0;
  height: 100%;
  background: repeating-linear-gradient(
    90deg,
    var(--border) 0 2px,
    transparent 2px 4px
  );
  border-radius: 3px;
  z-index: 0;
}

/* Ensure the fill paints over the ghost */
.src-cod-row .src-bar-fill {
  position: relative;
  z-index: 1;
}

/* Compare-mode legend below the table */
.src-cod-legend {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-md);
  margin-top: var(--space-sm);
  font-size: 10px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--text-dim);
}

.src-cod-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.src-cod-legend-tail {
  margin-left: auto;
  font-style: normal;
}

.src-cod-legend-swatch {
  display: inline-block;
  width: 18px;
  height: 6px;
  border-radius: 2px;
  border: 1px solid var(--border-subtle);
}

.src-cod-legend-swatch.src-cod-legend-fill {
  background: var(--accent);
  border-color: var(--accent);
}

.src-cod-legend-swatch.src-cod-legend-ghost {
  background: repeating-linear-gradient(
    90deg,
    var(--border) 0 2px,
    transparent 2px 4px
  );
}


/* ═══════════ HEALTH SCORE CARD (overview) ═══════════ */
.health-score-card {
  text-align: center;
  padding: var(--space-lg) var(--space-xl);
  background: rgba(245, 246, 248, 0.72);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid rgba(232, 234, 237, 0.6);
  border-radius: var(--radius-md);
  box-shadow:
    0 1px 3px rgba(0, 0, 0, 0.03),
    inset 0 0.5px 0 rgba(255, 255, 255, 0.7);
}

.health-score-value {
  font-size: 56px;
  font-weight: 300;
  line-height: 1;
}

.health-score-fraction {
  font-size: 11px;
  color: var(--text-dim);
  letter-spacing: 0.5px;
  margin-top: var(--space-sm);
}

.health-score-divider {
  height: 1px;
  background: var(--border-subtle);
  margin: var(--space-md) calc(-1 * var(--space-xl)) var(--space-sm);
}

.health-score-takeaway {
  font-size: 11px;
  letter-spacing: 0.4px;
  color: var(--text-secondary);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  flex-wrap: wrap;
  line-height: 1.4;
}

.health-score-takeaway.green {
  color: var(--green);
  letter-spacing: 1.2px;
  text-transform: uppercase;
  font-size: 10px;
  font-weight: 600;
}

.takeaway-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  flex-shrink: 0;
}
.takeaway-dot.green { background: var(--green); }
.takeaway-dot.amber { background: var(--amber); }
.takeaway-dot.red { background: var(--red); }

.takeaway-names {
  color: var(--text-primary);
  font-weight: 500;
}

.overview-allclear {
  text-align: center;
  padding: var(--space-xl) var(--space-lg);
  font-size: 12px;
  color: var(--green);
  letter-spacing: 0.5px;
  background: rgba(245, 246, 248, 0.72);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid rgba(232, 234, 237, 0.6);
  border-radius: var(--radius-md);
}

/* ═══════════ INSIGHT CARDS (overview, single-line) ═══════════ */
.insights-stack {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.insight-card {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 10px;
  padding: 7px 10px;
  background: transparent;
  border: none;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background 0.15s;
  font-family: var(--font-mono);
  text-align: left;
  box-shadow: none;
  min-width: 0;
}

.insight-card:hover {
  background: rgba(245, 246, 248, 0.72);
}

.insight-meter {
  display: inline-flex;
  align-items: flex-end;
  gap: 2px;
  height: 12px;
}

.insight-meter i {
  display: block;
  width: 3px;
  border-radius: 1px;
  background: var(--border);
}

.insight-meter i:nth-child(1) { height: 5px; }
.insight-meter i:nth-child(2) { height: 8px; }
.insight-meter i:nth-child(3) { height: 12px; }

.insight-meter i.on { background: var(--text-secondary); }

.insight-pair {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 11px;
  color: var(--text-primary);
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.insight-marker-name {
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.insight-arrow {
  color: var(--text-dim);
  font-size: 10px;
  margin: 0 1px;
  flex-shrink: 0;
}

.insight-tail {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 10px;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
}

.insight-r {
  color: var(--text-secondary);
  font-weight: 500;
}

.insight-conflict {
  color: var(--amber);
  font-weight: 700;
  font-size: 11px;
}

.insights-empty {
  padding: var(--space-sm) var(--space-md);
  font-size: 10px;
  color: var(--text-dim);
  letter-spacing: 0.3px;
  line-height: 1.5;
  text-align: left;
}

/* ═══════════ STATUS STRIP (overview bottom) ═══════════ */
.strip-segment {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 14px 16px;
  background: transparent;
  border: none;
  border-right: 1px solid var(--border-subtle);
  cursor: pointer;
  transition: background 0.15s;
  font-family: var(--font-mono);
  text-align: left;
  min-width: 0;
}

.strip-segment:last-child { border-right: none; }

.strip-segment:hover {
  background: rgba(245, 246, 248, 0.72);
}

.strip-segment.empty {
  cursor: default;
}

.strip-segment.empty .strip-name { color: var(--text-dim); }

.strip-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  flex-shrink: 0;
  background: var(--text-dim);
}

.strip-dot.green { background: var(--green); }
.strip-dot.amber { background: var(--amber); }
.strip-dot.red { background: var(--red); }

.strip-name {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--text-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.strip-detail {
  font-size: 10px;
  color: var(--text-dim);
  letter-spacing: 0.3px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ═══════════ SCORECARD OVERALL BAR ═══════════ */
.scorecard-overall {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-md) 0;
  margin-bottom: var(--space-lg);
  border-bottom: 1px solid var(--border-subtle);
}

.scorecard-overall-label {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--text-dim);
}

.scorecard-overall-score {
  font-size: 24px;
  font-weight: 400;
}

.scorecard-overall-score.green { color: var(--green); }
.scorecard-overall-score.amber { color: var(--amber); }
.scorecard-overall-score.red { color: var(--red); }

.scorecard-overall-detail {
  font-size: 12px;
  color: var(--text-dim);
}

/* ═══════════ SCORECARD DOMAIN HEADER ═══════════ */
.scorecard-domain-header {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  margin-bottom: var(--space-xs);
}

.scorecard-domain-header .section-title {
  margin-bottom: 0;
}

.domain-score-badge {
  font-size: 13px;
  font-weight: 500;
  padding: 2px 8px;
  border-radius: 4px;
}

.domain-flagged-ratio {
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.5px;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
  margin-left: var(--space-xs);
}

/* In show-all mode, the domain header reflects "0 of N" for nominal-only
   domains. Same dim label, no special treatment needed. */

.domain-score-badge.green { color: var(--green); background: var(--green-bg); }
.domain-score-badge.amber { color: var(--amber); background: var(--amber-bg); }
.domain-score-badge.red { color: var(--red); background: var(--red-bg); }

/* ═══════════ SCORECARD RANGE BAR (inline) ═══════════ */
.scorecard-range {
  flex: 1;
  min-width: 80px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 3px;
}

.range-labels {
  position: relative;
  height: 12px;
}

.range-label-threshold {
  position: absolute;
  transform: translateX(-50%);
  font-size: 10px;
  color: var(--text-dim);
  white-space: nowrap;
  line-height: 1;
}

.scorecard-range .range-bar {
  display: block;
  min-width: 0;
  width: 100%;
  height: 6px;
}

.scorecard-range .range-bar-marker {
  top: -3px;
  width: 3px;
  height: 12px;
}

/* ═══════════ SCORECARD EXPANDABLE CONTEXT ═══════════ */
.scorecard-row.expandable {
  cursor: pointer;
  border-radius: var(--radius-sm);
  padding-left: var(--space-xs);
  margin-left: calc(-1 * var(--space-xs));
  transition: background 0.15s;
}

.scorecard-row.expandable:hover {
  background: var(--bg-hover);
}

.scorecard-row.expanded {
  background: var(--bg-surface);
}

.scorecard-expand-arrow {
  font-size: 11px;
  color: var(--text-dim);
  margin-left: auto;
  opacity: 0.6;
}

.scorecard-context {
  padding: var(--space-sm) var(--space-md);
  margin: 0 0 var(--space-sm);
  border-left: 2px solid var(--border);
  font-size: 12px;
  line-height: 1.6;
  animation: expand-in 0.15s ease-out;
}

.scorecard-context-what {
  color: var(--text-secondary);
  margin-bottom: var(--space-xs);
}

.scorecard-context-detail {
  color: var(--text-dim);
  font-style: italic;
}

.scorecard-context .marker-actions {
  margin-top: var(--space-sm);
}

/* (scorecard nominal groups removed — nominal markers now inline in domain sections) */

/* ═══════════ EXPLAINER POPOVERS ═══════════ */
.explainer-wrap {
  position: relative;
  display: inline-flex;
  align-items: center;
}

.explainer-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: none;
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 600;
  color: var(--text-dim);
  cursor: pointer;
  transition: all 0.15s;
  vertical-align: middle;
  margin-left: 4px;
  flex-shrink: 0;
}

.explainer-btn:hover,
.explainer-wrap:hover .explainer-btn {
  color: var(--accent);
  border-color: var(--accent);
}

.explainer-text {
  position: absolute;
  top: calc(100% + 6px);
  left: -4px;
  z-index: 300;
  width: 280px;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: var(--space-sm) var(--space-md);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  color: var(--text-secondary);
  line-height: 1.5;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.15s;
}

.explainer-wrap:hover .explainer-text {
  opacity: 1;
}

/* ═══════════ TOPBAR HELP ═══════════ */
.topbar-help {
  width: 30px;
  height: 30px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: transparent;
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 500;
  color: var(--text-dim);
  cursor: pointer;
  transition: all 0.15s;
}

.topbar-help:hover {
  color: var(--accent);
  border-color: var(--accent);
}

/* Share — Overview-scoped utility, sibling to ?/data. Icon button on the
   same 30px frame as help so the right cluster stays even. Stays visible
   on mobile (it's the virality affordance — reach matters), unlike the
   TRENDS/?/iOS trio that fold into the overflow menu below 600px. */
.topbar-share {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text-dim);
  cursor: pointer;
  transition: all 0.15s;
}
.topbar-share:hover {
  color: var(--accent);
  border-color: var(--accent);
}
.topbar-share.is-busy { opacity: 0.5; pointer-events: none; }

/* Tour celebration uses this to draw the eye to the re-entry point.
   A two-ring sonar pulse on the help button for the duration of the
   completion toast — the inner ring is saturated, the outer ring is
   a softer halo trailing behind. Faster cadence than the first-time
   toast (which is ambient) because this fires for ~7s and needs to
   read as a directional cue, not background ambience. */
.topbar-help.is-tour-help-pulse {
  color: var(--accent);
  border-color: var(--accent);
  animation: topbarHelpPulse 1.6s cubic-bezier(0.2, 0.6, 0.3, 1) infinite;
}
@keyframes topbarHelpPulse {
  0%, 100% {
    box-shadow:
      0 0 0 0 transparent,
      0 0 0 0 transparent;
  }
  18% {
    box-shadow:
      0 0 0 0 color-mix(in srgb, var(--accent) 40%, transparent),
      0 0 0 0 color-mix(in srgb, var(--accent) 18%, transparent);
  }
  75% {
    box-shadow:
      0 0 0 9px transparent,
      0 0 0 18px transparent;
  }
}
@media (prefers-reduced-motion: reduce) {
  .topbar-help.is-tour-help-pulse { animation: none; }
}

/* While the framework overlay is open, the same button acts as the
   close affordance — visually active and showing × instead of ?. The
   glyph swap is via ::after so the underlying text content stays "?"
   (used by screen readers via the title attribute).

   The active state must read as clearly distinct from :hover on the
   default button, otherwise it looks like a stuck hover. So instead
   of a tinted/bordered look (which mirrors :hover), we go solid:
   filled accent background, white glyph, matching border. This is
   the "engaged / toggle-on" texture — unambiguously different from
   the "you're pointing at me" texture of :hover. */
.topbar-help[aria-expanded="true"] {
  color: #fff;
  border-color: var(--accent);
  background: var(--accent);
  font-size: 0;            /* hide the literal ? */
  position: relative;
}
.topbar-help[aria-expanded="true"]:hover {
  background: #0a7d99;     /* slightly darker accent on hover */
  border-color: #0a7d99;
}
.topbar-help[aria-expanded="true"]::after {
  content: '×';
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 22px;
  font-weight: 500;
  line-height: 1;
}
