/* ─── Badge progress rings (SVG arc) ─────────────────────────────────────────
   Horseshoe-style arc ring: 270° span, 90° gap at bottom (6 o'clock).
   Arc runs from 7:30 to 4:30. Implemented as inline SVG in badge-ring.js.
   The fill circle uses stroke-dashoffset for smooth CSS-transition animation.

   .ring-wrap: relative container so the text label can be absolutely centered.
   .ring-label: overlays tier initial + level number on top of the SVG.
   .ring-pct: smaller % display inside the label. */

.ring-wrap {
  position: relative;
  flex-shrink: 0;
  display: inline-flex;
}

.ring-wrap.sm { width: 48px; height: 48px; }
.ring-wrap.md { width: 72px; height: 72px; }
.ring-wrap.lg { width: 90px; height: 90px; }

.ring-label {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  font-weight: 850;
  line-height: 1.2;
  text-align: center;
  color: var(--text);
  pointer-events: none; /* label should never intercept clicks on parent */
}
.ring-wrap.sm .ring-label { font-size: 9px;  }
.ring-wrap.md .ring-label { font-size: 11px; }
.ring-wrap.lg .ring-label { font-size: 13px; }

.ring-pct { font-size: 8px; color: var(--muted); font-weight: 600; }

/* Arrow navigation button — compact replacement for text "Continue" in list rows.
   The › character signals forward navigation without consuming horizontal space. */
.icon-btn { padding: 8px 14px; font-size: 18px; min-width: unset; line-height: 1; }

/* Lesson type icon — shown inline before lesson title on topic page */
.lesson-icon { font-size: 11px; color: var(--muted); margin-right: 6px; flex-shrink: 0; }

/* ─── Bonus indicator ────────────────────────────────────────────────────────
   Shown on badges and lessons tied to the daily task bonus path.
   Uses warn color to visually signal "this is the high-value action today". */
.bonus-label {
  font-size: 10px;
  font-weight: 850;
  color: var(--warn);
  background: var(--warn-bg);
  border: 1px solid var(--warn-border);
  border-radius: 999px;
  padding: 2px 7px;
  white-space: nowrap;
}

/* ─── Content state badges ───────────────────────────────────────────────────
   Applied to lesson items on the topic page. The state tells users where they
   are in each piece of content without requiring them to tap in to find out. */
.state-badge {
  font-size: 10px;
  font-weight: 850;
  border-radius: 999px;
  padding: 3px 8px;
  white-space: nowrap;
}
.state-badge.not-started { background: var(--soft);         color: var(--muted);  }
.state-badge.in-progress  { background: var(--accent-soft); color: var(--accent); }
.state-badge.completed    { background: var(--good-bg); color: var(--good); border: 1px solid var(--good-border); }

/* ─── Content type tags ──────────────────────────────────────────────────────
   Lesson vs refresher must feel visually distinct — PRD requires they are
   understood as different content types before the user taps in. */
.content-type-tag {
  font-size: 9px;
  font-weight: 850;
  text-transform: uppercase;
  letter-spacing: .05em;
  border-radius: 6px;
  padding: 2px 6px;
  display: inline-block;
  margin-bottom: 4px;
}
.content-type-tag.lesson    { background: var(--accent-soft); color: var(--accent); }
.content-type-tag.refresher { background: var(--warn-bg);     color: var(--warn);   }

/* ─── Quiz answer states ─────────────────────────────────────────────────────
   Correct/incorrect feedback on quiz choices. Wrong answers show red background
   and are visually disabled — user must keep trying until correct. This is intentional
   UX per spec: quiz is a learning tool, not a pass/fail gate. */
.choice.correct  { border-color: var(--good); background: var(--good-bg); color: var(--good); }
.choice.wrong    { border-color: var(--danger); background: var(--danger-bg);
                   color: var(--danger); opacity: 0.55;
                   text-decoration: line-through; }

/* ─── Ring label level-up pulse ──────────────────────────────────────────────
   Applied momentarily via JS when the level number increments during the reward
   animation. Class is added by setTimeout, removed after 0.4s so it can retrigger
   for a second level-up on the same ring. Translate keeps it centered during scale. */
@keyframes ring-label-pulse {
  0%   { transform: translate(-50%,-50%) scale(1);   opacity: 1;   }
  40%  { transform: translate(-50%,-50%) scale(1.5); opacity: 0.8; }
  100% { transform: translate(-50%,-50%) scale(1);   opacity: 1;   }
}
.ring-label.pulse { animation: ring-label-pulse 0.4s ease; }

/* ─── Bonus corner indicators ────────────────────────────────────────────────
   .bonus-corner: large indicator with "⚡ Bonus" text — badge board cards and
   topic page header. Positioned absolute top-right; parent needs position:relative.
   .bonus-corner-sm: icon-only — lesson items in topic page. Smaller, no text. */
.bonus-corner {
  position: absolute;
  top: 8px;
  right: 10px;
  font-size: 9px;
  font-weight: 850;
  color: var(--warn);
  background: var(--warn-bg);
  border: 1px solid var(--warn-border);
  border-radius: 999px;
  padding: 2px 6px;
  white-space: nowrap;
}
.bonus-corner-sm {
  position: absolute;
  top: 6px;
  right: 8px;
  font-size: 11px;
  color: var(--warn);
}

/* ─── Reward screen — event badges (level up / tier up) ──────────────────────
   Shown ABOVE the ring for any badge that leveled up or tiered up.
   A fixed-height slot reserves space in every cell so all rings are vertically
   aligned regardless of which badges had events. Default color = green (level up);
   tier-up variant overrides to gold inline. */
.reward-event-badge {
  font-size: 10px;
  font-weight: 850;
  color: var(--good);
  background: var(--good-bg);
  border: 1px solid var(--good-border);
  border-radius: 999px;
  padding: 2px 8px;
  white-space: nowrap;
}
.reward-event-badge--warn {
  font-size: 10px;
  font-weight: 850;
  color: var(--warn);
  background: var(--warn-bg);
  border: 1px solid var(--warn-border);
  border-radius: 999px;
  padding: 2px 8px;
  white-space: nowrap;
}

/* ─── Lesson Player ──────────────────────────────────────────────────────────
   Full-height, non-scrolling three-section layout:
     .lp-stage    — top visual area (accent bg, waveform, title)
     .lp-subtitle — middle subtitle strip (prev / current / next sentence)
     .lp-controls — bottom audio controls bar
   screenRoot gets .lesson-mode in render.js, which removes padding + overflow. */

.lp-layout {
  display: flex;
  flex-direction: column;
  height: 100%;
}

/* ── Banner ──
   Back control and the lesson title, above the stage. The stage keeps its own
   height, so the banner takes its space from .lp-subtitle (flex: 1). */
.lp-banner {
  flex: 0 0 auto;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 52px;
  padding: 10px 56px;
  background: var(--accent);
}
.lp-banner-title {
  margin: 0;
  font-size: 17px;
  font-weight: 850;
  letter-spacing: -.02em;
  line-height: 1.2;
  text-align: center;
  color: var(--on-dark);
}

/* ── Stage ── */
.lp-stage {
  flex: 0 0 42%;
  background: var(--accent);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  padding: 24px 20px;
  position: relative;
}

/* Video mode: the animation owns the whole stage, edge to edge. */
.lp-stage-video { padding: 4px 10px 12px; gap: 0; }
.lp-hyperframes { width: 100%; height: 100%; display: block; }

.lp-stage-title {
  font-size: 22px;
  font-weight: 850;
  color: var(--on-dark);
  text-align: center;
  letter-spacing: -.03em;
  margin: 0;
  line-height: 1.25;
}

/* Back button — top-left of staging area */
.lp-back-btn {
  position: absolute;
  top: 14px;
  left: 14px;
  background: rgba(255, 255, 255, 0.2);
  border: none;
  color: var(--on-dark);
  font-size: 24px;
  font-weight: 850;
  border-radius: 10px;
  width: 36px;
  height: 36px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
}

/* Waveform bars — 7 bars, each with a unique keyframe for organic feel */
.lp-wave {
  display: flex;
  align-items: flex-end;
  gap: 5px;
  height: 56px;
}

.lp-wave-hidden { display: none; }

.lp-bar {
  width: 5px;
  height: 10px;
  background: rgba(255, 255, 255, 0.65);
  border-radius: 3px;
}

@keyframes lp-b1 { 0%,100% { height: 10px; } 50% { height: 50px; } }
@keyframes lp-b2 { 0%,100% { height: 22px; } 50% { height: 46px; } }
@keyframes lp-b3 { 0%,100% { height: 8px;  } 50% { height: 54px; } }
@keyframes lp-b4 { 0%,100% { height: 18px; } 50% { height: 48px; } }
@keyframes lp-b5 { 0%,100% { height: 12px; } 50% { height: 42px; } }
@keyframes lp-b6 { 0%,100% { height: 26px; } 50% { height: 52px; } }
@keyframes lp-b7 { 0%,100% { height: 6px;  } 50% { height: 46px; } }

.lp-wave.playing .lp-bar:nth-child(1) { animation: lp-b1 0.70s ease-in-out infinite; }
.lp-wave.playing .lp-bar:nth-child(2) { animation: lp-b2 0.90s ease-in-out infinite 0.12s; }
.lp-wave.playing .lp-bar:nth-child(3) { animation: lp-b3 0.65s ease-in-out infinite 0.28s; }
.lp-wave.playing .lp-bar:nth-child(4) { animation: lp-b4 0.85s ease-in-out infinite 0.05s; }
.lp-wave.playing .lp-bar:nth-child(5) { animation: lp-b5 0.75s ease-in-out infinite 0.40s; }
.lp-wave.playing .lp-bar:nth-child(6) { animation: lp-b6 0.80s ease-in-out infinite 0.20s; }
.lp-wave.playing .lp-bar:nth-child(7) { animation: lp-b7 0.70s ease-in-out infinite 0.35s; }

/* ── Subtitle strip ── */
.lp-subtitle {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 16px 22px;
  background: var(--screen);
}

.lp-sub-prev,
.lp-sub-next {
  font-size: 12px;
  color: var(--muted);
  text-align: center;
  line-height: 1.5;
  margin: 0;
  min-height: 1.5em;
  opacity: 0.6;
}

.lp-sub-curr {
  font-size: 15px;
  font-weight: 700;
  color: var(--text);
  text-align: center;
  line-height: 1.55;
  margin: 0;
  min-height: 2em;
}

/* ── Controls bar ── */
.lp-controls {
  flex: 0 0 auto;
  padding: 14px 16px 16px;
  border-top: 1px solid var(--line);
  background: var(--screen);
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.lp-ctrl-row {
  display: flex;
  gap: 8px;
  align-items: center;
}

.lp-ctrl-btn { flex: 1; font-size: 13px; }
.lp-speed-btn { min-width: 46px; }

.lp-progress-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.lp-time-label {
  font-size: 11px;
  color: var(--muted);
  white-space: nowrap;
  min-width: 30px;
}

/* Progress track — also the scrubber. */
.lp-progress {
  flex: 1;
  height: 6px;
  background: var(--progress-bg);
  border-radius: 999px;
  position: relative;
  cursor: pointer;
  touch-action: none;          /* the drag is ours, not the browser's scroll */
}
/* Invisible grab area, so a 6px bar is still a comfortable target. */
.lp-progress::before {
  content: "";
  position: absolute;
  left: 0; right: 0; top: -15px; bottom: -15px;
}
.lp-progress:focus-visible { outline: 2px solid var(--accent); outline-offset: 6px; }

.lp-progress-fill {
  height: 100%;
  background: var(--accent);
  border-radius: 999px;
  /* Short linear transition interpolates between the ~100ms clock ticks so the
     bar reads as continuous motion rather than 10 discrete steps per second. */
  transition: width 0.12s linear;
}

.lp-progress-knob {
  position: absolute;
  top: 50%;
  width: 14px; height: 14px;
  margin-left: -7px;
  border-radius: 50%;
  background: var(--accent);
  border: 2px solid var(--screen);
  transform: translateY(-50%);
  transition: left 0.12s linear;
  pointer-events: none;
}

/* While dragging, the smoothing transition would trail the finger. */
.lp-progress.scrubbing .lp-progress-fill,
.lp-progress.scrubbing .lp-progress-knob { transition: none; }

/* Cards */
.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 18px;
  padding: 16px;
  margin-bottom: 14px;
  box-shadow: 0 1px 4px rgba(16, 24, 40, .04);
}

.admin-card {
  background: var(--chrome-card);
  border: 1px solid var(--chrome-line);
  border-radius: 18px;
  padding: 14px;
  margin-bottom: 14px;
  color: var(--chrome-text);
}

.admin-card-title {
  font-size: 13px;
  font-weight: 850;
  margin: 0 0 10px;
}

.item-card {
  background: var(--chrome-card);
  border: 1px solid var(--chrome-line);
  border-radius: 18px;
  padding: 14px;
  margin-bottom: 10px;
}

.item-card:hover,
.task-card:hover {
  background: var(--soft);
  cursor: pointer;
}

.stage-card {
  height: 245px;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  font-size: 34px;
  font-weight: 850;
  letter-spacing: -.04em;
  /* Anchors .chat-buddy-btn, which deliberately overhangs the bottom edge.
     The extra bottom margin is the room that overhang needs. */
  position: relative;
  margin-bottom: 34px;
}

/* Chat pill — straddles the stage's bottom edge, half on/half off, so it reads
   as attached to the scene rather than to the task list below it. */
.chat-buddy-btn {
  position: absolute;
  bottom: -20px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--card);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 12px 26px;
  font-size: 15px;
  font-weight: 850;
  letter-spacing: 0;
  white-space: nowrap;
  cursor: pointer;
  box-shadow: 0 6px 16px rgba(16, 24, 40, .16);
  z-index: 2;
}

.chat-buddy-btn:hover  { background: var(--soft); }
.chat-buddy-btn:active { opacity: .86; }

/* Typography */
.title {
  font-size: 24px;
  font-weight: 850;
  letter-spacing: -.03em;
  margin: 0;
}

.subtitle {
  font-size: 13px;
  color: var(--muted);
  line-height: 1.35;
  margin: 6px 0 0;
}

.section-title {
  font-size: 16px;
  font-weight: 850;
  margin: 0 0 10px;
}

/* Semantic typography helpers — use instead of inline font-size/weight styles */
.label    { font-size: 10px; font-weight: 850; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
.stat-val { font-size: 13px; font-weight: 700; }
.caption  { font-size: 11px; color: var(--muted); }

.helper {
  font-size: 12px;
  color: var(--muted);
  line-height: 1.35;
}

/* Spacing utility overrides */
.mb-sm  { margin-bottom: 8px;  }
.mb-md  { margin-bottom: 12px; }
.mb-0   { margin-bottom: 0;    }

/* Layout helpers */
.row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
}

.grid-two {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}

/* Buttons */
/* --on-accent, not --on-dark: --accent inverts between the light and dark
   themes, so the text on it has to invert too (see variables.css). */
.button {
  border: 1px solid var(--accent);
  background: var(--accent);
  color: var(--on-accent);
  padding: 10px 13px;
  border-radius: 13px;
  font-size: 13px;
  font-weight: 850;
  cursor: pointer;
  white-space: nowrap;
}

/* .primary is an alias for the default button style — explicit intent marker */
.button.primary {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
}

.button.secondary {
  border-color: var(--line);
  background: var(--card);
  color: var(--text);
}

/* Compact button variant for inline actions inside cards */
.button.small {
  font-size: 11px;
  padding: 5px 10px;
}

.button.full {
  width: 100%;
}

.button:not(:disabled):hover        { filter: brightness(1.06); }
.button.secondary:not(:disabled):hover { filter: none; background: var(--soft); }
.button:not(:disabled):active       { opacity: .86; }

.button:disabled {
  opacity: .55;
  cursor: default;
}

/* Forms */
.input-group {
  margin-bottom: 12px;
}

.input-group label {
  display: block;
  font-size: 11px;
  color: var(--muted);
  font-weight: 850;
  margin-bottom: 5px;
}

.input-group input,
.input-group textarea,
.input-group select {
  width: 100%;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--card);
  color: var(--text);
  font-size: 13px;
  padding: 9px;
  font-family: Arial, Helvetica, sans-serif;
  outline: none;
}

.input-group textarea {
  min-height: 58px;
  resize: vertical;
}

/* Progress */
.progress {
  height: 10px;
  background: var(--progress-bg);
  border-radius: 999px;
  overflow: hidden;
  margin-top: 10px;
}

.progress-fill {
  height: 100%;
  background: var(--accent);
  border-radius: 999px;
}

/* Pills */
.pill-row {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.pill {
  border: 1px solid var(--accent-border);
  background: var(--accent-soft);
  color: var(--accent);
  border-radius: 999px;
  padding: 7px 10px;
  font-size: 12px;
  font-weight: 850;
}

/* Badges */
.badge-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}

.badge-card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 18px;
  padding: 13px;
}

.badge-patch {
  width: 52px;
  height: 52px;
  border: 1px solid var(--line);
  border-radius: 18px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 850;
  color: var(--muted);
  margin-bottom: 10px;
  background: var(--soft);
}

.badge-name {
  font-size: 13px;
  font-weight: 850;
  margin-bottom: 4px;
}

/* Task cards */
.home-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 12px;
  margin-bottom: 14px;
}

.settings-btn {
  min-width: 74px;
  height: 38px;
  border-radius: 13px;
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--muted);
  font-size: 12px;
  font-weight: 850;
  cursor: pointer;
}

.task-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding-bottom: 8px;
}

.task-card {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 12px;
  align-items: center;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 18px;
  padding: 14px;
  box-shadow: 0 1px 4px rgba(16, 24, 40, .04);
}

.task-card.completed {
  opacity: .72;
  background: var(--soft);
}

.task-title {
  font-size: 14px;
  font-weight: 850;
  margin: 0 0 4px;
}

.task-desc {
  font-size: 12px;
  color: var(--muted);
  line-height: 1.35;
  margin: 0;
}

.done-state {
  border: 1px solid var(--good-border);
  background: var(--good-bg);
  color: var(--good);
  border-radius: 13px;
  padding: 9px 11px;
  font-size: 12px;
  font-weight: 850;
  white-space: nowrap;
}

/* Flow */
.flow-footer {
  display: flex;
  gap: 10px;
  margin-top: 14px;
}

/* Lesson */
.audio-box {
  height: 64px;
  border: 1px solid var(--line);
  border-radius: 15px;
  background: var(--soft);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  font-size: 12px;
  font-weight: 850;
}

/* Quiz */
.choice {
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--text);
  border-radius: 15px;
  padding: 13px;
  text-align: left;
  cursor: pointer;
  font-weight: 800;
}

.choice.selected {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent);
}

/* Marketplace */
.market-category {
  font-size: 12px;
  font-weight: 850;
  color: var(--accent);
  text-transform: uppercase;
  letter-spacing: .04em;
  margin-bottom: 8px;
}

/* Notes */
.note {
  background: var(--card);
  border: 1px dashed var(--line);
  border-radius: 16px;
  padding: 12px;
  font-size: 12px;
  color: var(--muted);
  line-height: 1.35;
}

/* Baby Budget iframe */
.bb-iframe {
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
  background: var(--screen);
}

/* ─── Budget Dashboard ────────────────────────────────────────────────────── */

/* Ghost overlay — empty-state promise view */
.budget-ghost-wrap { position: relative; margin-bottom: 14px; }
.budget-ghost-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  z-index: 2;
}
.budget-ghost-card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 20px;
  padding: 20px;
  width: 100%;
  max-width: 280px;
  box-shadow: 0 8px 24px rgba(0,0,0,.10);
}

/* Category tile grid — 2 columns; savings spans full width */
.budget-tile-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-bottom: 14px;
}
.budget-tile {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 16px;
  padding: 14px;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: 5px;
  transition: box-shadow .15s;
}
.budget-tile:active { box-shadow: 0 2px 10px rgba(49,94,251,.18); }
.budget-tile-full { grid-column: 1 / -1; }

.budget-tile-top {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 2px;
}
.budget-tile-icon { font-size: 16px; flex-shrink: 0; }
.budget-tile-name { font-size: 12px; font-weight: 850; flex: 1; }
.budget-tile-amount { font-size: 20px; font-weight: 850; line-height: 1.1; }
.budget-tile-delta  { font-size: 10px; color: var(--muted); line-height: 1.3; }
.budget-tile-target { font-size: 10px; font-weight: 850; color: var(--accent); }

/* Signal pills */
.budget-signal-pill {
  display: inline-flex;
  align-items: center;
  font-size: 10px;
  font-weight: 850;
  padding: 3px 8px;
  border-radius: 99px;
  width: fit-content;
  white-space: nowrap;
}
.budget-signal-pill.on-track      { background: var(--good-pill-bg);        color: var(--good-pill-text); }
.budget-signal-pill.intentional   { background: var(--accent-soft);         color: var(--accent); }
.budget-signal-pill.worth-a-look  { background: var(--warn-pill-bg);        color: var(--warn-pill-text); }
.budget-signal-pill.worth-a-look.strong { background: var(--warn-pill-strong-bg); color: var(--warn-pill-strong-text); }
.budget-signal-pill.context-only  { background: var(--soft);                color: var(--muted); }

/* Goals mini — inside Savings tile */
.budget-goals-mini {
  margin-top: 4px;
  padding-top: 8px;
  border-top: 1px solid var(--line);
}
.budget-goals-row {
  display: flex;
  justify-content: space-between;
  font-size: 10px;
  color: var(--muted);
  padding: 2px 0;
  cursor: pointer;
}

/* Header metrics — Income / Plan / Remaining */
.budget-header-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin-top: 12px;
}
.budget-header-metric { text-align: center; }
.budget-header-label {
  font-size: 10px;
  font-weight: 850;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--muted);
  margin-bottom: 2px;
}
.budget-header-value { font-size: 18px; font-weight: 850; line-height: 1.1; }
.budget-header-sub   { font-size: 9px; color: var(--muted); }

/* Expandable overhead detail */
.budget-overhead-detail.hidden { display: none; }

/* ─── Budget Category Detail ─────────────────────────────────────────────── */

/* Intentional toggle pill */
.budget-intentional-toggle {
  flex-shrink: 0;
  padding: 8px 18px;
  border-radius: 99px;
  border: 2px solid var(--line);
  background: var(--soft);
  color: var(--muted);
  font-size: 13px;
  font-weight: 850;
  cursor: pointer;
  transition: border-color .15s, background .15s, color .15s;
}
.budget-intentional-toggle.active {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent);
}

/* ±$25 stepper */
.budget-stepper {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}
.budget-stepper-btn {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: 1.5px solid var(--line);
  background: var(--soft);
  font-size: 20px;
  font-weight: 850;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex-shrink: 0;
  line-height: 1;
  transition: background .1s;
}
.budget-stepper-btn:active { background: var(--accent-soft); }
.budget-stepper-input {
  flex: 1;
  text-align: center;
  font-size: 22px;
  font-weight: 850;
  border: 2px solid var(--line);
  border-radius: 12px;
  padding: 8px 12px;
  background: var(--card);
  color: var(--text);
  -moz-appearance: textfield;
}
.budget-stepper-input::-webkit-outer-spin-button,
.budget-stepper-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.budget-stepper-input:focus { outline: none; border-color: var(--accent); }

/* Goals V2 form fields — theme-aware (light + dark), no number spinners.
   Mirrors .input-group input; spinner removal scoped to number inputs so
   <select> keeps its native caret. */
.goal-field {
  width: 100%;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--card);
  color: var(--text);
  font-size: 13px;
  padding: 9px;
  font-family: inherit;
  outline: none;
}
.goal-field:focus { border-color: var(--accent); }
.goal-field option { background: var(--card); color: var(--text); }
input.goal-field[type="number"] { -moz-appearance: textfield; }
input.goal-field[type="number"]::-webkit-outer-spin-button,
input.goal-field[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* Income-relative progress bar with peer marker */
.budget-sub-bar {
  position: relative;
  height: 8px;
  border-radius: 99px;
  background: var(--bar);
  overflow: visible;
}
.budget-sub-bar-fill {
  height: 100%;
  border-radius: 99px;
  background: var(--accent);
  max-width: 100%;
  transition: width .2s ease;
}
.budget-sub-bar-peer {
  position: absolute;
  top: -3px;
  width: 2px;
  height: 14px;
  background: var(--warn);
  border-radius: 2px;
  transform: translateX(-50%);
  opacity: 0.8;
}

/* Card color variants — accent-soft or warn backgrounds */
.card.card--accent { background: var(--accent-soft); border-color: var(--accent-border); }
.card.card--warn   { background: var(--warn-bg);     border-color: var(--warn-border);   }

/* Inline alert strip — warn variant (reconciliation signal, budget alerts) */
.budget-alert--warn {
  padding: 10px 12px;
  background: var(--warn-bg);
  border: 1px solid var(--warn-border);
  border-radius: 12px;
  font-size: 12px;
}

/* Debt strategy pills — semantic colors for each payoff strategy */
.strategy-pill--snowball  { color: var(--accent); }
.strategy-pill--avalanche { color: var(--good);   }
.strategy-pill--equal     { color: var(--warn);   }
.strategy-pill--minimum   { color: var(--muted);  }

/* ─── Debt screens ─────────────────────────────────────────────────────────── */
.debt-card-chevron {
  font-size: 12px;
  color: var(--muted);
  transition: transform .15s;
  display: inline-block;
}
.debt-card-chevron.open {
  transform: rotate(180deg);
}
.debt-best-badge {
  font-size: 10px;
  font-weight: 850;
  background: var(--good-bg);
  color: var(--good);
  border: 1px solid var(--good-border);
  border-radius: 999px;
  padding: 2px 8px;
  white-space: nowrap;
  flex-shrink: 0;
}
.debt-graph-wrap {
  width: 100%;
  overflow-x: auto;
}
.debt-strategy-best {
  border-color: var(--good) !important;
}

.payoff-behavior-btn {
  background: var(--card);
  color: var(--text);
  border: 1.5px solid var(--line);
}
.payoff-behavior-btn.active {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent);
}

/* ─── Lifestyle question chain ────────────────────────────────────────────────
   Ported from the retired 2MB wizard. Used by lifestyle-chain.js.
*/
.lifestyle-img-box {
  width: 100%;
  height: 120px;
  background: var(--progress-bg, var(--soft));
  border-radius: 16px;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}
.lifestyle-img-section {
  position: absolute;
  top: 10px;
  left: 14px;
  font-size: 11px;
  font-weight: 850;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: .04em;
}
.lifestyle-img-coords {
  font-size: 22px;
  font-weight: 850;
  color: var(--accent);
  letter-spacing: .2em;
}
.lifestyle-img-subprog {
  position: absolute;
  bottom: 10px;
  right: 14px;
  font-size: 11px;
  color: var(--muted);
}
.tier-pill-row {
  display: flex;
  gap: 8px;
  margin: 12px 0 10px;
  flex-wrap: wrap;
}
.tier-pill {
  flex: 1;
  min-width: 60px;
  text-align: center;
  padding: 9px 6px;
  border: 1.5px solid var(--line);
  border-radius: 10px;
  cursor: pointer;
  font-size: 12px;
  font-weight: 700;
  background: var(--card);
  -webkit-user-select: none;
  user-select: none;
  transition: background .12s, border-color .12s, color .12s;
}
.tier-pill.active {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent);
}
.tier-selected-desc {
  font-size: 12px;
  color: var(--text);
  line-height: 1.45;
  padding: 9px 11px;
  background: var(--soft);
  border-radius: 10px;
}

/* ─── Chat with Buddy ────────────────────────────────────────────────────────
   Screen: screens/chat.js. The shell is a full-height flex column so the thread
   scrolls on its own while the chips + input bar stay pinned to the bottom —
   this requires .screen-scroll.chat-mode (layout.css) to drop its own padding
   and scrolling, the same arrangement lesson-mode uses. */
.chat-shell {
  height: 100%;
  display: flex;
  flex-direction: column;
}

.chat-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  padding: 18px 18px 12px;
  border-bottom: 1px solid var(--line);
  flex-shrink: 0;
}

.chat-thread {
  flex: 1;
  overflow-y: auto;
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.chat-row { display: flex; }
.chat-row-user { justify-content: flex-end; }

.chat-bubble {
  max-width: 82%;
  padding: 11px 13px;
  border-radius: 16px;
  font-size: 13px;
  line-height: 1.45;
}

.chat-bubble-buddy {
  background: var(--card);
  border: 1px solid var(--line);
  border-bottom-left-radius: 5px;
}

.chat-bubble-user {
  background: var(--accent-soft);
  border: 1px solid var(--accent-soft);
  color: var(--text);
  border-bottom-right-radius: 5px;
}

/* Link out of a reply — the user always taps it; chat never auto-navigates. */
.chat-link {
  display: block;
  width: 100%;
  margin-top: 10px;
  padding: 9px 11px;
  border: 1px solid var(--accent);
  border-radius: 11px;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 12px;
  font-weight: 850;
  text-align: left;
  cursor: pointer;
}

.chat-link:hover { filter: brightness(1.06); }

.chat-foot {
  flex-shrink: 0;
  border-top: 1px solid var(--line);
  background: var(--card);
  padding: 10px 18px 14px;
}

.chat-chips {
  display: flex;
  flex-wrap: wrap;
  align-content: flex-end;
  gap: 8px;
  padding-bottom: 12px;
}

.chat-chip {
  padding: 8px 14px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--soft);
  color: var(--text);
  font-size: 12px;
  font-weight: 700;
  white-space: nowrap; /* a chip never wraps its text — it moves to the next row whole */
  cursor: pointer;
}

.chat-chip:hover { background: var(--accent-soft); border-color: var(--accent); }

.chat-inputbar {
  display: flex;
  gap: 8px;
  align-items: center;
}

.chat-input {
  flex: 1;
  min-width: 0;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--screen);
  color: var(--text);
  font-size: 13px;
  padding: 11px 14px;
  font-family: Arial, Helvetica, sans-serif;
  outline: none;
}

.chat-input:focus { border-color: var(--accent); }

/* ─── Lifestyle Survey (builder flow) ────────────────────────────────────────
   Screens: screens/lifestyle-survey.js. Full-screen flow (nav hidden). The
   4-notch slider is a native range input with decorative dots under the track;
   commit on release (onchange) — never oninput, a mid-drag re-render would
   destroy the thumb. Theme tokens only. */
.ls-wrap { padding-top: 6px; }

.ls-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 14px;
}
.ls-header .button { font-size: 12px; padding: 8px 14px; }

/* Question phase: full-height column so the stage grows and the selector pins
   to the bottom. (Basics/Review still use .ls-wrap normal flow.) */
.ls-q-layout {
  min-height: 100%;
  display: flex;
  flex-direction: column;
  padding-top: 6px;
}

/* Large stage — the star of the screen; describes the current selection (or the
   typical reference before a pick) in real-world terms. Grows to fill space. */
.ls-q-stage {
  flex: 1 1 auto;
  overflow-y: auto;
  background: var(--accent-soft);
  border: 1px solid var(--accent-border);
  border-radius: 20px;
  padding: 22px 20px;
  margin-bottom: 16px;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.ls-stage-eyebrow {
  font-size: 21px;
  font-weight: 850;
  letter-spacing: -.02em;
  line-height: 1.2;
  margin-bottom: 14px;
}
.ls-stage-ref,
.ls-stage-figure { font-size: 15px; line-height: 1.5; margin: 0 0 12px; }
.ls-stage-ref strong,
.ls-stage-figure strong { font-weight: 850; }
.ls-stage-lead {
  font-size: 24px;
  font-weight: 850;
  letter-spacing: -.02em;
  line-height: 1.15;
  margin-bottom: 10px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.ls-stage-pct { font-size: 12px; font-weight: 850; }
.ls-stage-desc { font-size: 14px; line-height: 1.6; color: var(--text); margin: 0 0 12px; }
.ls-stage-desc:last-child { margin-bottom: 0; }
.ls-stage-prompt { font-size: 15px; font-weight: 800; color: var(--muted); margin: 0; }

/* Bottom selector: tappable 4-notch scale (all labels shown, hollow until
   tapped — no misleading fill) + Next. */
.ls-q-selector { flex: 0 0 auto; padding-bottom: 4px; }
.ls-notches {
  position: relative;
  display: flex;
  gap: 6px;
  margin-bottom: 14px;
}
.ls-notches::before {
  content: "";
  position: absolute;
  top: 9px;
  left: 12.5%;
  right: 12.5%;
  height: 2px;
  background: var(--line);
}
.ls-notch {
  flex: 1;
  background: transparent;
  border: none;
  padding: 0;
  font: inherit;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}
.ls-notch-dot {
  position: relative;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--card);
  border: 2px solid var(--line);
  transition: background .12s, border-color .12s, transform .12s;
}
.ls-notch.active .ls-notch-dot { background: var(--accent); border-color: var(--accent); transform: scale(1.12); }
.ls-notch-label { font-size: 10px; font-weight: 700; color: var(--muted); text-align: center; line-height: 1.2; }
.ls-notch.active .ls-notch-label { color: var(--accent); }

/* Slider used by the Review step (kept from the original ls- section). */
.ls-slider {
  width: 100%;
  position: relative;
  z-index: 1;
  padding: 0;
  accent-color: var(--accent);
  background: transparent;
}

/* Income mode toggle (twin of the wizard's seg-toggle) */
.ls-seg {
  display: grid;
  grid-template-columns: 1fr 1fr;
  border: 1px solid var(--line);
  border-radius: 13px;
  overflow: hidden;
  margin-bottom: 10px;
}
.ls-seg-btn {
  padding: 9px;
  font-size: 13px;
  font-weight: 850;
  background: var(--card);
  color: var(--muted);
  border: none;
  border-radius: 0;
  cursor: pointer;
}
.ls-seg-btn.active { background: var(--accent); color: var(--on-accent); }

/* Review step: compact slider rows + pinned-feel totals */
.ls-review-totals {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-bottom: 12px;
  position: sticky;
  top: 0;
  z-index: 5;
  background: var(--screen);
  padding: 4px 0 8px;
}
.ls-total-box {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 14px;
  padding: 10px 12px;
}
.ls-review-row { padding: 10px 12px; margin-bottom: 8px; }

/* Modal overlay (skip confirm / save prompt) */
.ls-modal-bg {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, .5);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  z-index: 100;
}


/* ─── Disabled nav tab (D33) ──────────────────────────────────────────────────
   Marketplace is visible in the nav but does nothing in the prototype. Greyed
   and non-interactive rather than hidden — the tab is meant to be SEEN. */
.tab-disabled {
  opacity: .38;
  cursor: not-allowed;
  pointer-events: none;
}

/* ─── Money Journal ───────────────────────────────────────────────────────────
   Head / body / foot so the prompt stays put and only the options scroll. */
.journal-shell {
  display: flex;
  flex-direction: column;
  min-height: 100%;
  padding: 4px 18px 18px;
}
.journal-head { flex-shrink: 0; padding-bottom: 14px; }
.journal-body { flex: 1; min-height: 0; }
.journal-foot {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding-top: 14px;
}
.journal-foot-total { border-top: 1px solid var(--line); }

.journal-progress { display: flex; gap: 5px; }
.journal-pip {
  height: 3px; flex: 1; border-radius: 99px;
  background: var(--progress-bg);
}
.journal-pip.on { background: var(--accent); }

.journal-options { display: flex; flex-direction: column; gap: 8px; }
.journal-opt {
  width: 100%;
  min-height: 44px;                 /* tap-target floor */
  text-align: left;
  padding: 13px 14px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--card);
  color: var(--text);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.journal-opt:hover { border-color: var(--accent-border); }
.journal-opt.picked {
  border-color: var(--accent);
  background: var(--accent-soft);
}
.journal-opt-label { font-size: 14px; font-weight: 700; }
.journal-opt-hint  { font-size: 11px; color: var(--muted); white-space: nowrap; }

.journal-freetext {
  width: 100%;
  padding: 13px 14px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--card);
  color: var(--text);
  font-family: inherit;
  font-size: 14px;
  resize: vertical;
}
.journal-attach { display: flex; gap: 8px; margin-top: 10px; }
.journal-attach-btn {
  flex: 1;
  min-height: 44px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  border: 1px dashed var(--line);
  border-radius: 12px;
  background: transparent;
  color: var(--muted);
  font-size: 10px;
  cursor: pointer;
}
.journal-attach-btn:hover { border-color: var(--accent-border); color: var(--text); }

.journal-entry-card { margin-bottom: 10px; }
.journal-amount { margin: 0; font-size: 16px; font-weight: 850; }
.journal-total  { margin: 0; font-size: 20px; font-weight: 500; }
.journal-slider { width: 100%; accent-color: var(--accent); }

/* ─── Budget (v3) ─────────────────────────────────────────────────────────────
   One row per category — twelve flat rows, no nesting (A2). */
/* My Progress — the one place on that screen where something goes IN rather
   than out. Tinted so it reads as an action among a column of read-outs. */
.mp-update { background: var(--accent-soft); border-color: var(--accent-border); }

/* Post-save beat. Text only — the point is that the tester registers the save
   happened before the Budget tab replaces it, not that anything is decorated. */
.budget-done { justify-content: center; text-align: center; }
.budget-done-body {
  display: flex; flex-direction: column; justify-content: center; align-items: center;
  gap: 8px;
}
.budget-done-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 30px;
  font-weight: 850;
  letter-spacing: -0.02em;
  color: var(--accent);
}
.budget-done-sub { margin: 0; font-size: 13px; color: var(--muted); }
.budget-done-anim      { animation: budgetDoneIn .42s cubic-bezier(.2,.7,.3,1) both; }
.budget-done-anim-late { animation: budgetDoneIn .42s cubic-bezier(.2,.7,.3,1) .18s both; }
@keyframes budgetDoneIn {
  from { opacity: 0; transform: translateY(10px) scale(.94); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .budget-done-anim, .budget-done-anim-late { animation: none; }
}

/* Worth a look — the same three bars as the list below, tinted so the section
   still reads as a callout rather than a duplicate of the twelve. */
.cmp-flag { background: var(--accent-soft); border-color: var(--accent-border); }

/* Lifestyle wizard — what the chosen option means, above the sliders it moves.
   Tinted so it reads as a response to the pick rather than another question. */
.lw-meaning {
  margin-top: 16px;
  margin-bottom: 10px;
  background: var(--accent-soft);
  border-color: var(--accent-border);
}

.budget-row { margin-bottom: 8px; padding: 12px 14px; }
/* Unbolded on purpose. These are the label and the readout on every slider row
   -- onboarding's income and commute sliders, and the wizard's twelve category
   rows. At 850 the readout competed with the screen's own heading and made a
   column of sliders read as a column of headlines. */
.budget-row-name { font-size: 14px; font-weight: 500; }
.budget-row-amt  { font-size: 15px; font-weight: 500; }

/* A slider that needs no label -- just its figure, above the track. Used by
   onboarding's income step, where "Your figure" said nothing the number did not
   already say. */
.slider-readout {
  margin: 0 0 6px;
  font-size: 15px;
  font-weight: 500;
  color: var(--text);
}
/* Tappable category header → detail. The name button carries the label; the
   chevron is a second, obvious tap target. Neither overlaps the slider below. */
.budget-row-head { gap: 8px; }
.budget-row-open {
  flex: 1; min-width: 0; text-align: left;
  background: none; border: none; padding: 0; margin: 0;
  color: inherit; cursor: pointer;
}
.budget-row-chev {
  background: none; border: none; cursor: pointer;
  color: var(--muted); font-size: 20px; line-height: 1; padding: 0 2px;
}
.budget-row-chev:hover { color: var(--accent); }

/* Plan-vs-take-home fill. Uses --warn when the plan exceeds income. */
.budget-bar {
  margin-top: 10px;
  height: 6px;
  border-radius: 99px;
  background: var(--progress-bg);
  overflow: hidden;
}
.budget-bar > span {
  display: block;
  height: 100%;
  background: var(--accent);
  border-radius: 99px;
}

/* ─── Observation card ────────────────────────────────────────────────────────
   Surfaced by the registry, never hardcoded by a screen (architecture §6).
   Attention states use --warn, never --danger — a flagged figure is not an
   emergency, and D36's palette has no alarm red at all. */
.obs-card {
  border-color: var(--warn-border);
  background: var(--warn-bg);
}
.obs-figure {
  font-size: 13px;
  font-weight: 850;
  color: var(--warn);
  white-space: nowrap;
}

.pill-warn { background: var(--warn-pill-bg); color: var(--warn-pill-text); border-color: var(--warn-border); }
.pill-good { background: var(--good-pill-bg); color: var(--good-pill-text); border-color: var(--good-border); }

/* ─── Three-layer comparison ─────────────────────────────────────────────────
   Plan / what you told me / peers. Three distinct hues so the layers are never
   read as one series — "never blur these" is the whole point of the screen. */
.cmp-legend { display: flex; flex-wrap: wrap; gap: 12px; font-size: 11px; color: var(--muted); }
.cmp-legend > div { display: flex; align-items: center; gap: 5px; }
.cmp-key { width: 10px; height: 10px; border-radius: 3px; display: inline-block; }
.cmp-key-plan { background: var(--bar); }
.cmp-key-user { background: var(--accent); }
.cmp-key-peer { background: var(--good); }

.cmp-row { margin-bottom: 8px; padding: 12px 14px; }
.cmp-bars {
  display: grid;
  grid-template-columns: 38px 1fr 54px;
  align-items: center;
  gap: 4px 8px;
}
.cmp-label { font-size: 10px; color: var(--muted); }
.cmp-val   { font-size: 11px; font-weight: 500; text-align: right; }
.cmp-bar   { height: 7px; background: var(--progress-bg); border-radius: 99px; overflow: hidden; }
.cmp-bar > span { display: block; height: 100%; border-radius: 99px; }

.cmp-gaps { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }

.cmp-compact { padding: 7px 0; border-bottom: 1px solid var(--line); gap: 8px; }
.cmp-compact:last-of-type { border-bottom: 0; }
.cmp-compact-name { font-size: 12px; font-weight: 700; flex: 1; }

/* ─── Phase 2.5 repaint (D36 / L19) ───────────────────────────────────────────
   Shape, depth and motion from 09-design-system, applied as overrides so the
   v2 rules above stay readable rather than being rewritten line by line.

   Radius 16 on cards, 24 on primary buttons, full round on pills and the buddy
   stage. Shadows wide and soft. Nothing square, no hard edges, no heavy
   elevation. Ease-out 240ms everywhere.

   Scoped to .screen — the admin panel and phone bezel are chrome and keep
   their own look (see the two scopes in variables.css). */

.screen .card,
.screen .item-card,
.screen .task-card,
.screen .journal-opt,
.screen .journal-freetext,
.screen .budget-row,
.screen .cmp-row {
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-soft);
}

/* The base .item-card paints with the chrome (frame) palette, which is fixed
   white in every theme — a bright block on a dark screen. Inside the screen it
   must follow the active theme, so it blends in all four. */
.screen .item-card { background: var(--soft); border-color: var(--line); }
.screen .item-card:hover { background: var(--accent-soft); }

.screen .button {
  border-radius: var(--radius-button);
  font-family: var(--font-display);
  transition: background var(--dur) var(--ease),
              border-color var(--dur) var(--ease),
              color var(--dur) var(--ease);
}

/* Primary is sage FILL with ink text — the raw pastel is too light to carry
   white type, and the design system has no darker primary to fall back on. */
.screen .button:not(.secondary) {
  background: var(--accent-fill);
  border-color: var(--accent-fill);
  color: var(--accent-fill-text);
}
.screen .button:not(.secondary):hover { filter: brightness(.96); }

.screen .button.secondary {
  background: transparent;
  border: 1px solid var(--accent-border);
  color: var(--accent);
}
.screen .button.secondary:hover { background: var(--accent-soft); }

.screen .pill { border-radius: var(--radius-pill); }

/* Inputs and sliders pick up the warm palette rather than the browser default. */
.screen input, .screen select, .screen textarea {
  border-radius: var(--radius-card);
  font-family: var(--font-body);
  background: var(--card);
  color: var(--text);
  border-color: var(--line);
}
.screen input[type="range"] { accent-color: var(--accent-fill); }

/* Focus must stay visible — 09-design-system's floor. */
.screen :focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Numbers are display face, one weight heavier than their label and always
   larger. Financial figures are the thing people look for. */
.screen .journal-total,
.screen .budget-row-amt,
.screen .journal-amount { font-family: var(--font-display); font-weight: 850; }

/* The buddy stage is fully round (09-design-system). */
.screen .stage-card { border-radius: var(--radius-pill); }

/* ─── Reduced motion ──────────────────────────────────────────────────────────
   Idle animation stops, transitions become instant. Respected globally, not
   per-component, so nothing added later can forget. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
    /* Delay must be reset too, not just duration. .du-stack-card staggers with
       `animation-delay: calc(var(--i) * 90ms)` and `backwards` fill, which
       holds each card at the keyframe's opacity:0 for the length of its delay.
       Zeroing only the duration left the cards invisible and then popping in
       one by one — the exact motion the setting asks us to remove. */
    animation-delay: -1ms !important;
    transition-delay: 0s !important;
  }
}


/* ─── Admin buttons are chrome ───────────────────────────────────────────────
   The base .button now resolves --accent to sage, which cannot carry white
   text. Admin lives outside .screen so the app override never reaches it —
   point it at the chrome accent instead. */
.admin .button {
  background: var(--chrome-accent);
  border-color: var(--chrome-accent);
  color: #fff;
  border-radius: 12px;
}
.admin .button.secondary {
  background: transparent;
  color: var(--chrome-accent);
  border: 1px solid var(--chrome-line);
}
.admin .button.secondary:hover { background: rgba(49, 94, 251, .06); }

/* ─── Buddy stage (L15) ───────────────────────────────────────────────────────
   A described placeholder, not art. Fully round per 09-design-system, sage
   ground because the buddy stage is a positive surface. */
.buddy-stage {
  background: var(--sage);
  border-radius: var(--radius-pill);
  padding: 22px 18px;
  margin-bottom: 14px;
  min-height: 190px;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  box-shadow: var(--shadow-soft);
  /* Both added for the illustration (L22), both harmless for the text frame:
     `relative` gives the absolutely-positioned image something to fill, and
     `hidden` is what clips it to the card's own corner radius. The description
     never overflows, so neither changes anything when no art is showing. */
  position: relative;
  overflow: hidden;
}

/* --- The buddy illustration (L22) ---
   Absolute + inset:0 so it fills the PADDING BOX -- edge to edge, ignoring the
   stage's 22px padding, which exists to inset text and would frame the picture
   in sage otherwise. It also escapes .buddy-inner's 240px max-width without
   that rule needing to know art exists.

   `cover`, because the background is painted into the image and is meant to
   fill the card; letterboxing it against the sage would read as a mistake
   rather than as a frame. The stage is square on Home and in the creator while
   the image is 1.4:1, so cover crops the sides -- the dog is centred, so it
   survives. On the compact stages (login, daily update) the box is wide and
   short, so it crops top and bottom instead. */
.buddy-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  display: block;
}

/* The name, over the picture. The creator's last step is naming, and a bare
   illustration would mean typing a name and seeing nothing change. Scrim rather
   than a solid bar so it reads as part of the scene. */
.buddy-img-name {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  margin: 0;
  padding: 22px 14px 10px;
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 850;
  color: #FFFFFF;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .55);
  background: linear-gradient(to top, rgba(0, 0, 0, .38), transparent);
  pointer-events: none;
}
.buddy-stage-compact { min-height: 130px; padding: 16px; }
.buddy-inner { max-width: 240px; }
.buddy-name { margin: 0 0 4px; font-family: var(--font-display); font-size: 17px; font-weight: 850; color: var(--ink); }
.buddy-desc { margin: 0; font-size: 11px; color: var(--ink); opacity: .78; line-height: 1.5; }
.buddy-pose {
  margin: 10px 0 0; font-size: 11px; font-style: italic;
  color: var(--ink); opacity: .62;
  transition: opacity var(--dur) var(--ease);
}
.buddy-note { margin: 6px 0 0; font-size: 9px; letter-spacing: .04em; text-transform: uppercase; color: var(--ink); opacity: .42; }

/* ─── Home ────────────────────────────────────────────────────────────────── */
/* Square dog stage with the chat button overhanging its bottom edge (v2 look).
   The wrapper anchors the absolute button; the bottom margin is the overhang's
   room. The stage's own margin is zeroed here so the overhang measures cleanly. */
.home-stage-wrap { position: relative; margin-bottom: 40px; }
.home-stage-wrap .buddy-stage { margin-bottom: 0; }
.home-chat-btn {
  position: absolute;
  bottom: -20px; left: 50%; transform: translateX(-50%);
  background: var(--card); color: var(--text);
  border: 1px solid var(--line); border-radius: var(--radius-pill);
  padding: 12px 26px; font-size: 15px; font-weight: 850;
  white-space: nowrap; cursor: pointer; box-shadow: var(--shadow-soft);
}
.home-chat-btn:hover  { background: var(--soft); }
.home-chat-btn:active { opacity: .86; }

.home-tip { display: flex; align-items: flex-start; gap: 10px; padding: 12px 14px; }
.home-tip-icon { font-size: 16px; line-height: 1.3; flex-shrink: 0; }
.home-tip-text { margin: 0; font-size: 13px; line-height: 1.45; }

.home-task { padding: 14px; margin-bottom: 8px; }
.home-task-done { opacity: .62; }
.home-task-kibble { margin: 8px 0 0; font-size: 11px; color: var(--muted); }

/* ─── Login scene ─────────────────────────────────────────────────────────── */
.login-scene {
  min-height: 100%;
  padding: calc(var(--topbar-h) + 12px) 18px 22px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 14px;
}
.login-day   { background: linear-gradient(180deg, var(--sky) 0%, var(--cream) 62%); }
.login-night { background: linear-gradient(180deg, #3B4A63 0%, #6B6480 100%); }
.login-night .login-hello, .login-night .helper, .login-night .login-bg-note { color: var(--on-dark); }

.login-bg-note {
  font-size: 10px;
  text-align: center;
  color: var(--ink);
  opacity: .55;
  line-height: 1.5;
}
.login-greeting { text-align: center; }
.login-hello { margin: 0; font-family: var(--font-display); font-size: 26px; font-weight: 850; }
.login-prompt { margin: 0; }
.login-remember {
  display: flex; align-items: center; gap: 7px;
  margin-top: 12px; font-size: 11px; color: var(--muted); cursor: pointer;
}
.login-notice { text-align: center; margin: 0; }

/* ─── Onboarding ──────────────────────────────────────────────────────────── */
/* Onboarding head: step counter + pips on the left, text-only Skip on the right. */
.onb-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}
.onb-head-progress { flex: 1; }
.onb-skip {
  flex-shrink: 0;
  min-height: 44px;
  padding: 0 4px;
  border: none;
  background: none;
  color: var(--muted);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.onb-skip:hover { color: var(--accent); }
.onb-skip.full { width: 100%; min-height: 40px; }

/* Onboarding ZIP cost-of-living comparison: centered chart, left-aligned text.
   Bars reuse the global .cmp-bar track+fill. */
.onb-col-chart {
  position: relative;
  max-width: 264px;
  margin: 16px auto 10px;
  padding-top: 14px;              /* room for the baseline label */
}
.onb-col-row { margin-bottom: 8px; }
.onb-col-head {
  display: flex;
  justify-content: space-between;
  font-size: 10px;
  font-weight: 700;
  color: var(--muted);
  margin-bottom: 3px;
}
.onb-col-baseline {
  position: absolute;
  top: 4px;
  bottom: 22px;
  width: 0;
  border-left: 1px dashed var(--muted);
  opacity: .55;
  transform: translateX(-50%);
}
.onb-col-teaser { text-align: left; margin: 14px 0 0; }
.onb-col-teaser-card { margin-top: 16px; }

.onb-col-axis {
  text-align: center;
  font-size: 9px;
  color: var(--muted);
  margin: 4px 0 0;
}
.onb-col-text { text-align: left; margin: 0; }

/* Multi-select checkbox — the shared affordance for "pick as many as apply".
   A .journal-opt on its own looks identical whether one or many are allowed,
   so every multi-select in the app carries this box: the onboarding goal step
   and the Money Journal both use it. `-dim` greys unselected options once a
   cap is reached (onboarding's max-3). */
.opt-check-box {
  width: 22px; height: 22px; flex-shrink: 0;
  border: 2px solid var(--line); border-radius: 7px;
  display: flex; align-items: center; justify-content: center;
  font-size: 13px; font-weight: 800; color: var(--on-accent);
}
.opt-check.picked .opt-check-box { background: var(--accent); border-color: var(--accent); }
.opt-check-dim { opacity: .5; }

/* Character creator (Mii-style) — square live stage + per-element controls. */
.buddy-stage-square { aspect-ratio: 1 / 1; min-height: 0; border-radius: var(--radius-card); }

/* Scroll list — breed, fur pattern. */
.buddy-scroll-list {
  /* A ceiling for any caller that is not in a flex column. Inside the creator
     the rule below overrides it and the list FILLS the space instead — a fixed
     height there showed two and a half breeds with a dead gap underneath. */
  max-height: 208px; overflow-y: auto;
  display: flex; flex-direction: column; gap: 8px;
  padding: 2px; -webkit-overflow-scrolling: touch;
}
.buddy-scroll-opt {
  display: flex; align-items: center; justify-content: space-between;
  width: 100%; min-height: 44px; padding: 12px 14px;
  border: 1px solid var(--line); border-radius: 14px;
  background: var(--card); color: var(--text);
  font-size: 14px; font-weight: 700; text-transform: capitalize; cursor: pointer;
}
.buddy-scroll-opt.picked { border-color: var(--accent); background: var(--accent-soft); }
.buddy-scroll-opt > span:last-child { color: var(--accent); font-weight: 800; }

/* ─── Body type: the 3x3 Mii-style grid ──────────────────────────────────────
   Label OUTSIDE the rounded square, placeholder circle inside it. Only the
   prototype has art; the eight others are told apart by circle size alone, so
   the tile has to give that circle room to read. */
.buddy-body-grid {
  display: grid;
  gap: 10px;
  /* Two rows and half of the third, so the grid LOOKS scrollable without a
     scrollbar having to say so. flex:1 lets it shrink on a short frame; the
     max-height stops it growing past the intended half-row on a tall one.

     A ROW IS NOT A SQUARE. The first version measured 2.5 rows off the tile's
     width alone and forgot the label sitting under it, so "two and a half rows"
     was really 2.3 and the third row's labels were sliced mid-word. Build the
     row out of its parts and it stays honest if any part changes. */
  --tile-w: 78px;
  --tile-gap: 10px;
  --tile-label: 16px;
  --tile-row: calc(var(--tile-w) + 5px + var(--tile-label));
  grid-template-columns: repeat(3, var(--tile-w));
  justify-content: center;
  flex: 1;
  min-height: 0;
  max-height: calc(var(--tile-row) * 2.5 + var(--tile-gap) * 2);
  overflow-y: auto;
  padding: 2px;
  -webkit-overflow-scrolling: touch;
}
.body-tile {
  display: flex; flex-direction: column; align-items: center; gap: 5px;
  padding: 0; border: none; background: none; cursor: pointer;
  color: var(--text);
}
.body-tile-square {
  display: flex; align-items: center; justify-content: center;
  width: 100%; aspect-ratio: 1 / 1;
  border: 2px solid var(--line); border-radius: 16px;
  background: var(--soft);
  overflow: hidden;
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.body-tile.picked .body-tile-square { border-color: var(--accent); background: var(--accent-soft); }
.body-tile-dot {
  display: block; border-radius: 50%;
  background: var(--progress-bg);
  border: 1px solid var(--rail);
}
.body-tile.picked .body-tile-dot { background: var(--accent-soft); border-color: var(--accent); }
.body-tile-img { width: 100%; height: 100%; object-fit: cover; display: block; }
.body-tile-label {
  font-size: 11px; font-weight: 700; line-height: 1.2; text-align: center;
  color: var(--muted);
}
.body-tile.picked .body-tile-label { color: var(--accent); }

/* ─── Breed search ───────────────────────────────────────────────────────────
   The clear button hides itself with no JS: :placeholder-shown is true exactly
   when the field is empty, and the button is its sibling. */
.body-search { position: relative; margin-bottom: 8px; }
.screen .body-search-input {
  width: 100%; min-height: 44px;
  padding: 10px 40px 10px 14px;
  border: 1px solid var(--line); border-radius: 14px;
  background: var(--card); color: var(--text);
  font-size: 14px;
}
.body-search-clear {
  position: absolute; top: 50%; right: 4px; transform: translateY(-50%);
  width: 36px; height: 36px;
  border: none; border-radius: 50%; background: none;
  color: var(--muted); font-size: 20px; line-height: 1; cursor: pointer;
}
.body-search-input:placeholder-shown ~ .body-search-clear { display: none; }
.body-search-match {
  margin: 0 0 8px; font-size: 11px; min-height: 15px;
}

/* Circular swatches — fur colour, eye colour. */
.buddy-swatch-row {
  display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; margin-top: 4px;
}
.buddy-swatch-circle {
  width: 48px; height: 48px; border-radius: 50%; padding: 3px;
  border: 2px solid transparent; background: none; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.buddy-swatch-circle.picked { border-color: var(--accent); }
.buddy-swatch-fill {
  width: 100%; height: 100%; border-radius: 50%;
  border: 1px solid var(--line); display: block;
}
.buddy-swatch-label {
  text-align: center; text-transform: capitalize;
  margin: 10px 0 0; min-height: 16px; font-weight: 700;
}

/* --- Back and Continue stay put from step 6 ---
   .journal-shell is min-height:100% by default, so tall content grows the shell
   and .screen-scroll scrolls the whole thing — taking the footer out of view
   exactly when there are enough options to need it. A shell that is EXACTLY the
   scroller's height cannot grow, so the overflow has to go somewhere: the body
   takes it and scrolls between a fixed header and a fixed footer.

   Scoped to a class rather than applied to .journal-shell, because the journal,
   the wizard and the lesson tail all use that shell and none of them asked. */
.journal-shell.onb-pinned {
  height: 100%; min-height: 0;
  /* The shared shell's 4px top assumes a 52px top bar above it. Onboarding no
     longer has one (components/topbar.js), and at 4px the step counter sat
     hard against the bezel. This is the breathing room the bar used to supply,
     without the 34px of dead strip that came with it.
     Scoped to onb-pinned, which only onboarding carries -- every other
     journal-shell screen still has its bar and still wants the 4px. */
  padding-top: 18px;
}

/* `overflow-y: auto` is not a one-axis declaration. When one axis is not
   `visible` the other's `visible` COMPUTES to `auto`, so this body is a scroll
   container horizontally too — and it clipped the focus ring, which reaches 4px
   outside an input's border box (.screen :focus-visible, 2px ring + 2px offset)
   on a field that is width:100% of the content box. The green ends of the ZIP
   field were being sliced off. An outline is ink overflow rather than scrollable
   overflow, which is why it clipped without ever showing a horizontal scrollbar.

   So: borrow the shell's side padding back and re-spend it inside the scroll
   container. The content box lands in the same place — still aligned with the
   head and the foot — the ring gets 18px of room, and a scrollbar that is
   genuinely needed sits at the screen edge instead of narrowing the content. */
.journal-shell.onb-pinned .journal-body {
  overflow-y: auto;
  margin: 0 -18px;
  padding: 0 18px;
}

/* The film step has nothing to scroll to, and its body differed from its content
   by a sub-pixel amount — enough for `auto` to show a scrollbar, which then took
   5px off the right of the content and left the stage no longer lining up with
   Continue below it. BOTH axes, or the computed-value rule above reappears. */
.journal-shell.onb-pinned .journal-body.onb-body-video { overflow: hidden; }

/* --- Onboarding headers ---
   ONE size for all ten, so "consistent throughout" is enforced by the
   stylesheet rather than by ten inline literals agreeing with each other.
   18px rather than 21px: at the old size the longest header wrapped to three
   rows in a 354px column. Size alone was not enough -- the worst offender was
   also shortened, because no size saves a 65-character question. */
.onb-title { font-size: 18px; line-height: 1.25; }

/* --- The character creator has to fit one screen ---
   Step 7 had TWO scrollbars: the breed list's own, and the page's. The list is
   meant to scroll; the page is not. The cause was height, not overflow -- a
   square stage is ~354px tall in a 390px frame, which pushed Continue below the
   fold on its own. Capping the stage here leaves room for the list and the
   footer without touching the shared .buddy-stage rules. */
/* --- The character creator fills its screen ---
   The step used to overflow, so the stage was shrunk to make room. That was the
   wrong lever: the stage is the thing the tester is here to look at, and the
   real cause was the LIST being a fixed height inside a body that stretches —
   so whatever the list did not use became dead space above the footer.
   The stage keeps its full square size and the list takes what is left. */
.onb-buddy-step {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;          /* or a nested scroller cannot shrink below content */
}
.onb-buddy-stage { margin-bottom: 18px; }
.onb-buddy-step .buddy-scroll-list {
  max-height: none;
  flex: 1;
  min-height: 96px;       /* two options, so it never collapses to nothing */
}
/* Breathing room under the stage for the controls that are NOT a list — the
   swatch rows and the name field sat hard against the illustration. */
.onb-buddy-step .buddy-swatch-row,
.onb-buddy-step .input-group { margin-top: 4px; }

/* ─── Body-type step: buy the grid its rows ──────────────────────────────────
   Three claims on one 390px screen -- portrait, search field, 2.5 rows of tiles
   -- and at the old sizes they need about 145px more than there is. Two levers,
   both scoped to this sub-step so the other four are untouched:

     1. the TITLE drops to one line. "Now the fun part -- let's give me a look."
        wrapped to two at 18px.
     2. the PORTRAIT drops from a full-width square (~330px) to 200px. The owner
        allowed resizing it; it is still the largest thing on the screen. */
.onb-buddy-step-body .onb-title { font-size: 15px; line-height: 1.3; }
.onb-buddy-step-body .onb-buddy-stage {
  width: 240px;
  margin: 0 auto 12px;
  align-self: center;
  flex-shrink: 0;
}
/* The counter sat 4px below a header that already has 14px of its own padding.
   Closing that gap is the cheapest 18px on the screen. `.onb-skip` KEEPS its
   44px min-height -- it is a tap target and the quality floor requires it, so
   that is the floor on how far this can tighten. */
.onb-buddy-count { margin: 0 0 2px; }
/* The header's own buffer, trimmed. `.onb-skip` KEEPS its 44px min-height --
   it is a tap target and the quality floor requires it, so that is the floor on
   how tight this gets.
   There used to be a `margin-top: -8px` here too. It was scraping back space
   that the top bar was sitting on (css/layout.css), and it did not even work:
   shifting a height:100% block upward does not shorten it, it just hangs 8px
   out of the bottom of its container. */
.journal-shell.onb-pinned .journal-head { padding-bottom: 8px; }

/* ─── Search mode ────────────────────────────────────────────────────────────
   While the search field has focus the step hands the screen over to it: the
   field rises to the top, the portrait goes away, and the GRID takes the space
   -- so what rises above the keyboard is the thing you are choosing from, not
   the dog you are not looking at.

   HIDING is what moves it -- there is no re-ordering. The search already sits
   directly after the portrait in the DOM, so `display: none` on the portrait,
   the title and the subtitle lifts it to the top on its own. `order` was the
   first idea and it was wrong: it would have lifted the field above the step
   header too.

   Nothing here moves a node, which is the point. The <input> keeps its focus and
   its caret, both of which it loses the instant anything re-renders around it.

   The shared `.screen-scroll.kbd-open { padding-bottom: 250px }`
   (css/keyboard.css) is what makes this work rather than something to fight:
   it shrinks the scroller's content box, the step compresses into what is left
   above the keyboard, and with the portrait hidden that compression lands on
   the grid. An earlier attempt here tried to SUPPRESS that rule -- one of the
   two attempts was inert anyway, setting padding on the step when the padding
   lives on the scroller. */
.onb-buddy-step-body.searching .onb-buddy-stage,
.onb-buddy-step-body.searching .onb-title,
.onb-buddy-step-body.searching .onb-buddy-sub { display: none; }
.onb-buddy-step-body.searching .buddy-body-grid {
  max-height: none;   /* the half-row cue is for the resting state; searching
                         wants every row it can show */
}

/* --- The intro film's controls sit at the foot of the screen ---
   Mirrors the lesson player, which is a full-height flex column with
   .lp-controls last. Without this the transport lands wherever the caption
   happens to end, which is the middle of the screen. */
/* THE PARENT HAS TO BE THE FLEX CONTAINER — that was the missing half both
   earlier attempts assumed was already there. `flex: 1` alone was inert (no
   flex parent); `min-height: 100%` then gave this element the body's FULL
   height while the eyebrow and the <h1> still sat above it inside that same
   body. Content came to header + 100%, so the body overflowed by exactly the
   header's height on every screen size, and what fell below the fold was the
   bottom of this box: the transport row and the scrubber.
   .onb-body-video (renderOnboarding, film step only) supplies the container. */
.journal-body.onb-body-video { display: flex; flex-direction: column; }

.onb-video { display: flex; flex-direction: column; gap: 12px; flex: 1; min-height: 0; }
/* Caption and transport keep their size; the stage is the one thing that gives
   way, so a short laptop viewport loses picture rather than losing controls. */
.onb-video > * { flex-shrink: 0; }
/* This used to be `margin-top: auto`, which pushed the transport onto the bottom
   edge and left a dead band under the caption. The spare height goes to the
   picture instead (see .onb-video-stage), so there is nothing left to push. */
.onb-video-controls-foot { flex-shrink: 0; }

/* --- v3.1: "which is closer" rows ---
   Three tap targets across a 390px frame, so each gets about a third minus the
   gaps. Ordered low-to-high by the renderer, which is why nothing here encodes
   which column is "theirs" — the selected one is the only distinguished state,
   because marking the model's column would be telling them which to pick. */
.compare-row { padding: 12px 12px 14px; margin-bottom: 10px; }
.compare-cat { margin: 0 0 8px; font-size: 13px; font-weight: 700; color: var(--text); }
.compare-opts { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
.compare-opt {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  min-height: 56px; padding: 8px 4px;
  border: 1px solid var(--line); border-radius: 12px;
  background: var(--card); color: var(--text);
  font: inherit; cursor: pointer; text-align: center;
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.compare-opt.picked { border-color: var(--accent); background: var(--accent-soft); }
.compare-opt:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.compare-opt-label {
  font-size: 9px; font-weight: 800; letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted); line-height: 1.2;
  min-height: 2.2em; display: flex; align-items: center;
}
.compare-opt-figure {
  font-family: var(--font-display); font-size: 15px; font-weight: 850;
  line-height: 1.1; color: var(--text);
}

/* Intro "video" — captioned narration synced to live Web Speech.
   The flex rule for .onb-video lives with the other onboarding rules above. */

/* The hyperframes stage. .lp-stage is written for the lesson player's
   full-height flex column, where it takes 42% of the screen; inside the
   onboarding card it has to state its own height instead, or it collapses to
   its content. Rounded to match the buddy card it replaced. */
.onb-video-stage {
  /* The one child that flexes: it takes whatever the caption and transport do
     not need. Capped because past ~255px the SVG stops growing — it is
     xMidYMid meet on a 100x72 viewBox — and the extra height would be accent
     colour rather than more picture. 340px also keeps it no taller than it is
     wide (354px inside a 390px frame). */
  flex: 1 1 210px;
  height: 210px;
  min-height: 110px;      /* the floor on a short viewport */
  max-height: 340px;
  border-radius: var(--radius-card);
  overflow: hidden;
}
/* The rendered film. `contain` reproduces the SVG engine's
   preserveAspectRatio="xMidYMid meet" exactly, so swapping tier 1 for tier 2
   does not move the picture. The stage already clips to its radius. */
.onb-film {
  display: block; width: 100%; height: 100%;
  object-fit: contain; background: transparent;
}

.onb-video-caption {
  font-size: 15px; line-height: 1.5; color: var(--text);
  min-height: 68px; margin: 0;
}
.onb-video-progress {
  height: 4px; background: var(--progress-bg); border-radius: 99px; overflow: hidden;
}
.onb-video-progress > span {
  display: block; height: 100%; background: var(--accent);
  transition: width var(--dur) var(--ease);
}
.onb-video-controls { display: flex; gap: 10px; }
.onb-video-controls .button { flex: 1; }

.onb-swatches { display: flex; flex-wrap: wrap; gap: 6px; }
.onb-swatch {
  min-height: 36px;
  padding: 7px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--card);
  color: var(--text);
  font-size: 12px;
  cursor: pointer;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.onb-swatch.picked { border-color: var(--accent); background: var(--accent-soft); font-weight: 700; }
.onb-trial-list { margin: 0 0 14px; padding-left: 18px; font-size: 13px; line-height: 1.7; color: var(--text); }

/* ─── Daily update ────────────────────────────────────────────────────────────
   09-design-system: "the one place motion is orchestrated rather than ambient.
   Let it be the memorable moment; keep everything else quiet." */
.du-shell {
  min-height: 100%;
  padding: calc(var(--topbar-h) + 8px) 18px 18px;
  display: flex; flex-direction: column; gap: 14px;
}
.du-stage {
  flex: 1; min-height: 250px;
  display: flex; align-items: center; justify-content: center;
}
.du-cue { width: 100%; text-align: center; }
.du-cue-label { margin: 8px 0 0; font-size: 12px; color: var(--muted); }
.du-figure {
  margin: 0; font-family: var(--font-display); font-size: 42px; font-weight: 850;
  color: var(--text); line-height: 1.1;
}
.du-figure-sm { font-size: 28px; }

.du-caption { min-height: 66px; }
.du-text {
  margin: 0; font-family: var(--font-display);
  font-size: 16px; line-height: 1.5; text-align: center; color: var(--text);
}

.du-controls { flex-shrink: 0; }
.du-progress { height: 4px; border-radius: 99px; background: var(--progress-bg); overflow: hidden; }
.du-progress > span { display: block; height: 100%; background: var(--accent); transition: width var(--dur) var(--ease); }
.du-segs { display: flex; gap: 5px; margin: 10px 0 12px; }
.du-seg { flex: 1; height: 22px; border: 0; background: transparent; cursor: pointer; padding: 0; }
.du-seg::after { content: ""; display: block; height: 3px; border-radius: 99px; background: var(--bar); opacity: .5; }
.du-seg.on::after { background: var(--accent); opacity: 1; }
.du-buttons { display: flex; gap: 8px; }
.du-buttons .button { flex: 1; }

.du-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
.du-tile {
  padding: 10px 6px; border-radius: var(--radius-card);
  background: var(--soft); font-size: 9px; color: var(--muted);
  display: flex; align-items: center; justify-content: center; text-align: center;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.du-tile.on { background: var(--warn-bg); color: var(--warn); font-weight: 850; }

.du-bill { max-width: 260px; margin: 0 auto; }
.du-stack { display: flex; flex-direction: column; gap: 8px; }
.du-stack-card { margin: 0; animation: duStackIn var(--dur) var(--ease) backwards; animation-delay: calc(var(--i) * 90ms); }

@keyframes duStackIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes duPulse   { 0%,100% { transform: scale(1); } 45% { transform: scale(1.07); } }
@keyframes duSlideIn { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
@keyframes duCountUp { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
.du-pulse    { animation: duPulse 900ms var(--ease); }
.du-slide-in { animation: duSlideIn var(--dur) var(--ease); }
.du-countup  { animation: duCountUp 420ms var(--ease); }
.du-compare { display: flex; gap: 14px; justify-content: center; margin-bottom: 4px; }
.du-compare-side { flex: 1; max-width: 130px; }
.du-figure-muted { color: var(--muted); }

/* ─── Share ───────────────────────────────────────────────────────────────── */
.share-toggle { display: flex; align-items: flex-start; gap: 10px; cursor: pointer; font-size: 13px; }
.share-toggle input { margin-top: 3px; flex-shrink: 0; }
.share-expand {
  width: 100%; min-height: 44px;
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  background: transparent; border: 0; padding: 0;
  font-size: 13px; font-weight: 700; color: var(--accent); cursor: pointer;
}
.share-preview {
  margin: 10px 0 0; padding: 12px;
  background: var(--soft); border-radius: var(--radius-card);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px; line-height: 1.6; color: var(--text);
  white-space: pre-wrap; word-break: break-word;
}
.share-platforms { display: flex; flex-wrap: wrap; gap: 6px; }
.share-platform {
  min-height: 44px; padding: 10px 14px;
  border: 1px solid var(--line); border-radius: var(--radius-pill);
  background: var(--card); color: var(--muted);
  font-size: 12px; opacity: .55; cursor: not-allowed;
}

/* ─── Goals ───────────────────────────────────────────────────────────────── */
.goal-strategic { background: var(--accent-soft); border-color: var(--accent-border); }
.goal-strategic-label { margin: 0; font-family: var(--font-display); font-size: 16px; font-weight: 850; }
.goal-card { position: relative; padding: 14px; margin-bottom: 8px; }
.goal-bar { height: 7px; border-radius: 99px; background: var(--progress-bg); overflow: hidden; }
.goal-bar > span { display: block; height: 100%; border-radius: 99px; background: var(--accent); }
.goal-bar-warn { background: var(--warn); }
.goal-pace { margin: 8px 0 0; font-size: 12px; font-weight: 700; color: var(--text); }
.goal-remove {
  position: absolute; top: 10px; right: 10px;
  background: none; border: 0; color: var(--muted);
  font-size: 10px; cursor: pointer; padding: 4px;
}
.goal-remove:hover { color: var(--warn); }

/* ─── My Progress ─────────────────────────────────────────────────────────── */
.mp-chart { display: block; margin-top: 4px; overflow: visible; }

.mp-bill { display: flex; align-items: center; gap: 12px; padding: 9px 0; border-bottom: 1px solid var(--line); }
.mp-bill:last-of-type { border-bottom: 0; }
.mp-bill-when {
  width: 44px; flex-shrink: 0; text-align: center;
  border-radius: var(--radius-card); padding: 6px 0; background: var(--soft);
}
.mp-bill-days { display: block; font-family: var(--font-display); font-size: 16px; font-weight: 850; line-height: 1; }
.mp-bill-unit { display: block; font-size: 9px; color: var(--muted); }
.mp-bill-amt  { font-size: 13px; font-weight: 850; }
/* A flagged bill uses clay, never alarm colouring — nothing here is an
   emergency (09-design-system). */
.mp-bill-flagged .mp-bill-when { background: var(--warn-bg); color: var(--warn); }
.mp-bill-flagged .mp-bill-amt  { color: var(--warn); }

.mp-sub { padding: 8px 0; border-bottom: 1px solid var(--line); }
.mp-sub:last-of-type { border-bottom: 0; }
.mp-sub-flag {
  margin-top: 10px; padding: 12px;
  background: var(--warn-bg); border: 1px solid var(--warn-border);
  border-radius: var(--radius-card);
}
.mp-badge { padding: 6px 0; }
.mp-kibble { display: flex; align-items: center; justify-content: space-between; gap: 12px; }

/* ─── Lesson quiz / simulation / reward ───────────────────────────────────── */
.quiz-right { border-color: var(--good); background: var(--good-bg); }
.quiz-wrong { border-color: var(--warn-border); background: var(--warn-bg); opacity: .7; }
/* Lesson calculator — the common-case shortcuts above the sliders. Chips, so a
   row of four fits a 390px frame without becoming four full-width buttons. */
.sim-presets {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 12px;
}
.sim-preset {
  flex: 0 1 auto;
  min-height: 34px;
  padding: 7px 12px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--card);
  color: var(--text);
  font-family: inherit;
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
}
.sim-preset:hover { border-color: var(--accent); }
.sim-preset.picked {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}
.sim-preset:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.sim-row { margin-bottom: 8px; padding: 12px 14px; }
.sim-out { text-align: center; padding: 18px 14px; background: var(--accent-soft); border-color: var(--accent-border); }

/* Three scenario boxes above the sliders. Same .sim-out card language, sized
   to sit three-across in a 390px frame — hence the tighter padding and the
   1fr grid rather than flex, so all three keep the same width whatever the
   figures inside them run to. */
.sim-scenarios {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin-bottom: 4px;
}
/* A button now, not a card: tapping one moves the sliders to that outcome.
   Buttons need the reset — .card gives it the surface, these give it back the
   block layout and the inherited type a <button> throws away. */
.sim-scenario {
  padding: 12px 6px; margin: 0;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  width: 100%; text-align: center; font: inherit; cursor: pointer;
}
.sim-scenario:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.sim-scenario-tone {
  font-size: 9px; font-weight: 800;
  letter-spacing: .06em; text-transform: uppercase; color: var(--muted);
  /* Two lines reserved whether the label needs them or not. "Healthy outcome"
     wraps in a third of a 390px frame and the others do not, so without this
     its figure sat a row lower than the two beside it. */
  min-height: 2.2em;
  display: flex; align-items: center; justify-content: center;
}
.sim-scenario-head {
  margin-top: 4px; font-family: var(--font-display); font-size: 19px;
  font-weight: 850; line-height: 1.1; color: var(--text);
}
/* Also two lines, so the short detail under a long interest figure does not
   pull the box's baseline out of line with its neighbours. */
.sim-scenario-sub {
  font-size: 10px; line-height: 1.35; color: var(--muted);
  min-height: 2.7em;
}
.sim-scenario-sub:last-child { min-height: 0; }
/* Tone is carried by the border only. A red/green fill would read as a verdict
   on which one to choose, and these are descriptions of arithmetic (D26). */
.sim-scenario-good    { border-color: var(--good-border); }
.sim-scenario-average { border-color: var(--accent-border); }
.sim-scenario-bad     { border-color: var(--warn-border); }
.sim-sub-row { padding: 8px 0; border-bottom: 1px solid var(--line); gap: 10px; }
.sim-sub-row:last-of-type { border-bottom: 0; }
.reward-course { margin-bottom: 8px; padding: 12px 14px; }

/* Spend estimator — the explicit confirm before the self-reported layer is
   written. A real control, not a sentence: nothing commits until it is ticked. */
/* Spend estimator — the "I already know this one" itemisation. */
.est-known-row {
  align-items: baseline;
  padding: 5px 0;
  border-top: 1px solid var(--line);
  font-size: 13px;
}
.est-known-row:first-of-type { border-top: none; }

.est-confirm {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  min-height: 44px;                 /* tap-target floor */
  margin-top: 12px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--card);
  cursor: pointer;
  font-size: 13px;
  line-height: 1.45;
  color: var(--text);
}
.est-confirm input[type="checkbox"] {
  width: 20px;
  height: 20px;
  margin: 0;
  flex: 0 0 auto;
  accent-color: var(--accent);
  cursor: pointer;
}
.est-confirm:has(input:checked) {
  border-color: var(--accent);
  background: var(--accent-soft);
}

/* ─── Budget band — the box-and-whiskers track (components/budget-band.js) ────
   Replaces the three stacked .cmp-bar rows. Built from absolutely-positioned
   spans on a percentage scale rather than SVG, so it is correct at any width
   and the dot stays a circle.

   COLOUR, and why it is not the legend's old three:
   `--accent` and `--good` are near-identical greens in the Natural themes
   (#557B58 vs #4B7650). Three separated bars carried that fine; three marks on
   ONE track would not. So peers become the pale --good-bg wash with a
   --good-border edge, the budget becomes a neutral --muted rule, and --accent
   is spent on the one mark that matters most — the tester's own dot. */

.band { position: relative; width: 100%; }

/* --progress-bg is a FILL, and against --card it measures 1.02-1.20:1 in every
   theme — no perceptible edge, which is why these read as a thumb floating in
   space with no rail under it. The hairline in --rail (~2.6:1) is what makes it
   a channel; the fill stays soft so the pale peer wash still reads on top of
   it. Swap the border for `background: var(--rail)` if it wants more weight. */
.band-track {
  position: relative;
  height: 10px;
  background: var(--progress-bg);
  border: 1px solid var(--rail);
  border-radius: 99px;
  box-sizing: border-box;
}

/* Where peers like you land. A wash, not a fill — it is context behind the
   figures, and a saturated band would out-shout the dot sitting on it. */
/* --good-bg is a CARD background — against the rail fill it is barely a tint
   (#EDF3EC on #EFE8DC in Natural Light) and the band read as a smudge.
   --good-border is the lightest token that is unambiguously green on the rail
   in all four themes, and still recessive enough that the dot sitting on it
   stays the loudest thing on the track. */
.band-peer {
  position: absolute;
  top: -1px;
  bottom: -1px;
  background: var(--good-border);
  border-radius: 99px;
  box-sizing: border-box;
}
.band-peer-mid {
  position: absolute;
  top: -1px;
  bottom: -1px;
  width: 2px;
  margin-left: -1px;
  background: var(--good);
  border-radius: 2px;
}

/* The budget: a reference line, taller than the track so it reads as an axis
   mark rather than as another value sitting in it. */
.band-budget {
  position: absolute;
  top: -5px;
  bottom: -5px;
  width: 2px;
  margin-left: -1px;
  background: var(--muted);
  border-radius: 2px;
}

/* What you actually spent. The ring is --card, not white: it keeps the dot
   legible when it lands on top of the peer wash, in every theme. */
.band-dot {
  position: absolute;
  top: 50%;
  width: 14px;
  height: 14px;
  margin: -7px 0 0 -7px;
  background: var(--accent);
  border: 2px solid var(--card);
  border-radius: 50%;
  box-shadow: 0 1px 3px rgba(0, 0, 0, .18);
}

/* Peers run past the right edge. Without this a clipped band is
   indistinguishable from one that merely reaches the end of the track. */
.band-clip {
  position: absolute;
  top: -5px;
  bottom: -5px;
  right: 0;
  width: 3px;
  background: var(--line);
  border-radius: 2px;
}
.band-clip::after {
  content: "›";
  position: absolute;
  left: 5px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 11px;
  font-weight: 850;
  color: var(--muted);
}

/* Sits ABOVE the rail: the budget figure, then where peers land, then the
   picture of both. Stacked rather than justified across a row, so "peers" reads
   as the line under the budget rather than as its opposite number. */
.band-caption {
  display: flex;
  flex-direction: column;
  gap: 1px;
  margin: 0 0 9px;
  font-size: 10px;
  line-height: 1.5;
  color: var(--muted);
}
.band-caption strong { color: var(--text); font-weight: 500; }

/* ── Build mode: the track IS the slider ─────────────────────────────────────
   A real <input type="range"> laid over the marks with a transparent track, so
   the browser keeps pointer capture, keyboard stepping and focus and we do not
   re-implement any of it.

   THE 9px INSET IS NOT COSMETIC. A native thumb's CENTRE travels from
   thumbWidth/2 to width - thumbWidth/2, while `left: %` marks travel the full
   0–100%. Left alone, the thumb and the peer band it is being compared against
   are measured on two different scales and disagree by ~3% at the ends. Inset
   the marks by half the thumb and both travel the same distance. */
/* The two numbers the thumb's vertical position is derived from. Declared
   here so the calc below states the relationship instead of hiding a 13. */
.band-build {
  --band-h: 44px;                      /* tap target, not decoration */
  --band-thumb: 18px;
  height: var(--band-h);
}
.band-build .band-track {
  position: absolute;
  top: 50%;
  left: 9px;
  right: 9px;
  margin-top: -5px;
}
/* ── .screen-SCOPED, AND THAT IS THE WHOLE POINT ─────────────────────────────
   `.screen input` (0,1,1) sets background: var(--card) and a card radius, and
   it BEATS a bare `.band-range` (0,1,0). This is the only slider in the app
   with -webkit-appearance: none — every other one keeps its native track, so
   the element's own background is never seen and the override is harmless.
   Strip the native track and that card background becomes visible: a 44px-tall,
   full-width, rounded box painted straight over the rail, with the thumb
   floating in the middle of it.
   Scoping to `.screen .band-range` (0,2,0) wins. Do not un-scope it. */
.screen .band-range {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  background: transparent;
  border: 0;
  border-radius: 0;
  -webkit-appearance: none;
  appearance: none;
}
.screen .band-range:focus { outline: none; }
.screen .band-range:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 6px; }
.screen .band-range::-webkit-slider-runnable-track { height: 100%; background: transparent; border: 0; }
.screen .band-range::-moz-range-track  { height: 100%; background: transparent; border: 0; }
/* ── THE THUMB HAS TO BE CENTRED BY HAND ────────────────────────────────────
   With -webkit-appearance: none, WebKit aligns the thumb to the TOP of the
   runnable track — it does not centre it. The track here is height: 100%, i.e.
   the whole 44px tap target, so an 18px thumb sat at 9px while the visual rail
   is centred at 22px: the dot floated above its own slider.
   margin-top closes exactly that gap, and box-sizing keeps the 2px ring inside
   the 18px rather than making the thumb 22px and the arithmetic wrong. */
.screen .band-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  box-sizing: border-box;
  width: var(--band-thumb, 18px);
  height: var(--band-thumb, 18px);
  margin-top: calc((var(--band-h, 44px) - var(--band-thumb, 18px)) / 2);
  border-radius: 50%;
  background: var(--accent);
  border: 2px solid var(--card);
  box-shadow: 0 1px 4px rgba(0, 0, 0, .22);
  cursor: grab;
}
/* Firefox centres its own thumb on the track, so it takes no margin here —
   adding one would shift it twice. */
.screen .band-range::-moz-range-thumb {
  box-sizing: border-box;
  width: var(--band-thumb, 18px);
  height: var(--band-thumb, 18px);
  border-radius: 50%;
  background: var(--accent);
  border: 2px solid var(--card);
  box-shadow: 0 1px 4px rgba(0, 0, 0, .22);
  cursor: grab;
}
.screen .band-range:disabled::-webkit-slider-thumb { background: var(--muted); cursor: not-allowed; }
.screen .band-range:disabled::-moz-range-thumb     { background: var(--muted); cursor: not-allowed; }
.band-build.is-disabled .band-track { opacity: .45; }

/* Legend keys — must match the marks above or the legend lies. */
.band-key { width: 12px; height: 10px; border-radius: 3px; display: inline-block; }
.band-key-budget { width: 3px; border-radius: 2px; background: var(--muted); }
.band-key-peer   { background: var(--good-bg); border: 1px solid var(--good-border); box-sizing: border-box; }
.band-key-you    { width: 10px; height: 10px; border-radius: 50%; background: var(--accent); }

/* Band rows inside the My Progress strip — separated by a rule rather than
   carded, because they already sit inside a card. */
.cmp-compact-band { padding: 12px 0; border-bottom: 1px solid var(--line); }
.cmp-compact-band:last-of-type { border-bottom: 0; }

/* Nothing logged yet. Replaces the chart rather than sitting under it — a
   track whose dot is pinned to zero reads as "you spent nothing" (D19). */
.cmp-nospend { }
.cmp-nospend-banner {
  background: var(--accent-soft);
  border-color: var(--accent-border);
  margin-bottom: 14px;
}

/* ─── Budget builder rows (screens/budget-build.js) ───────────────────────────
   The line's input and the toggle that hands the line over, side by side. */

/* Rows are a LIST, not a stack of cards: the toggle belongs on the title line,
   and twelve nested cards inside a card put three borders between the name and
   the slider it labels. */
.bb-row { padding: 15px 0; border-bottom: 1px solid var(--line); }
.bb-row:last-of-type { border-bottom: 0; }

.bb-row-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 3px;
}
.bb-row-name { font-size: 14px; font-weight: 700; }

/* The figure sits UNDER the name, so it reads as that line's caption rather
   than as a competing headline. Unbolded for the same reason. */
.bb-row-amt { margin: 0 0 10px; font-size: 15px; font-weight: 500; color: var(--text); }

.bb-row-input { min-width: 0; }
.bb-row-input.is-disabled { opacity: .4; pointer-events: none; }

.bb-exact {
  width: 100%;
  padding: 10px 12px;
  font-size: 15px;
  font-weight: 700;
  font-family: inherit;
  color: var(--text);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius-button);
}
.bb-exact:focus { outline: none; border-color: var(--accent); }
.bb-exact:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.bb-exact:disabled { color: var(--muted); background: var(--soft); }
.bb-exact::-webkit-outer-spin-button,
.bb-exact::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

.bb-help { display: flex; align-items: center; gap: 7px; flex-shrink: 0; }
/* Lights up when the line is handed over — with the figure gone, this label is
   the only thing left saying what is happening to the row. */
.bb-help-label {
  font-size: 10px;
  font-weight: 700;
  line-height: 1.2;
  white-space: nowrap;
  color: var(--muted);
}
.bb-row-helping .bb-help-label { color: var(--accent); font-weight: 850; }

/* The switch. 44px of tap target around a 40x22 pill — the button IS the
   target, and the pill is drawn inside it, so the visual size and the
   touchable size are not the same number. */
.bb-toggle {
  position: relative;
  width: 46px;
  height: 44px;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  flex-shrink: 0;
}
.bb-toggle::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 40px;
  height: 22px;
  margin: -11px 0 0 -20px;
  background: var(--bar);
  border-radius: 99px;
  transition: background var(--dur) var(--ease);
}
.bb-toggle.on::before { background: var(--accent); }
.bb-toggle-knob {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 18px;
  height: 18px;
  margin: -9px 0 0 -18px;
  background: var(--card);
  border-radius: 50%;
  box-shadow: 0 1px 3px rgba(0, 0, 0, .25);
  transition: transform var(--dur) var(--ease);
}
.bb-toggle.on .bb-toggle-knob { transform: translateX(16px); }
.bb-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: 8px; }

@media (prefers-reduced-motion: reduce) {
  .bb-toggle::before, .bb-toggle-knob { transition: none; }
}

/* ─── Budget total bar (renderBudgetTotalBar, components/budget-band.js) ──────
   The running budget against income, in the same grammar as the band below it:
   a rail, a fill, and a reference mark. Right edge is 1.1x the larger of the
   two, so the bar can genuinely overrun the income mark rather than pinning to
   the end and hiding by how much. */

.bb-total { margin-bottom: 20px; }
.bb-total-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 10px;
}
.bb-total-label { font-size: 11px; color: var(--muted); }
.bb-total-value { margin: 0; font-size: 20px; font-weight: 500; }

.bb-total-bar {
  position: relative;
  height: 12px;
  background: var(--progress-bg);
  border: 1px solid var(--rail);
  border-radius: 99px;
  box-sizing: border-box;
}
/* Green while it fits, warn once it does not. A green bar under the words
   "$1,330 more than you bring in" reads as approval of the opposite — one
   class to flip if the softer read is wanted. */
.bb-total-fill {
  position: absolute;
  top: -1px;
  bottom: -1px;
  left: -1px;
  min-width: 2px;
  background: var(--good);
  border-radius: 99px;
}
.bb-total-fill.over { background: var(--warn); }

.bb-total-mark {
  position: absolute;
  top: -5px;
  bottom: -5px;
  width: 2px;
  margin-left: -1px;
  background: var(--muted);
  border-radius: 2px;
}
.bb-total-foot {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  margin-top: 10px;
  font-size: 11px;
  color: var(--muted);
}
.bb-total-foot .over { color: var(--warn); }

/* ─── Help me out (screens/help-me-out.js) ────────────────────────────────────
   Every question for one category on a single screen, revealing as the
   previous one is answered. Answered questions stay visible and legible —
   greying them out would read as "you got these wrong". */

.hmo-running {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  padding: 12px 14px;
  margin-bottom: 16px;
  background: var(--accent-soft);
  border: 1px solid var(--accent-border);
  border-radius: var(--radius-card);
}
.hmo-running-figure { font-size: 20px; font-weight: 500; color: var(--text); }

.hmo-q { padding: 0 0 18px; }
.hmo-q + .hmo-q { border-top: 1px solid var(--line); padding-top: 18px; }
.hmo-q-prompt { margin: 0 0 10px; font-size: 14px; font-weight: 700; }
.hmo-slider-readout { font-size: 15px; font-weight: 500; }

/* The open question — the one thing left to do on the screen. */
.hmo-q.is-open .hmo-q-prompt { color: var(--accent); }

.hmo-figure { margin: 0; font-size: 34px; font-weight: 500; line-height: 1.05; }

.hmo-checklist { display: flex; flex-direction: column; gap: 6px; }
.hmo-check {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 44px;
  padding: 8px 12px;
  font: inherit;
  text-align: left;
  color: var(--text);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius-button);
  cursor: pointer;
}
.hmo-check.on { background: var(--accent-soft); border-color: var(--accent-border); }
.hmo-check:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.hmo-check-box {
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  font-size: 12px;
  font-weight: 850;
  color: var(--on-accent);
  background: var(--card);
  border: 1.5px solid var(--rail);
  border-radius: 5px;
}
.hmo-check.on .hmo-check-box { background: var(--accent); border-color: var(--accent); }
.hmo-check-label { flex: 1; font-size: 13px; }
.hmo-check-price { font-size: 12px; color: var(--muted); }

.hmo-done { color: var(--accent); }

/* The income figure beside a profile option — a fact about the choice, not the
   choice itself, so it sits under the label rather than competing with it. */
.pp-figure { display: block; margin-top: 2px; font-size: 11px; color: var(--muted); }
