/* lint-rules:ignore-font-size-file lint-rules:ignore-hex-file
 *
 * Grandfathered: this file pre-dates strict enforcement of the type-scale
 * (--fs-xs/sm/base/lg/xl) and hex-color rules. Existing declarations are
 * exempt; new declarations should use tokens. The grandfathering is a
 * known debt; see scripts/lint-rules.py for context.
 */
/* ── STAT CARDS ── */
.stats-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px;margin-bottom:24px;}
.stat-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);padding:16px 18px;position:relative;overflow:hidden;transition:border-color .2s;}
.stat-card::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;background:var(--accent-color,var(--orange));opacity:.6;}
.stat-label{font-size:9px;text-transform:uppercase;letter-spacing:.14em;color:var(--muted);margin-bottom:6px;}
.stat-value{font-family:var(--font-data);font-size:22px;font-weight:600;color:var(--text);line-height:1;font-variant-numeric:tabular-nums;}
.stat-unit{font-size:10px;color:var(--muted2);margin-left:3px;font-family:var(--font-data);}
.stat-sub{font-size:10px;color:var(--muted2);margin-top:5px;}

/* ── CHART PANEL ── */
.chart-panel{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg);overflow:hidden;margin-bottom:20px;transition:background .25s,border-color .25s;}
.chart-panel-head{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid var(--border);flex-wrap:wrap;gap:10px;}
.chart-panel-title{font-family:var(--font-ui);font-size:12px;font-weight:600;color:var(--text);letter-spacing:0;}
.chart-panel-sub{font-size:10px;color:var(--muted);margin-top:2px;}
.chart-body{padding:20px;position:relative;}
canvas{display:block;width:100%!important;
  /* Let vertical page scroll pass through chart canvases on touch
     devices. Without this, a one-finger touch-drag on a chart can
     trigger Chart.js's tooltip-tracking handlers (or, when the zoom
     plugin's pan was enabled, the pan handler) and call preventDefault
     mid-gesture, leaving the page feeling stuck. pan-y is the
     correct value: the user can still tap for tooltips, but
     vertical scroll always wins. */
  touch-action: pan-y;
}

/* ── CONTROLS ── */
.controls-bar{display:flex;flex-wrap:wrap;gap:10px;align-items:flex-end;background:var(--bg1);border:1px solid var(--border);border-radius:var(--r);padding:16px 20px;margin-bottom:20px;}
.ctrl{display:flex;flex-direction:column;gap:5px;}
.ctrl-label{font-size:9px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);}
select,input[type=number],input[type=text].formula-input{background:var(--surface2);border:1px solid var(--border);border-radius:7px;color:var(--text);font-family:var(--font-data);font-size:12px;padding:7px 10px;outline:none;transition:border-color .2s;min-width:110px;}
select:focus,input[type=number]:focus{border-color:var(--orange);}
select option{background:var(--bg2);}

/* ── PILLS ── */
.pill-row{display:flex;flex-wrap:wrap;gap:7px;align-items:center;}
.pill{display:flex;align-items:center;gap:5px;border-radius:999px;padding:4px 12px;font-size:11px;font-weight:500;border:1.5px solid;white-space:nowrap;}
.pill-remove{background:none;border:none;color:inherit;opacity:.55;cursor:pointer;font-size:15px;line-height:1;padding:0;transition:opacity .15s;}
.pill-remove:hover{opacity:1;}
.btn-add-pill{background:none;border:1.5px dashed var(--border);color:var(--muted);font-family:var(--font-data);font-size:11px;padding:4px 12px;border-radius:999px;cursor:pointer;transition:border-color .2s,color .2s;display:flex;align-items:center;gap:4px;}
.btn-add-pill:hover{border-color:var(--orange);color:var(--orange);}
.pill-input-wrap{display:flex;align-items:center;gap:6px;}
.pill-input-wrap input{width:64px;min-width:unset;padding:4px 8px;}
.btn-pill-confirm{background:var(--orange);color:#fff;border:none;font-family:var(--font-data);font-size:11px;padding:5px 12px;border-radius:7px;cursor:pointer;}

/* ── ZOOM HINT ── */
.zoom-hint{font-size:9px;color:var(--muted);letter-spacing:.06em;display:flex;align-items:center;gap:5px;}
.zoom-hint kbd{background:var(--surface2);border:1px solid var(--border);border-radius:3px;padding:1px 5px;font-family:var(--font-data);font-size:9px;}
.btn-reset-zoom{background:none;border:1px solid var(--border);color:var(--muted);font-family:var(--font-data);font-size:10px;padding:4px 10px;border-radius:6px;cursor:pointer;transition:border-color .2s,color .2s;}
.btn-reset-zoom:hover{border-color:var(--sky);color:var(--sky);}

/* ── LEGEND ── */
.legend{display:flex;flex-wrap:wrap;gap:14px;margin-top:14px;padding:0 4px;}
.legend-item{display:flex;align-items:center;gap:6px;font-size:10px;color:var(--muted2);}
.legend-swatch{width:20px;height:3px;border-radius:999px;}
.legend-swatch.bar{width:14px;height:10px;border-radius:3px;opacity:.6;}

/* ── TABLE ── */
.data-table-wrap{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg);overflow:hidden;}
.data-table-controls{display:flex;align-items:center;justify-content:space-between;padding:14px 20px;border-bottom:1px solid var(--border);gap:12px;flex-wrap:wrap;}
.search-input{background:var(--surface2);border:1px solid var(--border);border-radius:7px;color:var(--text);font-family:var(--font-data);font-size:12px;padding:7px 12px;outline:none;width:220px;transition:border-color .2s;}
.search-input:focus{border-color:var(--orange);}
.search-input::placeholder{color:var(--muted);}

/* ── Log filter bar (Plan 06 phases 1+2+3) ──────────────────────────
 *
 * Sits below .data-table-controls inside .data-table-wrap. Two stacked
 * rows: pill-toggles for type/HR/distance, and date inputs + preset
 * chips. Active-filter chip cluster sits below both rows.
 *
 * The pill-toggle styles are shared with other surfaces; this block
 * only owns layout and the chip / date / preset styles.
 */
.log-filter-bar {
  padding: 12px 20px;
  border-bottom: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: 10px;
  background: var(--surface);
}
.log-filter-row {
  display: flex;
  flex-wrap: wrap;
  gap: 16px 20px;
  align-items: flex-end;
}
.log-filter-group {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.log-filter-label {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  font-weight: 500;
  font-family: var(--font-ui);
}
.log-date-input {
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: 7px;
  color: var(--text);
  font-family: var(--font-data);
  font-size: var(--fs-sm);
  padding: 6px 10px;
  outline: none;
  transition: border-color 0.2s;
  font-variant-numeric: tabular-nums;
  /* Native date picker on most browsers exposes a calendar icon. Keep
     it visible but muted via filter so it doesn't clash with the
     theme. Works on Chromium and modern Safari. */
  color-scheme: light dark;
}
.log-date-input:focus {
  border-color: var(--orange);
}
.log-preset-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
}
.log-preset-btn {
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 4px 10px;
  font-size: var(--fs-xs);
  color: var(--text2);
  cursor: pointer;
  font-family: var(--font-ui);
  white-space: nowrap;
  transition: border-color 0.15s, color 0.15s;
}
.log-preset-btn:hover {
  border-color: var(--orange);
  color: var(--text);
}

/* Active-filter chip cluster. Each chip shows the filter and a remove
   button. "Clear all" appears when ≥2 chips are active. */
.log-filter-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
}
.log-filter-chips:empty {
  display: none;
}
.log-filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 3px 4px 3px 10px;
  font-size: var(--fs-xs);
  color: var(--text);
  font-family: var(--font-ui);
}
.log-filter-chip-remove {
  background: transparent;
  border: none;
  color: var(--muted);
  cursor: pointer;
  font-size: var(--fs-sm);
  line-height: 1;
  padding: 0 5px;
  border-radius: 999px;
  transition: color 0.15s, background 0.15s;
}
.log-filter-chip-remove:hover {
  color: var(--text);
  background: var(--surface);
}
.log-filter-clear-all {
  background: transparent;
  border: none;
  color: var(--orange);
  cursor: pointer;
  font-size: var(--fs-xs);
  font-family: var(--font-ui);
  padding: 3px 8px;
  margin-left: 4px;
  border-radius: 999px;
  transition: background 0.15s;
}
.log-filter-clear-all:hover {
  background: var(--surface2);
}

/* Empty-state row when filters match no runs. The row spans the full
   table width; the inner button clears all filters. */
.log-empty-row td.log-empty-cell {
  padding: 32px 20px;
  text-align: center;
  color: var(--muted);
  font-size: var(--fs-sm);
  font-family: var(--font-ui);
}
.log-empty-clear {
  background: transparent;
  border: 1px solid var(--orange);
  color: var(--orange);
  border-radius: 999px;
  padding: 4px 12px;
  font-size: var(--fs-xs);
  cursor: pointer;
  font-family: var(--font-ui);
  margin-left: 8px;
  transition: background 0.15s, color 0.15s;
}
.log-empty-clear:hover {
  background: var(--orange);
  color: var(--bg);
}

@media (max-width: 700px) {
  /* On narrow viewports the filter rows wrap; let the date inputs
     take their natural width and the presets wrap onto their own line. */
  .log-filter-row {
    gap: 12px;
  }
  .log-filter-presets {
    width: 100%;
  }
}

table{width:100%;border-collapse:collapse;}
thead th{padding:10px 16px;text-align:left;font-size:9px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);font-weight:500;border-bottom:1px solid var(--border);background:var(--surface2);cursor:pointer;white-space:nowrap;user-select:none;}
thead th:hover{color:var(--text);}
thead th.sort-asc::after{content:' ↑';}
thead th.sort-desc::after{content:' ↓';}
tbody tr{border-bottom:1px solid var(--border);transition:background .15s;}
tbody tr:last-child{border-bottom:none;}
tbody tr:hover{background:var(--surface2);}
td{padding:10px 16px;font-size:11px;color:var(--text);white-space:nowrap;}
td.muted{color:var(--muted2);}
td.good{color:var(--teal);}
.run-type-badge{display:inline-flex;align-items:center;padding:2px 7px;border-radius:4px;font-size:9px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;}
.badge-race{background:rgba(251,113,133,.15);color:var(--rose);}
.badge-long{background:rgba(56,189,248,.12);color:var(--sky);}
.badge-workout{background:rgba(163,230,53,.12);color:var(--lime);}
.badge-easy{background:rgba(74,96,128,.2);color:var(--muted2);}
/* Cross-training (non-run) badge in the activity log — distinct violet so
   it reads as "a different sport," not a run workout type. */
.badge-sport{background:rgba(167,139,250,.14);color:var(--violet);}
/* Best-effort tag — "5K PR", "Fastest 10K in 90d". Renders inline
   inside the activity-log name cell so a user scanning the log spots
   notable runs without reading pace. PR variant uses --gold (highest
   signal); the 90-day variant uses --ctl (still notable, less
   superlative). Tokens-only — no inline hex. */
.log-best-tag{display:inline-block;margin-left:6px;padding:1px 6px;border:1px solid var(--border);border-radius:4px;font-size:9px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;font-family:var(--font-ui);white-space:nowrap;vertical-align:middle;}
.log-best-tag-pr{border-color:var(--gold);color:var(--gold);}
.log-best-tag-recent{border-color:var(--ctl);color:var(--ctl);}
.table-pagination{display:flex;align-items:center;justify-content:space-between;padding:12px 20px;border-top:1px solid var(--border);font-size:10px;color:var(--muted);}
.page-btns{display:flex;gap:6px;}
.page-btn{background:var(--surface2);border:1px solid var(--border);color:var(--muted2);font-family:var(--font-data);font-size:10px;padding:4px 10px;border-radius:5px;cursor:pointer;transition:.15s;}
.page-btn:hover:not(:disabled){border-color:var(--orange);color:var(--orange);}
.page-btn:disabled{opacity:.35;cursor:default;}
.page-btn.active{border-color:var(--orange);color:var(--orange);}

/* ── Log column visibility ────────────────────────────────────────
   The Activity Log table supports per-athlete column toggling
   (utils/log-columns.js). Hidden columns are applied as classes on
   .data-table-wrap, e.g. .log-col-hide-hr_max. The selectors below
   match each toggleable column. Date and Name are mandatory so they
   have no hide rule. */
.log-col-hide-distance_mi  th[data-col="distance_mi"],
.log-col-hide-distance_mi  td[data-col="distance_mi"]  { display: none; }
.log-col-hide-moving_time  th[data-col="moving_time"],
.log-col-hide-moving_time  td[data-col="moving_time"]  { display: none; }
.log-col-hide-pace_sec_mi  th[data-col="pace_sec_mi"],
.log-col-hide-pace_sec_mi  td[data-col="pace_sec_mi"]  { display: none; }
.log-col-hide-hr_avg       th[data-col="hr_avg"],
.log-col-hide-hr_avg       td[data-col="hr_avg"]       { display: none; }
.log-col-hide-hr_max       th[data-col="hr_max"],
.log-col-hide-hr_max       td[data-col="hr_max"]       { display: none; }
.log-col-hide-cadence_spm  th[data-col="cadence_spm"],
.log-col-hide-cadence_spm  td[data-col="cadence_spm"]  { display: none; }
.log-col-hide-elevation_m  th[data-col="elevation_m"],
.log-col-hide-elevation_m  td[data-col="elevation_m"]  { display: none; }
.log-col-hide-vo2max_est   th[data-col="vo2max_est"],
.log-col-hide-vo2max_est   td[data-col="vo2max_est"]   { display: none; }
.log-col-hide-workout_type th[data-col="workout_type"],
.log-col-hide-workout_type td[data-col="workout_type"] { display: none; }

/* Columns button — sits in .data-table-controls next to the search
   and count. Anchor for the popover. Matches the visual weight of
   .page-btn so it doesn't read as a primary action. */
.btn-columns {
  background: var(--surface2);
  border: 1px solid var(--border);
  color: var(--muted2);
  font-family: var(--font-ui);
  font-size: var(--fs-xs);
  padding: 6px 12px;
  border-radius: 5px;
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-weight: 600;
}
.btn-columns:hover { border-color: var(--accent); color: var(--accent); }
.btn-columns:focus { outline: none; border-color: var(--accent); color: var(--accent); }

/* Columns popover — body-mounted, positioned by JS. Owns its own
   scroll if the column list grows beyond viewport height. */
.log-columns-popover {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.35);
  min-width: 200px;
  z-index: 100;
  padding: 6px 0;
  font-family: var(--font-ui);
}
.log-columns-header {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  padding: 8px 16px 4px;
}
.log-columns-body {
  display: flex;
  flex-direction: column;
  padding: 4px 0;
}
.log-columns-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 16px;
  cursor: pointer;
  font-size: var(--fs-sm);
  color: var(--text);
  user-select: none;
}
.log-columns-row:hover { background: var(--surface2); }
.log-columns-row-mandatory { cursor: default; color: var(--muted); }
.log-columns-row-mandatory:hover { background: transparent; }
.log-columns-row input[type="checkbox"] { margin: 0; cursor: pointer; }
.log-columns-row-mandatory input[type="checkbox"] { cursor: default; }
.log-columns-label { flex: 1; }
.log-columns-locked {
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
}
.log-columns-footer {
  border-top: 1px solid var(--border);
  padding: 8px 16px;
  text-align: right;
}
.log-columns-reset {
  font-size: var(--fs-xs);
  color: var(--accent);
  text-decoration: none;
  cursor: pointer;
}
.log-columns-reset:hover { color: var(--accent-hi); text-decoration: underline; }

/* ── OVERVIEW HERO ── */
.overview-hero{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-bottom:20px;}
@media(max-width:900px){.overview-hero{grid-template-columns:1fr 1fr;}}
@media(max-width:580px){.overview-hero{grid-template-columns:1fr;}}
.hero-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg);padding:22px 24px;position:relative;overflow:hidden;}
.hero-card-bg{position:absolute;bottom:-20px;right:-20px;font-size:80px;opacity:.05;line-height:1;pointer-events:none;}
.hero-card-label{font-size:9px;text-transform:uppercase;letter-spacing:.18em;color:var(--muted);margin-bottom:8px;}
.hero-card-value{font-family:var(--font-data);font-size:32px;font-weight:600;color:var(--text);line-height:1;font-variant-numeric:tabular-nums;}
.hero-card-unit{font-size:14px;color:var(--muted2);margin-left:4px;}
.hero-card-sub{font-size:11px;color:var(--muted2);margin-top:8px;}

/* ── VO2 BAR MEASURE ──
   ACSM measure bar rendered inline inside the Current strip cell.
   Replaces the prior standalone Fitness Category Gauge panel. The
   pattern mirrors the ACWR strip in sidebar-stats.js: a segmented
   colored bar (Poor/Fair/Good/Excellent/Superior) with a dot at the
   user's value, threshold ticks beneath, and an optional nudge line
   when age or gender is missing.

   Lives inside .stat-strip-cell so the markup flows in the strip's
   row. The .vo2-current-cell class on the cell gives the bar a
   wider flex basis than peer cells, so the ticks have room to read. */

/* Widen the Current cell so the bar measure has horizontal room.
   Peer cells stay at flex:1; flex:2.4 leaves the bar ~140-180px wide
   on a typical desktop viewport while keeping all cells legible. */
.vo2-current-cell {
  flex: 2.4 1 0;
  min-width: 220px;
  /* Override the default .stat-strip-cell white-space:nowrap so the
     bar measure and ticks can wrap naturally inside the cell. */
  white-space: normal;
}

/* The bar itself. Height matches .sidebar-acwr-strip's 6px so the
   visual weight is the same as ACWR on Overview. */
.vo2-bar-strip {
  position: relative;
  height: 6px;
  border-radius: 3px;
  display: flex;
  overflow: visible;
  margin-top: 8px;
}
.vo2-bar-seg { height: 100%; opacity: 0.55; }
.vo2-bar-seg:first-child { border-radius: 3px 0 0 3px; }
.vo2-bar-seg:last-child  { border-radius: 0 3px 3px 0; }
/* Dot indicating the user's actual VO2max. White-bordered circle for
   contrast against any segment color underneath. */
.vo2-bar-dot {
  position: absolute;
  top: 50%;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--text);
  border: 2px solid var(--bg);
  transform: translate(-50%, -50%);
  pointer-events: none;
}
/* Tick labels at threshold positions. Absolute-positioned so they
   align with their actual values on the bar, not evenly spaced. */
.vo2-bar-ticks {
  position: relative;
  height: 14px;
  margin-top: 4px;
  font-family: var(--font-data);
  font-size: 9px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.vo2-bar-ticks span {
  position: absolute;
  transform: translateX(-50%);
  top: 0;
}
/* Nudge line: small caption beneath the bar prompting the user to
   set missing profile data. Hidden entirely when both age and gender
   are set. */
.vo2-bar-nudge {
  font-size: var(--fs-xs);
  color: var(--muted);
  margin-top: 6px;
  line-height: 1.4;
}

/* Zero-state cell — free user with no race-tagged runs. Stretches
   the strip cell to a column so the explanation reads as a block,
   not a single value. Sits in the same .stat-strip parent as the
   normal strip cells so layout matches the populated states. */
.vo2-zero-state {
  align-items: flex-start;
  text-align: left;
}

/* ── Chart-panel collapsible ────────────────────────────────────────
   Power-user panels that should be available but not the page lead.
   Renders as a single-row eyebrow + sub line; clicking expands to
   reveal the chart body. Used by the VO2 page (distribution +
   by-distance) per the page redesign. Browser-managed state
   (native <details>) so it persists across re-renders. */
details.chart-panel-collapsible {
  /* Inherit .chart-panel surface/border styling. */
  padding: 0;
  cursor: default;
}
details.chart-panel-collapsible > summary {
  list-style: none;
  cursor: pointer;
  padding: 14px 20px;
  display: flex;
  align-items: baseline;
  gap: 12px;
  user-select: none;
}
/* Remove the default disclosure triangle in WebKit. The triangle
   makes the summary read like a form control; we want it to read
   as a clean tappable label with our own affordance. */
details.chart-panel-collapsible > summary::-webkit-details-marker {
  display: none;
}
/* Custom chevron — rotates 90° when open. Cheaper than animating a
   pseudo-element and reads at a glance. */
details.chart-panel-collapsible > summary::before {
  content: "›";
  color: var(--muted);
  font-size: var(--fs-base);
  line-height: 1;
  margin-right: 4px;
  transition: transform 0.15s ease;
  display: inline-block;
}
details.chart-panel-collapsible[open] > summary::before {
  transform: rotate(90deg);
}
details.chart-panel-collapsible > summary > .section-eyebrow {
  margin-bottom: 0;
}
details.chart-panel-collapsible > summary > .chart-panel-sub {
  font-size: var(--fs-sm);
  color: var(--muted);
}
.chart-panel-collapsible-body {
  padding: 0 8px 16px;
}

/* ── Chart-panel toggle (open-by-default collapsible) ───────────────
   The lead chart on each tab uses this variant. The native
   chart-panel-head stays as the summary, with a chevron prepended
   so users see the affordance. Open by default; clicking the head
   row collapses. Browser-managed state; not persisted. */
details.chart-panel-toggle {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  overflow: hidden;
  margin-bottom: 20px;
  transition: background .25s, border-color .25s;
}
details.chart-panel-toggle > summary {
  list-style: none;
  cursor: pointer;
  user-select: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px;
  border-bottom: 1px solid var(--border);
  flex-wrap: wrap;
  gap: 10px;
}
details.chart-panel-toggle:not([open]) > summary {
  border-bottom: 1px solid transparent;
}
details.chart-panel-toggle > summary::-webkit-details-marker {
  display: none;
}
/* Chevron sits to the left of the title block, matching the
   chart-panel-collapsible affordance. */
details.chart-panel-toggle > summary > .chart-panel-toggle-chevron {
  color: var(--muted);
  font-size: var(--fs-base);
  line-height: 1;
  margin-right: 8px;
  transition: transform 0.15s ease;
  display: inline-block;
  flex: 0 0 auto;
}
details.chart-panel-toggle[open] > summary > .chart-panel-toggle-chevron {
  transform: rotate(90deg);
}
/* The title block (title + sub) is the existing structure; wrap
   it in a flex item so chevron and right-side controls sit either
   side of it. */
details.chart-panel-toggle > summary > .chart-panel-head-inner {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

/* Height ceiling utility classes for the chart-body. Use these on
   the .chart-body element to cap the canvas. Chart.js runs with
   maintainAspectRatio:false globally, so the canvas fills its
   parent; capping .chart-body is what actually bounds rendered
   height. */
.chart-body-h-lead    { max-height: var(--chart-h-lead);    }
.chart-body-h-default { max-height: var(--chart-h-default); }
.chart-body-h-compact { max-height: var(--chart-h-compact); }
.chart-body-h-lead canvas,
.chart-body-h-default canvas,
.chart-body-h-compact canvas {
  max-height: 100%;
}

/* ── RACE PREDICTOR ── */
.race-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:12px;margin-bottom:24px;}
.race-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg);padding:18px 20px 20px;position:relative;overflow:hidden;transition:border-color .2s,transform .15s;}
.race-card:hover{border-color:var(--border2);transform:translateY(-1px);}
.race-card-dist{font-size:9px;text-transform:uppercase;letter-spacing:.16em;color:var(--muted);margin-bottom:6px;}
.race-card-time{font-family:var(--font-data);font-size:24px;font-weight:600;color:var(--text);line-height:1;font-variant-numeric:tabular-nums;}
.race-card-pace{font-size:11px;color:var(--muted2);margin-top:6px;}
.race-card-range{display:flex;align-items:center;gap:6px;margin-top:10px;font-size:10px;color:var(--muted);font-variant-numeric:tabular-nums;}
.race-card-range-label{font-size:8px;text-transform:uppercase;letter-spacing:.12em;color:var(--muted);}
.race-card-range-bar{flex:1;height:3px;background:var(--border);border-radius:999px;position:relative;overflow:hidden;}
.race-card-range-fill{position:absolute;top:0;bottom:0;border-radius:999px;}
.race-card-models{margin-top:10px;padding-top:10px;border-top:1px solid var(--border);font-size:9px;color:var(--muted);line-height:1.6;font-variant-numeric:tabular-nums;}
.race-card-model-row{display:flex;justify-content:space-between;gap:8px;}
.race-card-model-name{color:var(--muted2);}
.race-card-bar{position:absolute;bottom:0;left:0;right:0;height:3px;}
/* Peregrine range block */
.peregrine-block{margin-top:10px;padding:8px 10px;background:var(--accent-dim);border:1px solid rgba(255,60,172,.25);border-radius:var(--r);font-variant-numeric:tabular-nums;}
.peregrine-block-label{font-size:8px;text-transform:uppercase;letter-spacing:.14em;color:var(--accent-hi);font-weight:600;margin-bottom:4px;display:flex;align-items:center;gap:5px;}
.peregrine-range-time{font-family:var(--font-data);font-size:11px;color:var(--text);font-weight:500;}
.peregrine-range-pace{font-size:10px;color:var(--muted2);margin-top:1px;}
.peregrine-range-bar-wrap{margin-top:5px;display:flex;align-items:center;gap:6px;}
.peregrine-range-bar-track{flex:1;height:4px;background:rgba(255,60,172,.15);border-radius:999px;position:relative;}
.peregrine-range-bar-fill{position:absolute;top:0;bottom:0;border-radius:999px;background:var(--accent);opacity:.6;}
.peregrine-range-bar-dot{position:absolute;top:50%;transform:translate(-50%,-50%);width:8px;height:8px;border-radius:50%;background:var(--accent);border:2px solid var(--surface);}
.confidence-pill{display:inline-flex;align-items:center;gap:3px;font-size:8px;text-transform:uppercase;letter-spacing:.1em;font-weight:600;padding:2px 6px;border-radius:999px;}
/* When a stat-tip wraps a confidence-pill, the default dotted underline
   conflicts with the pill's rounded shape. Suppress the underline; the
   pill itself is a clear-enough hover affordance. */
.confidence-pill.stat-tip{border-bottom:none;cursor:help;}
.confidence-high{background:rgba(0,245,212,.15);color:var(--teal);}
.confidence-medium{background:rgba(245,158,11,.15);color:var(--gold);}
.confidence-low{background:rgba(248,113,113,.15);color:var(--rose);}

/* ── MARATHON READINESS ── */
.readiness-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg);padding:24px;margin-bottom:20px;}
.readiness-score-wrap{display:flex;align-items:center;gap:32px;flex-wrap:wrap;}
.readiness-score-circle{position:relative;width:120px;height:120px;flex-shrink:0;}
.readiness-score-circle svg{width:100%;height:100%;}
.readiness-score-num{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;}
.readiness-score-num span{font-family:var(--font-data);font-size:26px;font-weight:600;color:var(--text);line-height:1;}
.readiness-score-num small{font-size:9px;color:var(--muted);letter-spacing:.1em;}
.readiness-factors{flex:1;display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px;}
.readiness-factor{background:var(--surface2);border-radius:var(--r);padding:12px 14px;}
.rf-label{font-size:9px;text-transform:uppercase;letter-spacing:.12em;color:var(--muted);margin-bottom:6px;}
.rf-bar-wrap{background:var(--border);border-radius:999px;height:4px;margin-bottom:6px;overflow:hidden;}
.rf-bar{height:100%;border-radius:999px;transition:width .6s ease;}
.rf-value{font-size:11px;color:var(--text);}

/* ── FATIGUE / TRIMP ── */
.fitness-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg);padding:20px 24px;margin-bottom:20px;}
.fitness-metrics{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-bottom:20px;}
@media(max-width:700px){.fitness-metrics{grid-template-columns:1fr;}}
.fm-box{background:var(--surface2);border-radius:var(--r);padding:14px 16px;text-align:center;}
.fm-label{font-size:9px;text-transform:uppercase;letter-spacing:.14em;color:var(--muted);margin-bottom:6px;}
.fm-val{font-family:var(--font-data);font-size:24px;font-weight:500;line-height:1;font-variant-numeric:tabular-nums;}
.fm-sub{font-size:10px;color:var(--muted2);margin-top:4px;}

/* Fatigue page 7-day plan grid. The grid-template-columns and gap
   used to live as an inline style on #fat-plan-grid in index.html;
   moving them here lets the mobile.css override at ≤640px do its
   work without !important. */
.fat-plan-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(128px, 1fr));
  gap: 8px;
}

/* ── CUSTOM METRICS ── */
.custom-builder{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg);padding:24px;margin-bottom:20px;}
.custom-builder h3{font-family:var(--font-ui);font-size:12px;font-weight:600;color:var(--text);margin-bottom:16px;}
.formula-row{display:flex;gap:10px;align-items:flex-end;flex-wrap:wrap;margin-bottom:12px;}
.formula-input{flex:1;min-width:240px;font-family:var(--font-data);font-size:12px;padding:8px 12px;}
input.formula-input:focus{border-color:var(--teal);}
.btn-primary{background:var(--orange);color:#fff;border:none;font-family:var(--font-ui);font-size:11px;font-weight:700;padding:8px 16px;border-radius:7px;cursor:pointer;transition:opacity .2s;letter-spacing:.02em;}
.btn-primary:hover{opacity:.85;}
.btn-secondary{background:var(--surface2);border:1px solid var(--border);color:var(--muted2);font-family:var(--font-data);font-size:11px;padding:8px 14px;border-radius:7px;cursor:pointer;transition:border-color .2s,color .2s;}
.btn-secondary:hover{border-color:var(--border2);color:var(--text);}
.formula-error{font-size:11px;color:var(--rose);margin-top:6px;min-height:16px;}
.formula-preview{font-size:11px;color:var(--teal);margin-top:6px;}
.custom-metric-list{display:flex;flex-direction:column;gap:12px;margin-top:16px;}
.custom-metric-item{background:var(--surface2);border:1px solid var(--border);border-radius:var(--r);padding:14px 16px;}
.cmi-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;}
.cmi-name{font-family:var(--font-ui);font-size:12px;font-weight:600;color:var(--text);}
.cmi-formula{font-size:10px;color:var(--muted2);font-family:var(--font-data);}
/* "starter" pill shown next to seeded metric names. Lets the user
   distinguish auto-populated metrics from ones they built themselves.
   Removing a starter is fine — we don't re-seed on next visit. */
.cmi-starter-tag {
  display: inline-block;
  margin-left: 8px;
  padding: 1px 6px;
  border: 1px solid var(--border2);
  border-radius: 4px;
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  font-family: var(--font-ui);
  vertical-align: 1px;
}
.btn-delete{background:none;border:1px solid var(--border);color:var(--muted);font-family:var(--font-data);font-size:10px;padding:3px 8px;border-radius:5px;cursor:pointer;transition:.2s;}
.btn-delete:hover{border-color:var(--rose);color:var(--rose);}
/* Slug tag shown next to a metric name. Tells the user the exact
   identifier other formulas can reference this metric by. Visually
   subdued — informational, not decorative. */
.cmi-slug-tag {
  display: inline-block;
  margin-left: 6px;
  vertical-align: 1px;
}
.cmi-slug-tag code {
  font-size: 10px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 1px 5px;
  color: var(--muted2);
  font-family: var(--font-data);
}
/* Broken-state custom metric card. Used when the metric references a
   slug that no longer exists, when the metric is in a cycle, or when
   the formula no longer parses. We mute the name and surface a one-line
   reason; the chart is suppressed entirely (rendering it would either
   throw or show an empty axis). */
.custom-metric-item.cmi-broken {
  opacity: 0.85;
}
.custom-metric-item.cmi-broken .cmi-name {
  color: var(--muted);
}
.cmi-broken-msg {
  font-size: 11px;
  color: var(--rose);
  font-family: var(--font-ui);
  margin: 2px 0 8px;
}
.avail-vars{font-size:10px;color:var(--muted);line-height:1.8;margin-top:8px;}
/* "View on Strava" attribution link, per Strava brand guidelines: text "View on Strava" in Strava orange #FC4C02. Sits alongside .btn-delete in the action cell. */
.strava-link{color:#fc4c02;font-weight:600;text-decoration:none;font-size:11px;margin-right:8px;letter
spacing:.01em;white-space:nowrap;}
.strava-link:hover{text-decoration:underline;}
.avail-vars code{background:var(--surface2);border:1px solid var(--border);border-radius:4px;padding:1px 5px;color:var(--sky);font-family:var(--font-data);}
/* Clickable variable chips in the help panel. Inserts the chip's
   identifier into the formula input at the caret on click. Example-
   formula blocks intentionally lack this class so a click doesn't
   paste a full expression. */
.avail-vars code.cm-var-chip {
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
  user-select: none;
}
.avail-vars code.cm-var-chip:hover {
  background: var(--surface);
  border-color: var(--border2);
}

/* ── LOADING ── */
#loading-screen{display:none;position:fixed;inset:0;z-index:500;background:var(--bg);flex-direction:column;align-items:center;justify-content:center;gap:20px;transition:background .25s;}
.loader-ring{width:52px;height:52px;border:3px solid var(--border);border-top-color:var(--orange);border-radius:50%;animation:spin .75s linear infinite;}
@keyframes spin{to{transform:rotate(360deg);}}
.loader-text{color:var(--muted2);font-size:12px;letter-spacing:.08em;}
.loader-progress{width:200px;height:2px;background:var(--border);border-radius:999px;overflow:hidden;}
.loader-bar{height:100%;background:var(--orange);width:0%;transition:width .4s ease;}

/* ── MISC ── */
.alert{background:rgba(251,113,133,.08);border:1px solid rgba(251,113,133,.25);border-radius:var(--r);padding:12px 16px;color:var(--rose);font-size:12px;margin-bottom:16px;display:none;}
.two-col{display:grid;grid-template-columns:1fr 1fr;gap:16px;}
@media(max-width:1000px){.two-col{grid-template-columns:1fr;}}
.full-col{display:grid;grid-template-columns:1fr;gap:16px;}
.three-col{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;}
@media(max-width:1100px){.three-col{grid-template-columns:1fr 1fr;}}
@media(max-width:700px){.three-col{grid-template-columns:1fr;}}
@media(max-width:700px){.page-inner{padding:20px 14px;}.controls-bar{flex-direction:column;}}
.info-box{background:rgba(56,189,248,.06);border:1px solid rgba(56,189,248,.2);border-radius:var(--r);padding:12px 16px;font-size:11px;color:var(--sky);line-height:1.7;margin-bottom:16px;}
.info-box-muted{background:rgba(160,170,200,.04);border:1px solid var(--border);color:var(--muted2);font-size:10px;padding:10px 14px;line-height:1.6;}
/* Math/formula block inside .info-box. Monospace, slightly inset.
   Uses <sub> and <sup> for variable subscripts and exponents. The
   sub/sup default sizes are too aggressive on mono fonts, so we soften
   them. */
.info-box-formula{font-family:var(--font-data);font-size:12px;background:rgba(0,0,0,.18);border-radius:6px;padding:10px 14px;margin-top:4px;color:var(--text2);line-height:1.9;}
.info-box-formula sub,.info-box-formula sup{font-size:.78em;line-height:0;}
.info-box-formula-where{display:block;font-size:10.5px;color:var(--muted2);margin-top:6px;}
.rb-row{display:grid;grid-template-columns:140px 1fr;gap:14px;align-items:baseline;padding:6px 0;border-bottom:1px solid var(--border);}
.rb-row:last-child{border-bottom:none;}
.rb-label{font-size:9px;text-transform:uppercase;letter-spacing:.14em;color:var(--muted);font-weight:600;}
.rb-value{font-size:11px;color:var(--text2);font-variant-numeric:tabular-nums;}
.rb-notes{margin-top:8px;padding-top:8px;border-top:1px solid var(--border);font-size:10px;color:var(--muted2);line-height:1.6;}
.rb-notes div + div{margin-top:2px;}
@media(max-width:700px){.rb-row{grid-template-columns:1fr;gap:2px;}}
.section-head-sm{margin-bottom:10px;}
.section-title-sm{font-size:11px !important;text-transform:uppercase;letter-spacing:.14em;color:var(--muted) !important;font-weight:600;}
.section-head-sm .section-sub{font-size:11px;color:var(--muted2);}
.readiness-card-sm{padding:16px 18px;}
.readiness-card-sm .readiness-score-circle{width:88px;height:88px;}
.readiness-card-sm .readiness-score-num span{font-size:20px;}

.model-table-wrap{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg);padding:14px 16px;margin-bottom:20px;overflow-x:auto;}
.model-table{width:100%;border-collapse:collapse;font-size:11px;font-variant-numeric:tabular-nums;}
.model-table thead th{font-size:9px;text-transform:uppercase;letter-spacing:.12em;color:var(--muted);font-weight:600;padding:8px 10px 10px;border-bottom:1px solid var(--border);text-align:left;}
.model-table thead th:not(:first-child){text-align:right;}
.model-table .mt-model-col{font-size:10px;color:var(--muted2);font-weight:500;text-transform:none;letter-spacing:0;text-align:left;white-space:nowrap;padding:8px 10px;}
.model-table tbody tr{border-bottom:1px solid var(--border);}
.model-table tbody tr:last-child{border-bottom:none;}
.model-table .mt-cell{padding:8px 10px;text-align:right;}
.model-table .mt-time{font-family:var(--font-data);font-size:12px;color:var(--text);font-weight:500;line-height:1;}
.model-table .mt-pace{font-size:9px;color:var(--muted2);margin-top:3px;}
.model-table .mt-empty{color:var(--muted);}
.model-table .mt-median-row{border-top:1px solid var(--border2);}
.model-table .mt-median-row th,.model-table .mt-median-row td{padding-top:10px;}
.model-table .mt-median-row .mt-time{color:var(--muted2);font-weight:600;}
.model-table .mt-median-row .mt-model-col{color:var(--muted);font-weight:600;}
.tag{display:inline-block;background:var(--surface2);border:1px solid var(--border);border-radius:5px;padding:2px 7px;font-size:10px;color:var(--muted2);margin:2px;}

/* ── THEME TOGGLE ── */
[data-theme="light"] body::before{opacity:.5;}
[data-theme="light"] header{background:rgba(248,250,252,.93);}
.theme-btn{background:none;border:1px solid var(--border);border-radius:6px;padding:5px 9px;cursor:pointer;font-size:14px;line-height:1;color:var(--text2);transition:border-color .2s;}
.theme-btn:hover{border-color:var(--border2);}

/* ── REFINED TYPOGRAPHY ── */
.stat-label{font-size:10px;text-transform:uppercase;letter-spacing:.1em;color:var(--muted);margin-bottom:5px;font-family:var(--font-ui);font-weight:500;}
.ctrl-label{font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);font-weight:500;}
.section-sub{font-size:12px;color:var(--text2);margin-top:3px;font-weight:400;}
.chart-panel-sub{font-size:11px;color:var(--text2);margin-top:2px;}
.stat-unit{font-size:11px;color:var(--muted2);margin-left:2px;font-family:var(--font-data);}
.hero-card-unit{font-size:13px;color:var(--muted2);margin-left:3px;font-family:var(--font-data);}
.hero-card-sub{font-size:12px;color:var(--text2);margin-top:8px;}
.hero-card-label{font-size:10px;text-transform:uppercase;letter-spacing:.12em;color:var(--muted);margin-bottom:8px;font-weight:500;}

/* ── REFINED CARDS ── */
.stat-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);padding:14px 16px;transition:background .25s,border-color .25s;}
.stat-card::before{height:2px;opacity:.7;}
.hero-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg);padding:20px 22px;}
.chart-panel{background:var(--surface);border:1px solid var(--border);}
.controls-bar{background:var(--bg1);border:1px solid var(--border);}
.data-table-wrap{background:var(--surface);}
.readiness-card{background:var(--surface);}
.custom-builder{background:var(--surface);}
.fitness-card{background:var(--surface);}
.custom-metric-item{background:var(--surface2);}
.readiness-factor{background:var(--surface2);}
.fm-box{background:var(--surface2);}
.race-card{background:var(--surface);}

/* ── INPUTS ── */
select,input[type=number],input[type=text]{
  background:var(--surface2);border:1px solid var(--border);
  color:var(--text);font-family:var(--font-data);font-size:12px;
  padding:7px 10px;border-radius:6px;outline:none;transition:border-color .2s;
}
select:focus,input:focus{border-color:var(--accent);}
select option{background:var(--bg2);color:var(--text);}
.search-input{background:var(--surface2);border:1px solid var(--border);color:var(--text);}
.search-input::placeholder{color:var(--muted);}
thead th{background:var(--surface2);color:var(--muted);}
tbody tr:hover{background:var(--surface2);}
.page-btn{background:var(--surface2);border:1px solid var(--border);color:var(--text2);}

/* ── DASHBOARD CUSTOMIZE ── */
/* ── Dashboard kebab + dropdown menu ────────────────────────────────
   Replaces the old labeled "Customize" button. The kebab sits in the
   section-head row alongside the Window/Unit selectors; the dropdown
   floats below it on click. Two items: Customize dashboard, Reset to
   defaults. Bound by setupDashKebab() in dashboard/modal.js.
*/
.kebab-wrap {
  position: relative;
  /* Match the height the old labeled button occupied so the row's
     align-items:flex-end keeps the kebab visually centered with the
     selectors next to it (which have a label above their select). */
  display: flex;
  align-items: center;
  height: 32px;
}
.kebab {
  background: none;
  border: 1px solid var(--border);
  color: var(--muted);
  font-family: var(--font-ui);
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.04em;
  width: 32px;
  height: 32px;
  border-radius: 6px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* The ⋯ glyph sits visually below the optical center; nudge up so
     the dots align with the selectors next to it. */
  line-height: 1;
  padding-bottom: 2px;
  transition: border-color 0.15s, color 0.15s;
}
.kebab:hover,
.kebab[aria-expanded="true"] {
  border-color: var(--border2);
  color: var(--text);
}
.kebab-menu {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  min-width: 200px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r);
  /* Soft shadow for separation from the page; keeps the menu legible
     against the chart backgrounds it may overlap. */
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
  padding: 4px;
  z-index: 50;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
/* The [hidden] HTML attribute defaults to display:none in the UA
   stylesheet, but our class-level `display: flex` above wins on
   specificity. Restore the hide-when-hidden behavior with an
   attribute selector that beats the class rule. */
.kebab-menu[hidden] {
  display: none;
}
.kebab-item {
  background: none;
  border: none;
  color: var(--text);
  font-family: var(--font-ui);
  font-size: 12px;
  font-weight: 500;
  text-align: left;
  padding: 8px 12px;
  border-radius: 4px;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.12s, color 0.12s;
}
.kebab-item:hover {
  background: var(--surface2);
  color: var(--text);
}
.modal-overlay{display:none;position:fixed;inset:0;z-index:400;background:rgba(0,0,0,.6);backdrop-filter:blur(4px);align-items:flex-start;justify-content:center;padding:40px 20px;overflow-y:auto;}
.modal-overlay.open{display:flex;}
.modal{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg);width:100%;max-width:680px;box-shadow:0 32px 80px rgba(0,0,0,.5);overflow:hidden;}
.modal-header{display:flex;align-items:center;justify-content:space-between;padding:18px 22px;border-bottom:1px solid var(--border);}
.modal-title{font-family:var(--font-ui);font-size:14px;font-weight:600;color:var(--text);}
.modal-close{background:none;border:none;color:var(--muted);cursor:pointer;font-size:20px;padding:2px 6px;border-radius:4px;transition:color .15s;}
.modal-close:hover{color:var(--text);}
.modal-body{padding:22px;}
.modal-section-title{font-size:10px;text-transform:uppercase;letter-spacing:.12em;color:var(--muted);font-weight:600;margin-bottom:10px;margin-top:20px;}
.modal-section-title:first-child{margin-top:0;}
.dash-panel-list{display:flex;flex-direction:column;gap:6px;}
.dash-panel-item{display:flex;align-items:center;gap:10px;background:var(--surface2);border:1px solid var(--border);border-radius:var(--r);padding:10px 12px;cursor:grab;user-select:none;transition:border-color .15s,opacity .15s;}
.dash-panel-item:active{cursor:grabbing;}
.dash-panel-item.dragging{opacity:.4;border-style:dashed;}
.dash-panel-item.drag-over{border-color:var(--accent);}
.drag-handle{color:var(--muted);font-size:14px;cursor:grab;flex-shrink:0;}
.dash-panel-name{flex:1;font-size:12px;color:var(--text);font-weight:500;}
.dash-panel-type{font-size:10px;color:var(--muted2);margin-left:4px;}
.dash-panel-remove{background:none;border:1px solid var(--border);color:var(--muted);font-size:10px;padding:3px 8px;border-radius:5px;cursor:pointer;transition:.15s;font-family:var(--font-data);}
.dash-panel-remove:hover{border-color:var(--rose);color:var(--rose);}
.widget-picker{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:8px;margin-top:8px;}
.widget-option{background:var(--surface2);border:1px solid var(--border);border-radius:var(--r);padding:10px 12px;cursor:pointer;transition:border-color .15s,background .15s;}
.widget-option:hover{border-color:var(--accent);background:var(--bg3);}
.widget-option.added{opacity:.45;cursor:default;pointer-events:none;}
.widget-option-name{font-size:12px;font-weight:500;color:var(--text);margin-bottom:2px;}
.widget-option-sub{font-size:10px;color:var(--muted2);}
.modal-actions{display:flex;justify-content:flex-end;gap:8px;padding:16px 22px;border-top:1px solid var(--border);}
#dash-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;}
@media(max-width:900px){#dash-grid{grid-template-columns:1fr;}}
.dash-widget{position:relative;}
.dash-widget-full{grid-column:1/-1;}

/* ── Widget chrome strip (Phase B-4) ────────────────────────────────
   The dashboard grid lives only inside #dash-grid. We strip the
   panel chrome (background, border, padding, panel-head border-
   bottom) so the grid reads as a typographic dashboard report rather
   than a stack of 4-9 cards. Other uses of .chart-panel on Race,
   VO2, Training etc. are unaffected — the override scopes to
   .dash-widget specifically.

   Hairlines between rows on desktop. Grid is 2-up
   (grid-template-columns: 1fr 1fr); widgets at index 3 onward sit
   below row 1, so a top border on those creates a clean inter-row
   rule. Mobile (single column) drops the rule — vertical spacing
   alone separates widgets there. */
#dash-grid .dash-widget.chart-panel {
  background: transparent;
  border: none;
  border-radius: 0;
  margin-bottom: 0;
  padding: 0;
  /* The original .chart-panel sets overflow:hidden, which would clip
     tooltips drawn by the .stat-tip system. Reset it. */
  overflow: visible;
}
#dash-grid .dash-widget .chart-panel-head {
  border-bottom: none;
  padding: 0 0 var(--space-tight);
}
#dash-grid .dash-widget .chart-body {
  /* The chart-body padding originally compensated for the panel-head
     border-bottom and gave the canvas margin from the panel edge.
     Without the panel border, the chart can sit flush with the
     widget edge horizontally. Keep a tiny bottom padding so adjacent
     widgets in the desktop grid don't kiss. */
  padding: 0 0 var(--space-row);
}
@media (min-width: 901px) {
  /* Desktop: hairline between rows. Two-column grid, so widgets at
     index 3+ sit below row 1. The :not exemptions handle full-width
     widgets which span both columns and have their own row by
     themselves — they get a top rule too, but never a bottom one. */
  #dash-grid .dash-widget.chart-panel:nth-child(n+3) {
    border-top: 1px solid var(--border);
    padding-top: var(--space-row);
  }
  /* A full-width widget anywhere (not just below row 1) gets a top
     rule — it's the start of its own row. */
  #dash-grid .dash-widget.chart-panel.dash-widget-full {
    border-top: 1px solid var(--border);
    padding-top: var(--space-row);
  }
  /* But the very first child (if it happens to be a full-width
     widget) shouldn't have a top rule — there's nothing above it
     within the grid. */
  #dash-grid .dash-widget.chart-panel:first-child {
    border-top: none;
    padding-top: 0;
  }
}

/* ── Widget deepen affordance (Phase F4) ───────────────────────────
   Built-in widgets that map to a dedicated tab become whole-widget
   click targets. A small ↗ glyph sits at the right of the panel head;
   on hover the title and glyph brighten to signal interactivity.
   Custom widgets don't have a destination and skip this entirely
   (they don't get the .dash-widget-link class).
*/
#dash-grid .dash-widget-link {
  cursor: pointer;
  transition: opacity 0.12s;
}
#dash-grid .dash-widget-link:hover .chart-panel-title { color: var(--accent); }
#dash-grid .dash-widget-link:hover .dash-widget-deepen { color: var(--accent); }
.dash-widget-deepen {
  font-family: var(--font-ui);
  font-size: 14px;
  color: var(--text2);
  line-height: 1;
  padding: 0 4px;
  transition: color 0.12s;
}

/* ── Widget foot strip (shared) ────────────────────────────────────
   Sits below the chart canvas inside a .dash-widget panel. Holds:
     - One or more stat cells with current values, each labeled in
       its series color so the chart legend isn't needed.
     - An optional narrative sentence below the strip.
   Used by the PMC widget (Fitness / Fatigue / Form + observer
   narrative) and the Pace Trend widget (4-week average + per-run
   labels). New widgets that want this pattern can opt in by
   appending a .widget-foot with the same structure. */
.widget-foot {
  margin-top: var(--space-tight);
  display: flex;
  flex-direction: column;
  gap: var(--space-tight);
}
.widget-foot-stats {
  display: flex;
  align-items: stretch;
}
.widget-foot-stat {
  display: flex;
  flex-direction: column;
  padding: 0 14px;
  flex: 1;
  min-width: 0;
}
.widget-foot-stat:first-child { padding-left: 0; }
.widget-foot-stat:last-child  { padding-right: 0; }
.widget-foot-label {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: .12em;
  font-weight: 500;
  margin-bottom: 2px;
}
.widget-foot-value {
  font-family: var(--font-data);
  font-size: var(--fs-lg);
  font-weight: 600;
  color: var(--text);
}
/* Narrative reads as the headline insight, not a footnote. Sized
   at --fs-base (one slot above --fs-sm) so the spoon-fed sentence
   carries more weight than the raw numbers above it. */
.widget-foot-narrative {
  font-size: var(--fs-base);
  color: var(--text2);
  line-height: 1.4;
}

/* ── Pro gating: upsell pill + upgrade panel + pricing + tier badges ── */

/* Inline pill shown next to free-version chart titles. Subtle: hints
   that a fuller version exists, doesn't block content. */
.upsell-pill{
  display:inline-flex;align-items:center;gap:4px;
  background:linear-gradient(135deg,rgba(20,217,178,.15),rgba(168,85,247,.15));
  color:var(--text);
  border:1px solid rgba(20,217,178,.4);
  font-family:var(--font-ui);font-size:10px;font-weight:600;letter-spacing:.04em;
  padding:3px 9px;border-radius:999px;cursor:pointer;
  transition:transform .12s,border-color .12s,background .12s;
}
.upsell-pill:hover{
  border-color:var(--teal);
  background:linear-gradient(135deg,rgba(20,217,178,.25),rgba(168,85,247,.25));
  transform:translateY(-1px);
}

/* Full-width upgrade card — replaces the chart/section it gates.
   Used on Custom Metrics page, locked KDE/distribution panels, etc. */
.upgrade-panel{
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:10px;padding:36px 24px;text-align:center;min-height:240px;
  background:radial-gradient(circle at 50% 30%,rgba(20,217,178,.08) 0%,transparent 60%);
  border:1px dashed rgba(20,217,178,.3);
  border-radius:var(--r-lg);
}
.upgrade-panel-headline{font-family:var(--font-ui);font-size:15px;font-weight:600;color:var(--text);}
.upgrade-panel-body{font-size:12px;color:var(--muted);max-width:480px;line-height:1.6;}
.upgrade-panel-cta{margin-top:8px;padding:8px 18px;font-size:12px;}

/* Thin horizontal banner — single-line nudge to upgrade. Sits inside
   chart-panel-head's right side or as a standalone strip. Less weight
   than the full upgrade-panel; used when we still want to show useful
   free-tier content underneath. */
.upgrade-banner{
  display:flex;align-items:center;gap:10px;
  padding:8px 14px;border-radius:var(--r);
  background:linear-gradient(90deg,rgba(20,217,178,.10),rgba(168,85,247,.10));
  border:1px solid rgba(20,217,178,.28);
  font-size:11px;line-height:1.4;color:var(--text);
}
.upgrade-banner-text{flex:1;color:var(--muted);}
.upgrade-banner-text strong{color:var(--text);font-weight:600;}
.upgrade-banner-cta{
  background:var(--teal);color:var(--bg);
  font-family:var(--font-ui);font-size:10px;font-weight:700;
  letter-spacing:.04em;text-transform:uppercase;
  padding:5px 12px;border-radius:999px;border:none;cursor:pointer;
  white-space:nowrap;
  transition:transform .12s,opacity .12s;
}
.upgrade-banner-cta:hover{transform:translateY(-1px);opacity:.9;}

/* ── Upgrade notice (inline variant) ────────────────────────────────
   Single-line nudge with a link-styled CTA. Used on the Overview page
   where Phase B has stripped most other card chrome. Hosted by
   #overview-free-banner-host; the show/hide logic lives in main.js's
   refreshOverviewBanner(). The container collapses to display:none
   when the user is on Pro or billing is unavailable. */
.upgrade-inline {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 6px;
  font-size: 12px;
  line-height: 1.5;
  color: var(--muted);
  margin-bottom: var(--space-row);
  font-family: var(--font-ui);
}
.upgrade-inline-text { color: var(--muted); }
.upgrade-inline-cta {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font: inherit;
  font-weight: 600;
  color: var(--accent);
  cursor: pointer;
  /* Link-look: dotted underline keeps the affordance visible without
     looking like the bordered banner CTA. */
  text-decoration: underline;
  text-decoration-style: dotted;
  text-underline-offset: 3px;
}
.upgrade-inline-cta:hover { color: var(--accent-hi); }

/* Tier pill in footer (or anywhere). Color coded: pro=teal, free=muted. */
.tier-pill{
  display:inline-flex;align-items:center;gap:4px;
  font-family:var(--font-ui);font-size:10px;font-weight:600;letter-spacing:.06em;
  padding:3px 8px;border-radius:999px;text-transform:uppercase;
}
.tier-pill-pro{background:rgba(20,217,178,.18);color:var(--teal);border:1px solid rgba(20,217,178,.4);}
.tier-pill-free{background:var(--surface2);color:var(--muted);border:1px solid var(--border);}

/* ── Pricing tiles on /upgrade page ── */
.pricing-grid{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));
  gap:20px;margin:28px 0 32px;max-width:1100px;
}
.pricing-card{
  position:relative;background:var(--surface);border:1px solid var(--border);
  border-radius:var(--r-lg);padding:28px 24px;display:flex;flex-direction:column;
  transition:transform .15s,border-color .15s;
}
.pricing-card:hover{transform:translateY(-2px);border-color:var(--muted2);}
.pricing-card-featured{
  border-color:var(--teal);
  background:linear-gradient(180deg,var(--surface) 0%,rgba(20,217,178,.04) 100%);
}
.pricing-badge{
  position:absolute;top:-10px;right:18px;
  background:var(--teal);color:var(--bg);
  font-family:var(--font-ui);font-size:10px;font-weight:700;letter-spacing:.08em;
  padding:3px 10px;border-radius:999px;text-transform:uppercase;
}
.pricing-name{font-family:var(--font-ui);font-size:11px;color:var(--muted);
  letter-spacing:.12em;text-transform:uppercase;font-weight:600;margin-bottom:8px;}
.pricing-price{display:flex;align-items:baseline;gap:6px;margin-bottom:6px;}
.pricing-price .price-amount{font-family:var(--font-data);font-size:36px;font-weight:700;color:var(--text);line-height:1;}
.pricing-price .price-period{font-size:12px;color:var(--muted);}
.pricing-sub{font-size:11px;color:var(--muted2);margin-bottom:18px;}
.pricing-features{list-style:none;padding:0;margin:0 0 22px;display:flex;flex-direction:column;gap:8px;flex:1;}
.pricing-features li{font-size:12px;color:var(--text);line-height:1.5;padding-left:18px;position:relative;}
.pricing-features li::before{content:"✓";position:absolute;left:0;color:var(--teal);font-weight:700;}
.pricing-cta{width:100%;padding:10px;font-size:12px;}

.upgrade-status{
  margin:8px 0 0;padding:10px 14px;
  border-radius:var(--r);font-size:12px;display:none;
}
.upgrade-status.is-pro{display:block;background:rgba(20,217,178,.08);border:1px solid rgba(20,217,178,.3);color:var(--teal);}
.upgrade-status.is-cancelled{display:block;background:rgba(244,63,94,.08);border:1px solid rgba(244,63,94,.3);color:var(--rose);}

/* Locked dashboard widget overlay — visible inside the chart-body of
   gated mini-widgets so the slot still has the right layout. */
.widget-locked{
  position:absolute;inset:0;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:6px;
  background:rgba(0,0,0,.02);backdrop-filter:blur(2px);
  border-radius:var(--r);text-align:center;padding:14px;cursor:pointer;
}
.widget-locked-text{font-size:11px;color:var(--muted);max-width:200px;line-height:1.5;}

/* Toast for ?upgraded=1 / ?upgrade_cancelled=1 */
.app-toast{
  position:fixed;bottom:20px;right:20px;z-index:1000;
  background:var(--surface);border:1px solid var(--teal);
  padding:14px 18px;border-radius:var(--r);
  box-shadow:0 8px 24px rgba(0,0,0,.3);
  font-size:13px;color:var(--text);max-width:340px;
  animation:toast-slide .25s ease-out;
}
.app-toast.toast-error{border-color:var(--rose);}
@keyframes toast-slide{from{transform:translateY(20px);opacity:0;}to{transform:translateY(0);opacity:1;}}

/* ── Training sub-tabs ─────────────────────────────────────────────
   Lightweight tab row used inside the Training page to switch between
   Mileage / Pace / ROC sub-sections. Styled smaller than the main nav
   so it reads as secondary navigation. Active state uses an underline
   to match the conventional tab-row idiom. */
.subtab-bar {
  display:flex; gap:4px;
  border-bottom:1px solid var(--border);
  margin-bottom:16px;
  overflow-x:auto;  /* mobile: scroll horizontally if cramped */
}
.subtab {
  background:none; border:none; cursor:pointer;
  padding:10px 16px;
  font-family:var(--font-ui);
  font-size:12px; font-weight:500;
  color:var(--muted);
  border-bottom:2px solid transparent;
  margin-bottom:-1px;  /* overlap the bar's bottom border */
  white-space:nowrap;
  transition:color .15s, border-color .15s;
}
.subtab:hover { color:var(--text); }
.subtab.active {
  color:var(--text);
  border-bottom-color:var(--teal);
}

/* Per-sub-tab controls row. Same flex behavior as .controls-bar but
   without the heavy padding so it fits naturally below the sub-tab
   bar. Margin separates it from the bar above and content below. */
.train-sub-controls {
  display:flex; flex-wrap:wrap; gap:14px; align-items:flex-end;
  margin-bottom:16px;
}
@media(max-width:700px){
  .train-sub-controls { flex-direction:column; align-items:stretch; }
  .subtab { padding:8px 12px; font-size:11px; }
}

/* ── COMPACT STAT STRIP ───────────────────────────────────────────
   Single-row stat display used at the top of pages where plots are
   the focal point. Replaces the previous 3-hero-cards + 6-stat-grid
   pattern that was visually too heavy. Roughly 50px tall vs the
   prior ~360px combination, giving plots the page's visual focus.

   Markup:
     <div class="stat-strip">
       <div class="stat-strip-cell">
         <div class="stat-strip-label">DISTANCE</div>
         <div class="stat-strip-value">432<span class="stat-strip-unit">mi</span></div>
       </div>
       ...
     </div>

   For tooltips on individual labels, wrap label text in a span:
     <span class="stat-tip" data-tip="...">Z2 time</span>
*/
.stat-strip {
  display: flex;
  align-items: stretch;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r);
  padding: 14px 22px;
  margin-bottom: 18px;
  /* No overflow clipping — would also clip vertically (CSS quirk:
     overflow-x non-visible forces overflow-y to non-visible too) and
     hide tooltips popping above the strip. Cells with flex:1;
     min-width:0 will shrink on narrow viewports rather than scrolling. */
}

/* Overview-scoped chrome strip (Phase B-5). On the Overview page the
   .stat-strip sits beneath the THIS QUARTER eyebrow and reads as a
   visual peer to the THIS WEEK strip above. Both should look like
   typographic rows on the page, not bordered cards. Other pages
   (Race, VO2, Fatigue, Heart Rate) keep the bordered chassis. */
#page-overview .stat-strip {
  background: transparent;
  border: none;
  border-radius: 0;
  padding: 0;
  margin-bottom: var(--space-section);
}
.stat-strip-cell {
  display: flex;
  flex-direction: column;
  padding: 0 22px;
  border-right: 1px solid var(--border);
  flex: 1;
  min-width: 0;
  white-space: nowrap;
}
.stat-strip-cell:first-child { padding-left: 0; }
.stat-strip-cell:last-child  { border-right: none; padding-right: 0; }
.stat-strip-label {
  font-size: 9px;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: .12em;
  font-weight: 500;
  margin-bottom: 4px;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.stat-strip-value {
  font-family: var(--font-data);
  font-size: 18px;
  font-weight: 600;
  color: var(--text);
}
.stat-strip-unit {
  font-size: 11px;
  color: var(--muted2);
  margin-left: 3px;
  font-family: var(--font-data);
}
/* Optional second line inside a stat-strip-cell. Renders below the
   primary value with a tight 2px gap, smaller font, muted color. Used
   when a single number is the headline and a related count or context
   value belongs in the same cell (e.g., VO2 Confidence cell with the
   effective N under the High/Medium/Low label). */
.stat-strip-sub {
  font-size: var(--fs-xs);
  color: var(--muted);
  margin-top: 2px;
  font-family: var(--font-data);
}

/* ── TOOLTIP (.stat-tip) ──────────────────────────────────────────
   Visual cue (dotted underline + help cursor) is here in CSS.
   The actual hover tooltip is rendered by /js/tooltip.js — a small
   document-level layer that creates a single floating element at <body>
   root, positioned with `position: fixed` against viewport coords.

   That JS approach exists because the previous CSS-only pseudo-element
   approach hit three structural walls:
     - overflow:hidden|auto on any ancestor clipped the tooltip, regardless
       of the tooltip's own z-index. Stacking can't escape clipping.
     - chart canvases stack above stat-tip parents, hiding tooltips
       behind plots.
     - no auto-flip near viewport edges, so right-edge stats sent the
       tooltip off-screen (and vice versa for the left variant).

   The .stat-tip-left class is preserved as a HINT to the JS layer:
   when present, the tooltip prefers left-anchored placement (extending
   right from the trigger). The JS still auto-flips when needed.
*/
.stat-tip {
  position: relative;
  cursor: help;
  border-bottom: 1px dotted var(--muted);
}

/* SVGs with .stat-tip (e.g., the hero-card ring + spark) don't render
   the dotted underline meaningfully — it'd appear as a hanging line
   under the graphic. Reset border on SVG-typed triggers; the cursor:
   help still fires, and the floating tooltip layer still triggers on
   hover via event delegation. */
svg.stat-tip {
  border-bottom: none;
}

/* .stat-tip-left is a marker class read by tooltip.js. No CSS rules here;
   the class's effect is JS-driven placement. Kept as a class (not a data
   attribute) for backwards compatibility with existing markup across the
   codebase. The .confidence-pill.stat-tip override is defined earlier
   in this file (line ~123). */

/* ── Compact peregrine-block variant ──────────────────────────────
   Pro race-card range block, slimmed down. The card's headline now
   shows the median, so this just needs to convey range + half-span +
   confidence + visual dot bar. Tighter than the full peregrine-block. */
.peregrine-block-compact {
  margin-top: 8px;
  padding: 6px 10px;
}
.peregrine-range-line {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 4px;
}
.peregrine-range-text {
  font-family: var(--font-data);
  font-size: 11px;
  color: var(--text);
  font-weight: 500;
}
.peregrine-range-meta {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-data);
  font-size: 10px;
  color: var(--muted2);
}

/* ── Pro race card B3b layout ─────────────────────────────────────
   Pro tier race cards integrate uncertainty directly into the
   headline and meta line rather than a separate range panel:

     [DISTANCE]
     19:15  ±0:30                  ← headline row (median + tinted ±)
     7:09/mi · ±0:09 · Medium      ← meta line (pace + ±pace + pill)

   The ± time is tinted to the distance color at low opacity so the
   median + uncertainty pair reads as one visual unit. Free tier cards
   keep the original layout (single big median + pace). */
.race-card-headline-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  /* Match line-height behavior of standalone .race-card-time so cards
     with and without ± end at the same y-position. */
  line-height: 1;
}
/* Legacy single-pill ±. Phase 2 replaced it with the stacked-pair
   layout below (race-card-pm-stack). Class kept for any older render
   path that hasn't been migrated; safe to remove once nothing in
   pages/ references it. */
.race-card-time-pm {
  font-family: var(--font-data);
  font-size: 12px;
  font-weight: 400;
  /* Tinted to inherit the per-distance color set on the inline style
     of the parent <span>. Opacity drops it to a quiet companion. */
  opacity: .65;
  font-variant-numeric: tabular-nums;
}

/* ── Asymmetric ±1σ pill stack ──────────────────────────────────────
   Pair of stacked pills to the right of the median, centered vertically
   against it. Upper pill is "+N" (slower-side half-span); lower pill is
   "−N" (faster-side half-span). Both tinted to the per-distance color
   set on the inline style of the parent <span>. Together they describe
   the same ±1σ empirical band the Race-tab and Overview charts shade.

   Layout uses inline-flex column with the median-row's align-items:
   baseline overridden locally via center via translateY on the parent
   span. We avoid that complexity by setting the headline row to align
   items: center on cards that use the stack, and keeping the same row
   for legacy single-pill cards via the .race-card-time line-height: 1
   rule above.
*/
.race-card-headline-row:has(.race-card-pm-stack) {
  align-items: center;
}
.race-card-pm-stack {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 1px;
  font-family: var(--font-data);
  font-size: var(--fs-xs);
  font-weight: 400;
  opacity: .7;
  font-variant-numeric: tabular-nums;
  line-height: 1.15;
}
.race-card-pm-up,
.race-card-pm-down {
  display: block;
}
.race-card-meta-line {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 6px;
  font-family: var(--font-data);
  font-size: 11px;
  color: var(--muted2);
  font-variant-numeric: tabular-nums;
  line-height: 1.4;
}
.race-card-meta-line .sep {
  color: var(--border2);
  margin: 0 2px;
}

/* ── Hero card (Phase 1 consolidation) ─────────────────────────────
   Replaces the trio of today-card / tomorrow-card / last-run-card with
   one card, one chrome, three slots: headline area, hero column,
   footer line. Mode picked by hero-card.js based on run freshness +
   readiness status.

   Layout (pre-run mode, desktop):

     [eyebrow — TODAY, accent-colored]
     [headline                       | streak ring + score + spark]
     [context line (optional)        | SCORE label              ]
     [goal line (optional)           |                          ]
     [delta callout (optional)       |                          ]
     [footer line — tomorrow plan, divider above              ]
     [?] (top-right)

   Post-run mode swaps the headline for the run finding + stats, and
   the hero column shows the run distance + pace instead of the score.

   Tone is set by --tone (cascaded from .t-* class). Pre-run uses
   t-readiness-{green,amber,rose}; post-run uses t-run-{pr,season,
   better,streak,fallback}; cold-start and loading are muted via
   --border2.

   Mobile chrome (≤768px) is overridden in mobile.css: borders +
   background drop, leaving only the left tone stripe and a hairline
   divider. Desktop keeps full card chrome.
*/
.hero-card {
  --tone: var(--border2);
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 4px solid var(--tone);
  border-radius: var(--r);
  padding: 24px 26px;
  margin-bottom: 24px;
  position: relative;
}
.hero-card:empty { display: none; }

/* Pre-run tone variants (today-card lineage). */
.hero-card.t-pr            { --tone: var(--accent); }
.hero-card.t-goal-ahead    { --tone: var(--accent); }
.hero-card.t-goal-near     { --tone: var(--text2); }
.hero-card.t-goal-stretch  { --tone: var(--gold); }
.hero-card.t-goal-behind   { --tone: var(--rose); }
.hero-card.t-goal-met      { --tone: var(--teal); }
.hero-card.t-goal-missed   { --tone: var(--muted2); }
.hero-card.t-tsb-fresh     { --tone: var(--gold); }
.hero-card.t-tsb-fatigued  { --tone: var(--rose); }
.hero-card.t-sustained     { --tone: var(--teal); }
.hero-card.t-fallback      { --tone: var(--border2); }
.hero-card.t-loading       { --tone: var(--border2); }
.hero-card.t-building      { --tone: var(--border2); }
.hero-card.t-readiness-green { --tone: var(--status-ready); }
.hero-card.t-readiness-amber { --tone: var(--status-caution); }
.hero-card.t-readiness-rose  { --tone: var(--status-strain); }

/* Post-run tone variants (last-run-card lineage). */
.hero-card.t-run-pr        { --tone: var(--accent); }
.hero-card.t-run-season    { --tone: var(--teal); }
.hero-card.t-run-better    { --tone: var(--lime); }
.hero-card.t-run-streak    { --tone: var(--gold); }
.hero-card.t-run-fallback  { --tone: var(--border2); }

/* Loading + building eyebrows are muted (no signal to amplify). */
.hero-card.t-loading .hero-card-eyebrow,
.hero-card.t-building .hero-card-eyebrow,
.hero-card.t-fallback .hero-card-eyebrow,
.hero-card.t-run-fallback .hero-card-eyebrow {
  color: var(--muted);
}
.hero-card-headline-pending {
  color: var(--muted);
  font-weight: 500;
}

.hero-card-eyebrow {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--tone);
  margin-bottom: 10px;
  font-weight: 600;
  font-family: var(--font-ui);
}

.hero-card-body {
  display: flex;
  align-items: center;
  gap: 20px;
}
.hero-card-text {
  flex: 1 1 auto;
  min-width: 0;
}
.hero-card-headline {
  font-size: var(--fs-lg);
  font-weight: 500;
  color: var(--text);
  line-height: 1.35;
  margin-bottom: 6px;
}
.hero-card-context {
  font-size: var(--fs-sm);
  color: var(--text2);
  line-height: 1.5;
}
/* #3: pre-run weather/timing window. Reads as an actionable heads-up;
   collapses when there's no heat clause. */
.hero-card-weather {
  font-size: var(--fs-sm);
  color: var(--text2);
  line-height: 1.5;
  margin-top: 2px;
}
.hero-card-weather:empty { display: none; }
.hero-card-goal {
  font-size: var(--fs-sm);
  color: var(--muted);
  line-height: 1.5;
  margin-top: 2px;
}
/* Goal-probability headline ("62% chance of a sub-3:45 marathon — up 14
   pts in 8 weeks"). The probability is the lead metaphor, so it reads a
   touch stronger than the gap line above it. Collapses when empty. */
.hero-card-goal-prob {
  font-size: var(--fs-sm);
  color: var(--text2);
  font-weight: 600;
  line-height: 1.5;
  margin-top: 1px;
}
.hero-card-goal-prob:empty { display: none; }
.hero-card-delta {
  display: flex;
  align-items: baseline;
  gap: 6px;
  font-size: var(--fs-sm);
  color: var(--text2);
  line-height: 1.5;
  margin-top: 2px;
}
.hero-card-delta-arrow {
  font-size: var(--fs-xs);
  color: var(--muted);
  position: relative;
  top: -1px;
}
.hero-card-delta.good .hero-card-delta-arrow {
  color: var(--completed);
}

/* QW6: "what this run moved" row in post-run mode (Fitness + Form). */
.hero-card-deltas {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px;
  font-size: var(--fs-sm);
  color: var(--text2);
  line-height: 1.5;
  margin-top: 3px;
}
.hero-card-deltas:empty { display: none; }
.hero-card-delta-stat { white-space: nowrap; }
.hero-card-delta-stat.good { color: var(--status-ready); font-weight: 600; }
.hero-card-delta-stat.down { color: var(--status-strain); font-weight: 600; }
.hero-card-delta-sep { color: var(--muted); }
.hero-card-deltas .hero-card-delta-arrow {
  font-size: var(--fs-xs);
  position: relative;
  top: -1px;
}

/* QW20: visually group This Week + Today's Number as one momentum block. */
.ov-week-group { margin-bottom: var(--space-row); }

.hero-card-hero {
  flex: 0 0 auto;
  text-align: right;
  position: relative;
  /* Reserves space for the absolutely-positioned .hero-card-ring on
     the right. Geometry: ring is 64px wide, anchored right:4px, so
     the ring occupies right-edge → 68px-from-right. Add 8px of
     breathing room between the ring's left edge and the score text,
     for 76px total. */
  padding-right: 76px;
}
.hero-card-hero-value {
  font-family: var(--font-data);
  font-size: var(--fs-lg);
  font-weight: 600;
  line-height: 1;
  color: var(--tone);
  letter-spacing: -0.02em;
}
.hero-card-hero-denom {
  font-size: var(--fs-xs);
  font-weight: 500;
  color: var(--muted);
  letter-spacing: 0;
  margin-left: 1px;
}
.hero-card-hero-unit {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  margin-top: 4px;
  font-family: var(--font-ui);
}

/* Post-run hero column: big run distance + pace. */
.hero-card-run-distance {
  font-family: var(--font-data);
  font-size: var(--fs-xl);
  font-weight: 600;
  color: var(--text);
  line-height: 1;
  letter-spacing: -0.02em;
}
.hero-card-run-pace {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  margin-top: 6px;
  font-family: var(--font-ui);
}

/* Action cluster (info button, share button) sits absolute top-right. */
.hero-card-actions {
  position: absolute;
  top: 14px;
  right: 16px;
  display: flex;
  gap: 8px;
  align-items: center;
}
.hero-card-info {
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 2px 8px;
  font-size: var(--fs-xs);
  color: var(--muted);
  cursor: help;
  font-family: var(--font-ui);
}
.hero-card-info:hover {
  color: var(--text);
  border-color: var(--border2);
}
.hero-card-share {
  background: var(--accent);
  color: var(--bg);
  border: 1px solid var(--accent);
  border-radius: 4px;
  padding: 4px 10px;
  font-size: var(--fs-xs);
  font-weight: 500;
  cursor: pointer;
  font-family: var(--font-ui);
}
.hero-card-share:hover {
  background: var(--accent-hi);
}

/* Footer line. Single muted line below the body row, divider above. */
.hero-card-footer {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
  font-size: var(--fs-sm);
  color: var(--text2);
  line-height: 1.5;
}
.hero-card-footer:empty { display: none; }

/* ── Readiness spark (14-day score history) ─────────────────────────
   Inline SVG polyline below the hero score. Uses currentColor via
   --tone so it matches the card's accent. The endpoint dot pulses
   the same tone color so the eye knows where "now" sits on the line.

   y-axis is fixed at 0-100 (set in composeReadinessSparkPoints) so
   shape is absolute, not normalized — a flat 90 spark visibly
   differs from a flat 30 spark. */
.hero-card-spark {
  margin-top: 8px;
  display: block;
  height: 20px;
  width: 80px;
  margin-left: auto;
}
.hero-card-spark-line {
  fill: none;
  stroke: var(--tone);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.85;
}
.hero-card-spark-dot {
  fill: var(--tone);
}

/* ── Streak ring (ACWR-safe weekly streak) ──────────────────────────
   Conic-style progress arc around the readiness score. Two circles:
   a muted track (full circumference) and a colored arc (proportional
   fill to streak_weeks / 12).

   Positioned absolute inside .hero-card-hero so it surrounds the
   score number. On mobile the layout flips to stacked and the ring
   anchors top-left next to the score.

   Status variants: on_pace uses --tone; slipping mutes the arc;
   no_data hides it entirely (component returns null in that case
   so this rule is defensive). */
.hero-card-ring {
  position: absolute;
  top: 50%;
  right: 4px;
  transform: translateY(-50%);
  width: 64px;
  height: 64px;
  /* No pointer-events:none — we want the ring to accept hover for the
     .stat-tip floating tooltip. Earlier versions of this rule had
     pointer-events:none to avoid the ring intercepting clicks on
     overlapping score text; the hero-column padding-right was bumped
     to 76px so there's no overlap now, and the ring is purely a hover
     target for its identifying tooltip. */
  opacity: 0.7;
}
.hero-card-ring-track {
  fill: none;
  stroke: var(--border);
  stroke-width: 2;
}
.hero-card-ring-arc {
  fill: none;
  stroke: var(--tone);
  stroke-width: 2;
  stroke-linecap: round;
  transform: rotate(-90deg);
  transform-origin: center;
  transition: stroke-dashoffset 0.4s ease-out;
}
.hero-card-ring-arc.slipping {
  stroke: var(--muted);
}


/* ── Mobile swipe strip (Phase 2) ───────────────────────────────────
   Horizontal snap-scroll strip on mobile. Replaces the vertical stack
   of This Week + Today's Number + What's New + Weekly Digest. Each
   card is independently composed; the strip just lays them out and
   handles scroll behavior.

   Desktop (≥769px) hides this host entirely (via the default rule
   below); the legacy vertical-stack hosts (still painted) carry the
   same data. Mobile (≤768px) reveals this host (rule in mobile.css)
   and hides the vertical-stack hosts.
*/
.strip-cards-host {
  display: none;
}
.strip-cards-host:empty {
  display: none !important;
}

.strip-cards-row {
  display: flex;
  gap: 12px;
  overflow-x: auto;
  /* Scroll-snap. `proximity` instead of `mandatory` lets short swipes
     drift naturally; mandatory was forcing a hard snap on every
     release which read as the page "fighting" the user's gesture.
     proximity only snaps when the gesture lands near a snap point —
     mid-card pauses settle wherever they end up. */
  scroll-snap-type: x proximity;
  /* Respect the row's left/right padding for snap geometry, so a
     snapped card sits 16px from the viewport edge rather than flush
     against it. */
  scroll-padding-inline: 16px;
  /* Smooth scroll for programmatic + snap-correction motions. The
     finger drag itself ignores this; only the inertial settle uses
     it. */
  scroll-behavior: smooth;
  /* Contain horizontal overscroll so an edge swipe doesn't bubble up
     into iOS's back-gesture or the page's own horizontal pan. The
     strip "owns" its axis. */
  overscroll-behavior-x: contain;
  /* Hint the browser that this is a horizontal pan surface so it
     resolves the touch axis immediately instead of waiting to see if
     the gesture turns vertical. Removes the brief lag at the start
     of a swipe. */
  touch-action: pan-x;
  -webkit-overflow-scrolling: touch;
  padding: 4px 16px 16px;
  /* GPU-composite the scroll port so card paints don't block the
     scroll thread on mid-range Android. Pair with will-change on
     each card. */
  contain: paint;
  /* The negative left/right margin offsets the page-inner padding so
     the strip can flush against the viewport edges. The first card's
     padding-left at the start of the row keeps it off the edge. */
  margin: 0 -12px 16px;
  scrollbar-width: none;
}
.strip-cards-row::-webkit-scrollbar { display: none; }

.strip-card {
  /* Each card sized so ~1.5 cards are visible at 375px viewport.
     78vw works at 375 (~292px wide); 240px min keeps narrower phones
     from cramping. The next card peeks to telegraph swipeability. */
  flex: 0 0 78vw;
  min-width: 240px;
  max-width: 320px;
  scroll-snap-align: start;
  /* Tells the browser the snap position can't be skipped during a
     fast fling — prevents the user from blowing past the last card
     into nothingness. */
  scroll-snap-stop: always;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r);
  padding: 16px;
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 8px;
  font-family: var(--font-ui);
  /* Hint the compositor that cards may transform during scroll. The
     scroll port itself doesn't transform, but `will-change: transform`
     on each card promotes it to its own layer, which keeps card
     painting off the scroll thread. */
  will-change: transform;
  /* Defensive: don't tap-highlight the entire card on touch. The
     interactive bits (Share / Dismiss / overflow tap) handle their
     own feedback. */
  -webkit-tap-highlight-color: transparent;
}

.strip-card-eyebrow {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  font-weight: 600;
}

/* This-week card body. */
.strip-card-this-week .strip-card-value-row {
  display: flex;
  align-items: baseline;
  gap: 4px;
}
.strip-card-this-week .strip-card-value {
  font-family: var(--font-data);
  font-size: var(--fs-lg);
  font-weight: 600;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}
.strip-card-this-week .strip-card-unit {
  font-size: var(--fs-xs);
  color: var(--muted2);
}
.strip-card-this-week .strip-card-sub {
  font-size: var(--fs-xs);
  color: var(--muted2);
  line-height: 1.4;
}

/* Today's-number card. */
.strip-card-todays-number .strip-card-headline {
  font-size: var(--fs-base);
  color: var(--text);
  line-height: 1.4;
}
.strip-card-todays-number .strip-card-subline {
  font-size: var(--fs-sm);
  color: var(--text2);
  line-height: 1.5;
}

/* Finding card. Eyebrow row carries the category dot inline. */
.strip-card-finding {
  --dot-color: var(--border2);
}
.strip-card-finding[data-cat="race"]        { --dot-color: var(--accent); }
.strip-card-finding[data-cat="pace"]        { --dot-color: var(--lime); }
.strip-card-finding[data-cat="volume"]      { --dot-color: var(--gold); }
.strip-card-finding[data-cat="workout"]     { --dot-color: var(--violet); }
.strip-card-finding[data-cat="fitness"]     { --dot-color: var(--teal); }
.strip-card-finding[data-cat="consistency"] { --dot-color: var(--sky); }

.strip-card-finding .strip-card-eyebrow-row {
  display: flex;
  align-items: center;
  gap: 8px;
}
.strip-card-finding .strip-card-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--dot-color);
  flex-shrink: 0;
}
.strip-card-finding .strip-card-sentence {
  font-size: var(--fs-sm);
  color: var(--text);
  line-height: 1.5;
}

/* Digest card. */
.strip-card-digest .strip-card-stats {
  font-size: var(--fs-sm);
  color: var(--text);
  font-weight: 500;
}
.strip-card-digest .strip-card-insight {
  font-size: var(--fs-sm);
  color: var(--text2);
  line-height: 1.5;
}

/* Action row inside any card (digest, finding share, etc.) */
.strip-card-actions {
  display: flex;
  gap: 6px;
  margin-top: auto;
}

/* Overflow card. Visually distinct from data cards — dashed border,
   centered label. Tap-target for the bottom-sheet modal. */
.strip-card-overflow {
  align-items: center;
  justify-content: center;
  text-align: center;
  cursor: pointer;
  border-style: dashed;
  background: transparent;
}
.strip-card-overflow:hover,
.strip-card-overflow:focus {
  border-color: var(--accent);
  outline: none;
}
.strip-card-overflow .strip-card-label {
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--accent);
}
.strip-card-overflow .strip-card-count {
  font-size: var(--fs-xs);
  color: var(--muted);
}

/* Share / Dismiss buttons inside a strip card. */
.strip-card-btn {
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 4px 10px;
  font-size: var(--fs-xs);
  color: var(--text2);
  cursor: pointer;
  font-family: var(--font-ui);
}
.strip-card-btn:hover {
  border-color: var(--border2);
  color: var(--text);
}
.strip-card-btn-primary {
  background: var(--accent);
  color: var(--bg);
  border-color: var(--accent);
}
.strip-card-btn-primary:hover {
  background: var(--accent-hi);
  border-color: var(--accent-hi);
}


/* ── Pull-to-refresh indicator (Phase 2) ───────────────────────────
   Small circular spinner that translates down with the drag gesture.
   Lives inside #page-overview; positioned absolute relative to that
   container so it sits above the page content. Hidden by default
   (opacity 0); revealed when drag distance exceeds the visibility
   threshold (~8px, handled in JS via inline transform).

   The spinner uses currentColor on a partial stroke-dasharray so the
   ring animates in two states: muted while armed-pending, accent
   color + rotation while spinning.
*/
.strip-pull-indicator {
  position: absolute;
  top: -56px;
  left: 50%;
  transform: translate(-50%, 0);
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--surface);
  border: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  opacity: 0;
  z-index: 100;
}
.strip-pull-indicator.strip-pull-armed {
  border-color: var(--accent);
}
.strip-pull-spinner {
  width: 24px;
  height: 24px;
  color: var(--muted);
}
.strip-pull-spinner circle {
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-dasharray: 56;
  stroke-dashoffset: 28;
}
.strip-pull-indicator.strip-pull-armed .strip-pull-spinner {
  color: var(--accent);
}
.strip-pull-indicator.strip-pull-spinning .strip-pull-spinner {
  color: var(--accent);
  animation: strip-pull-spin 0.8s linear infinite;
}
@keyframes strip-pull-spin {
  to { transform: rotate(360deg); }
}


/* ── Strip overflow bottom-sheet modal (Phase 2) ───────────────────
   Slides up from the bottom of the viewport when the overflow card
   is tapped. Lists every finding in significance order. Mobile-first;
   functions on desktop too but the strip itself is mobile-only so
   desktop users won't reach it via normal interaction. */
.strip-overflow-modal {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 1000;
  align-items: flex-end;
  justify-content: center;
}
.strip-overflow-modal.open {
  display: flex;
}
body.strip-overflow-open {
  overflow: hidden;
}

.strip-overflow-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
}
.strip-overflow-sheet {
  position: relative;
  background: var(--surface);
  width: 100%;
  max-width: 560px;
  max-height: 80vh;
  border-top-left-radius: 16px;
  border-top-right-radius: 16px;
  padding: 16px 16px max(20px, env(safe-area-inset-bottom));
  overflow-y: auto;
  animation: strip-overflow-rise 0.22s cubic-bezier(0.4, 0, 0.2, 1);
  font-family: var(--font-ui);
}
@keyframes strip-overflow-rise {
  from { transform: translateY(100%); }
  to   { transform: translateY(0); }
}
.strip-overflow-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 16px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border);
}
.strip-overflow-title {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  font-weight: 600;
}
.strip-overflow-close {
  background: transparent;
  border: none;
  font-size: 24px;
  line-height: 1;
  color: var(--muted);
  cursor: pointer;
  padding: 4px 8px;
}
.strip-overflow-close:hover {
  color: var(--text);
}
.strip-overflow-list {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.strip-overflow-item {
  display: flex;
  align-items: baseline;
  gap: 12px;
  font-size: var(--fs-sm);
  color: var(--text);
  line-height: 1.5;
}
.strip-overflow-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
  background: var(--border2);
  align-self: center;
}
.strip-overflow-dot[data-cat="race"]        { background: var(--accent); }
.strip-overflow-dot[data-cat="pace"]        { background: var(--lime); }
.strip-overflow-dot[data-cat="volume"]      { background: var(--gold); }
.strip-overflow-dot[data-cat="workout"]     { background: var(--violet); }
.strip-overflow-dot[data-cat="fitness"]     { background: var(--teal); }
.strip-overflow-dot[data-cat="consistency"] { background: var(--sky); }
.strip-overflow-text {
  flex: 1 1 auto;
}
.strip-overflow-share {
  background: transparent;
  border: none;
  color: var(--muted);
  font-size: var(--fs-base);
  cursor: pointer;
  padding: 2px 6px;
  border-radius: 4px;
}
.strip-overflow-share:hover {
  color: var(--accent);
  background: var(--surface2);
}
.strip-overflow-empty {
  font-size: var(--fs-sm);
  color: var(--muted);
  text-align: center;
  padding: 32px 16px;
}


/* ── Page insight line ──────────────────────────────────────────────
   A single-sentence narration populated by builders in insights.js.
   Sits under each page's section-head, before stat strips and charts.
   Distinct from the Today's Read card — this narrates the page,
   doesn't surface a single notable observation across athlete state.

   No card chrome, no info button, no border, no background. Just a
   sentence. Numbers in <strong> get the regular text color so they
   pop against the muted body text without needing a tone palette.

   :empty collapses cleanly when the builder returns null (insufficient
   data, free-tier branches that don't apply, etc.) so there's no
   placeholder flicker on first paint or after a re-render.
*/
.page-insight {
  font-size: 13px;
  color: var(--text2);
  line-height: 1.55;
  margin: 4px 0 16px;
  padding: 0 2px;
}
.page-insight:empty { display: none; }
.page-insight strong {
  color: var(--text);
  font-weight: 600;
}

/* Race page plain-language headline summary (declutter). Leads the page
   with "Marathon ≈ 3:45 · typical range 3:40–3:52" before any modeling
   detail. Collapses when empty (pre-load / no prediction). */
.race-headline-summary {
  margin: 2px 0 14px;
  padding: 0 2px;
  line-height: 1.5;
}
.race-headline-summary:empty { display: none; }
.race-headline-label {
  font-weight: 600;
  color: var(--text);
}
.race-headline-time {
  font-weight: 700;
  color: var(--text);
}
.race-headline-range,
.race-headline-conf {
  color: var(--text2);
  font-size: 13px;
}

/* ── Pace mode caption ──────────────────────────────────────────────
   Sits below the time-series chart on the Pace sub-section when the
   user has selected a heat-adjusted mode (HAP/AP) and at least one
   run in the visible window lacks weather data. Reports the
   adjusted-vs-total count so the user understands the chart includes
   raw-pace fallbacks for treadmill / no-GPS / pre-resync runs. The
   raw-pace points are visually muted in the chart datasets; this
   caption gives the count.
*/
.pace-mode-caption {
  padding: 0 20px 14px;
  font-size: 11px;
  color: var(--muted);
  font-style: italic;
  line-height: 1.45;
}
.pace-mode-caption strong {
  color: var(--text2);
  font-weight: 600;
  font-style: normal;
}

/* ── Race-tab pace-distribution summary strip ──────────────────────
   One-line summary sits below the pace-distribution-by-race-distance
   chart. Default state describes the spread across distances ("Marathon
   band is widest. 5K is tightest from recent quality."). On hover of a
   specific curve, the strip swaps to that distance's per-factor context
   ("Marathon: low confidence. Spread driven by longest 14 mi (target
   20)."). The chart's onHover handler writes textContent; the strip
   is always present in the DOM with the default summary on render.

   No bullets, no chrome. Muted color so the chart remains primary; the
   strip just gives a reader who isn't hovering an immediate "why."
*/
.race-pace-dist-summary {
  padding: 8px 20px 16px;
  font-size: var(--fs-xs);
  color: var(--muted);
  line-height: 1.5;
  min-height: 1.5em;
}

/* ── Chart caption ─────────────────────────────────────────────────
   A single short line that sits below a chart and narrates one thing
   about it: a delta vs a baseline, a projected category change, a
   YoY total. Sibling to chart-panel internals, not a chart-panel
   itself. Kept muted so the chart remains the focus; the caption
   adds a number, not a heading.

   Used by:
   - VO2 gauge: next-category projection
   - (future) Mileage YoY caption, goal-probability caption
*/
.chart-caption {
  padding: 6px 20px 12px;
  font-size: 11px;
  color: var(--muted);
  line-height: 1.45;
  text-align: center;
}
.chart-caption strong {
  color: var(--text2);
  font-weight: 600;
}

/* ── TRIMP-type legend ────────────────────────────────────────────
   Compact swatch row below the Daily TRIMP chart on the Fatigue
   page. Each bar in the chart is colored by its day's workout type
   (easy / long / tempo / workout / race / recovery); the legend
   names the colors. Sits inside the chart-panel below the canvas,
   same pattern as the ACWR legend on Training.
*/
.trimp-type-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  padding: 4px 20px 16px;
  font-size: 11px;
  color: var(--muted);
}
.trimp-type-legend .swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 2px;
  margin-right: 6px;
  vertical-align: middle;
}

/* ── Pill-toggle (segmented control) ─────────────────────────────────
   Used in the Pace Distribution chart panel head to switch between
   the per-zone / single / recent-vs-prior view modes. Three buttons
   in a row, the active one highlighted. Reuses the existing pill
   visual language (small, pill-shaped, low-noise) so it doesn't
   feel like a foreign control.

   The hidden <select> sibling carries the actual value; clicks on
   the buttons mirror into the select via a listener in main.js.
*/
.pill-toggle {
  display: inline-flex;
  gap: 0;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 2px;
  align-items: center;
  flex-wrap: wrap;
}
.pill-toggle-btn {
  background: transparent;
  border: none;
  border-radius: 999px;
  padding: 4px 12px;
  font-size: 11px;
  font-weight: 500;
  color: var(--muted);
  cursor: pointer;
  font-family: var(--font-ui);
  white-space: nowrap;
  transition: background 0.15s, color 0.15s;
}
.pill-toggle-btn:hover {
  color: var(--text2);
}
.pill-toggle-btn.is-active {
  background: var(--surface);
  color: var(--text);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
}
@media (max-width: 480px) {
  /* On narrow screens the chart panel head wraps; keep the toggle
     readable by allowing the buttons to break onto a second row. */
  .pill-toggle { width: 100%; justify-content: stretch; }
  .pill-toggle-btn { flex: 1; text-align: center; }
}

/* ── PR share modal ─────────────────────────────────────────────────
   Triggered from the today's card. Floating modal with image preview
   and download button. Uses position:fixed for the overlay since
   the modal needs to escape page scroll.
*/
.pr-card-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,.6);
  z-index: 9000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.pr-card-modal {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  max-width: 480px;
  width: 100%;
  display: flex;
  flex-direction: column;
}
.pr-card-modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px;
  border-bottom: 1px solid var(--border);
}
.pr-card-modal-title {
  font-size: 16px;
  font-weight: 500;
  color: var(--text);
}
.pr-card-modal-close {
  background: none;
  border: none;
  color: var(--muted);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  padding: 0 4px;
  font-family: var(--font-ui);
}
.pr-card-modal-close:hover { color: var(--text); }
.pr-card-modal-body { padding: 16px 20px; }
.pr-card-modal-hint {
  font-size: 13px;
  color: var(--text2);
  margin: 0 0 16px;
  line-height: 1.5;
}
.pr-card-preview-wrap {
  background: var(--bg2);
  border-radius: var(--r);
  padding: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 240px;
}
.pr-card-preview {
  width: 100%;
  max-width: 320px;
  height: auto;
  border-radius: var(--r);
}
.pr-card-preview-loading {
  font-size: 13px;
  color: var(--muted);
}
.pr-card-modal-foot {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  padding: 12px 20px 16px;
  border-top: 1px solid var(--border);
}
.pr-card-cancel,
.pr-card-download {
  font-size: 13px;
  padding: 8px 14px;
  border-radius: var(--r);
  cursor: pointer;
  font-family: var(--font-ui);
  border: 1px solid var(--border2);
}
.pr-card-cancel {
  background: transparent;
  color: var(--text2);
}
.pr-card-cancel:hover { color: var(--text); }
.pr-card-download {
  background: var(--accent);
  color: var(--bg);
  border-color: var(--accent);
  font-weight: 500;
}
.pr-card-download:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.pr-card-download:not(:disabled):hover {
  background: var(--accent-hi);
}

/* ── Race-time goal slot on the Race page ──────────────────────────
   Lives between the section head and the stat strip. Displays either
   a "Set a goal" CTA or an active-goal cell with gap-to-target.
*/
.set-goal-prompt {
  background: transparent;
  border: 1px dashed var(--border2);
  border-radius: var(--r);
  padding: 12px 14px;
  font-size: 12px;
  color: var(--text2);
  cursor: pointer;
  margin-bottom: 14px;
  font-family: var(--font-ui);
  transition: border-color 0.15s, color 0.15s;
}
.set-goal-prompt:hover {
  border-color: var(--accent);
  color: var(--text);
}

.goal-cell {
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--r);
  padding: 12px 16px;
  margin-bottom: 14px;
}
.goal-cell-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}
.goal-cell-main { flex: 1; min-width: 0; }
.goal-cell-label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
  font-weight: 500;
  margin-bottom: 4px;
  font-family: var(--font-ui);
}
.goal-cell-value {
  font-size: 22px;
  color: var(--text);
  font-family: var(--font-data, var(--font-ui));
  font-weight: 500;
  line-height: 1;
}
.goal-cell-unit {
  font-size: 11px;
  color: var(--muted);
  font-weight: 400;
  margin-left: 10px;
  letter-spacing: 0;
  text-transform: none;
  font-family: var(--font-ui);
}
.goal-cell-edit {
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 4px 10px;
  font-size: 11px;
  color: var(--muted);
  cursor: pointer;
  font-family: var(--font-ui);
}
.goal-cell-edit:hover {
  color: var(--text);
  border-color: var(--border2);
}
.goal-gap-line {
  font-size: 12px;
  margin-top: 8px;
  color: var(--text2);
  font-family: var(--font-ui);
  letter-spacing: 0;
  text-transform: none;
  font-weight: 400;
  line-height: 1.4;
}
.goal-gap-line.goal-gap-within_reach { color: var(--ctl, var(--teal)); }
.goal-gap-line.goal-gap-stretch       { color: var(--gold, #d4a017); }
.goal-gap-line.goal-gap-reach         { color: var(--text2); }

/* ── Goal modal (set / edit) ─────────────────────────────────────── */
.goal-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,.6);
  z-index: 9000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.goal-modal {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  max-width: 480px;
  width: 100%;
  display: flex;
  flex-direction: column;
}
.goal-modal-head, .goal-modal-foot {
  padding: 16px 20px;
  display: flex;
  align-items: center;
  gap: 8px;
}
.goal-modal-head {
  border-bottom: 1px solid var(--border);
  justify-content: space-between;
}
.goal-modal-foot {
  border-top: 1px solid var(--border);
  justify-content: flex-end;
}
.goal-modal-title {
  font-size: 16px;
  font-weight: 500;
  color: var(--text);
}
.goal-modal-close {
  background: none;
  border: none;
  color: var(--muted);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  padding: 0 4px;
  font-family: var(--font-ui);
}
.goal-modal-close:hover { color: var(--text); }
.goal-modal-body { padding: 16px 20px; }

.goal-form-row {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 14px;
  font-family: var(--font-ui);
}
.goal-form-label {
  font-size: 13px;
  color: var(--text2);
  width: 100px;
  flex-shrink: 0;
}
.goal-form-row select,
.goal-form-row input[type="date"] {
  flex: 1;
  padding: 6px 8px;
  background: var(--bg2);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text);
  font-family: var(--font-ui);
  font-size: 13px;
}
.goal-time-inputs {
  display: flex;
  align-items: center;
  gap: 6px;
}
.goal-time-inputs input {
  width: 56px;
  text-align: center;
  padding: 6px;
  background: var(--bg2);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text);
  font-family: var(--font-data, var(--font-ui));
  font-size: 14px;
}
.goal-time-inputs span { color: var(--muted); }
.goal-suggestion-row {
  display: flex;
  gap: 8px;
  margin: -8px 0 16px 112px;
  flex-wrap: wrap;
}
.goal-suggestion-btn {
  background: transparent;
  border: 1px dashed var(--border2);
  color: var(--text2);
  border-radius: 4px;
  padding: 5px 10px;
  font-size: 11px;
  cursor: pointer;
  font-family: var(--font-ui);
}
.goal-suggestion-btn:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.goal-modal-error {
  font-size: 12px;
  color: var(--rose);
  margin-top: 8px;
  font-family: var(--font-ui);
}

.goal-cancel,
.goal-save,
.goal-clear {
  font-size: 13px;
  padding: 8px 14px;
  border-radius: var(--r);
  cursor: pointer;
  font-family: var(--font-ui);
  border: 1px solid var(--border2);
}
.goal-cancel { background: transparent; color: var(--text2); }
.goal-cancel:hover { color: var(--text); }
.goal-clear {
  background: transparent;
  color: var(--rose);
  border-color: var(--border);
  margin-right: auto;  /* pushes Cancel/Save to the right */
}
.goal-clear:hover { border-color: var(--rose); }
.goal-save {
  background: var(--accent);
  color: var(--bg);
  border-color: var(--accent);
  font-weight: 500;
}
.goal-save:hover { background: var(--accent-hi, var(--accent)); }

/* ── Section eyebrow ────────────────────────────────────────────────
   Single shared style used by every Overview section heading
   (THIS WEEK, THIS QUARTER, FITNESS TRAJECTORY, YOUR DASHBOARD).
   Adding a new section? Apply class="section-eyebrow" to its caps
   label and you're done — no per-section rule needed.
*/
.section-eyebrow {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--muted);
  font-weight: 600;
  font-family: var(--font-ui);
  margin-bottom: var(--space-tight);
}

/* ── THIS WEEK strip ────────────────────────────────────────────────
   Trailing-7-day momentum row. No card chrome — typographic strip on
   the page, separated from neighbors by spacing only. Four cells:
   distance, runs, time, streak. Each cell is a primary value + a
   sub-line showing the delta vs the prior 7 days (or, for streak,
   the gap to the user's all-time best).

   Colors of the deltas:
     up   → --lime  (more volume, longer streak — generally good)
     down → --muted (less volume isn't bad — tapers, recovery)
     same → --muted2

   :empty collapses the host when the user has no runs yet.
*/
.this-week:empty { display: none; }
.this-week {
  margin-bottom: var(--space-section);
}
.this-week-row {
  display: flex;
  align-items: flex-start;
  gap: 0;
}
.this-week-cell {
  flex: 1;
  min-width: 0;
  padding-right: 18px;
}
.this-week-cell:last-child { padding-right: 0; }
.this-week-value {
  font-family: var(--font-data);
  font-size: 22px;
  font-weight: 600;
  color: var(--text);
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}
.this-week-unit {
  font-size: 11px;
  color: var(--muted2);
  margin-left: 4px;
  font-weight: 500;
}
.this-week-sub {
  font-size: 11px;
  color: var(--muted2);
  margin-top: 4px;
  line-height: 1.4;
  font-family: var(--font-ui);
}
.this-week-sub-muted { color: var(--muted2); }
.this-week-delta-up   { color: var(--lime); font-weight: 500; }
.this-week-delta-down { color: var(--muted); font-weight: 500; }
.this-week-delta-same { color: var(--muted2); }

@media (max-width: 600px) {
  /* Two-up wrap on narrow screens. Use a grid for clean alignment;
     flex-wrap leaves uneven row heights at this size. */
  .this-week-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 14px 12px;
  }
  .this-week-cell { padding-right: 0; }
  .this-week-value { font-size: 20px; }
}

/* ── Activity Log year-to-date strip (F8) ──────────────────────────
   Two-cell typographic strip at the top of the Log page below the
   section head. No card chrome — just label + value, hairlines below.
   The cells hold the year totals that used to sit in the Overview
   sidebar; they earn space here on the historical-record page.
*/
.log-year-strip:empty { display: none; }
.log-year-strip {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 24px;
  margin: 0 0 var(--space-section);
  padding: var(--space-tight) 0;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}
.log-year-cell {
  display: flex;
  flex-direction: column;
  gap: 4px;
  cursor: default;
}
.log-year-label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--muted);
  font-weight: 600;
  font-family: var(--font-ui);
}
.log-year-value {
  font-family: var(--font-data);
  font-size: 24px;
  font-weight: 600;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}
@media (max-width: 600px) {
  .log-year-strip { gap: 16px; }
  .log-year-value { font-size: 20px; }
}

/* ── Quarter strip ──────────────────────────────────────────────────
   The eyebrow above the existing .stat-strip uses the shared
   .section-eyebrow class. Phase B-5 strips the .stat-strip card
   chrome below this eyebrow so the two strips read as visual peers
   with THIS WEEK above.
*/

/* ── Goal ribbon ────────────────────────────────────────────────────
   Phase F7 — four-line typographic banner:
     RACE GOAL · 38 DAYS   (small-caps eyebrow)
     Half Marathon          (sentence-case headline, larger)
     Target X · Trajectory Y
     [bar with marker]  status word

   The 🎯 emoji prefix was removed in F7; the eyebrow + headline
   pattern matches THIS WEEK and RACE TIMES eyebrows, which keeps the
   page rhythm consistent.

   No card chrome — borders, padding, and background are deliberately
   absent. The bar provides the only visual interest.

   The whole ribbon is a clickable link to the Race page (anchor
   styled to look like body text — color and decoration reset).

   :empty collapses when no active goal exists.
*/
.goal-ribbon:empty { display: none; }
.goal-ribbon {
  margin-bottom: var(--space-section);
}
.goal-ribbon-link {
  display: block;
  color: inherit;
  text-decoration: none;
  cursor: pointer;
  /* The one allowed visual affordance — a hover state hints at
     interactivity without adding chrome at rest. */
  transition: opacity 0.15s ease;
}
.goal-ribbon-link:hover { opacity: 0.88; }
.goal-ribbon-eyebrow {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--muted);
  font-weight: 600;
  font-family: var(--font-ui);
  margin-bottom: 4px;
}
.goal-ribbon-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 6px;
}
.goal-ribbon-headline {
  font-size: 18px;
  color: var(--text);
  font-weight: 600;
  font-family: var(--font-ui);
  letter-spacing: -0.01em;
}
.goal-ribbon-trajectory {
  font-size: 13px;
  color: var(--text2);
  margin-bottom: 10px;
  font-family: var(--font-ui);
  font-variant-numeric: tabular-nums;
}
.goal-ribbon-bar-row {
  display: flex;
  align-items: center;
  gap: 12px;
}
.goal-ribbon-bar {
  position: relative;
  flex: 1;
  height: 6px;
  display: flex;
  border-radius: 999px;
  overflow: hidden;
  background: var(--border);
}
.goal-ribbon-bar-segment {
  flex: 1;
  /* Background colors set inline (rose / gold / lime) so the segment
     order matches the achievability spectrum. Slight opacity keeps
     the bar from screaming compared to the marker dot. */
  opacity: 0.55;
}
.goal-ribbon-bar-marker {
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--text);
  border: 2px solid var(--bg);
  /* Box-shadow gives the marker a tiny glow so it pops against the
     three-tone bar regardless of which segment it lands on. */
  box-shadow: 0 0 0 1px var(--border);
  transition: left 0.4s ease;
}
.goal-ribbon-status {
  font-size: 11px;
  font-family: var(--font-ui);
  font-weight: 600;
  text-transform: lowercase;
  letter-spacing: 0.02em;
  white-space: nowrap;
  flex-shrink: 0;
}
.goal-status-ahead   { color: var(--lime); }
.goal-status-on      { color: var(--ctl, var(--teal)); }
.goal-status-stretch { color: var(--gold); }
.goal-status-behind  { color: var(--rose); }
.goal-status-pending { color: var(--muted); }

/* ── Dashboard heading row ──────────────────────────────────────────
   Eyebrow above the widget grid (uses the shared .section-eyebrow
   class). After Phase B the widget panels themselves are chrome-
   stripped so the heading + hairline-separated chart rows read as a
   single dashboard report rather than a stack of cards.
*/
.dashboard-heading-row {
  margin-bottom: var(--space-tight);
}

/* ── Overview upper section ────────────────────────────────────────
   H3: previously a float-based two-column layout (sidebar floated
   right, cards wrapped around). Now a single-column block — the
   Numbers panel moved out of .overview-upper to a slot below This
   Week (rendered by sidebar-stats.js into #ov-sidebar). The cards
   and strips inside flow top-to-bottom: Today card, Last Run,
   This Week, Race times.

   .overview-clear stays harmless: nothing inside .overview-upper
   floats anymore, so the clear is a no-op, but removing it would
   be a separate cleanup pass.
*/
.overview-upper {
  margin-bottom: var(--space-section);
}
/* H3: .overview-sidebar float behavior retired. The sidebar moved
   out of .overview-upper into a slot below This Week; #ov-sidebar
   is now hosted as a normal block with no float and no fixed width.
   Old class kept as a passthrough so any stale references don't
   blow up. */
.overview-sidebar { display: block; }
.overview-clear { clear: both; }

/* H3: kept from the float era. display:flow-root on these children
   used to prevent block-level overlap with the floated sidebar. With
   the sidebar moved out, flow-root is harmless: it still establishes
   a new block formatting context per child, which doesn't change
   layout but doesn't break anything either. Leaving it alone avoids
   regression risk during the H1–H3 refactor; can be cleaned up in a
   later pass. */
.overview-upper .hero-card,
.overview-upper .this-week,
.overview-upper .race-times,
.overview-upper .goal-ribbon {
  display: flow-root;
  margin-bottom: var(--space-row);
}
/* The block above wins specificity over the base `.X:empty { display:
   none }` rules scattered throughout the file. Without these high-
   specificity overrides, an empty child of .overview-upper still
   reserves a 34px box (padding from its own .X rule). Hero card
   needs this in early-load states; the other surfaces use :empty
   for "no data yet" hides, so they need the same treatment for
   consistency. */
.overview-upper .hero-card:empty,
.overview-upper .this-week:empty,
.overview-upper .race-times:empty,
.overview-upper .goal-ribbon:empty,
.overview-upper .plan-preview-host:empty {
  display: none;
}

/* ── Numbers panel (H3) ────────────────────────────────────────────
   Replaces the old floated-right "Sidebar stats" block. Renders as a
   collapsed <details> panel below the This Week strip. Pin-open
   toggle persists via localStorage (handled in JS).

   Visual notes:
   - No card chrome. A 1px top border separates the panel from This
     Week above it, matching the typographic-rhythm direction the rest
     of Overview is heading in.
   - Open state shows a horizontal strip of metric rows. On wide
     viewports the rows distribute evenly; on narrow ones they wrap.
   - The ACWR row (Load ratio) is special: it spans the full row width
     so its colored band reads legibly.
   - Font sizes use the --fs-* token scale per project rules. Existing
     visual rhythm preserved by mapping old literal sizes:
       10px eyebrow → --fs-xs (11px)
       12px label    → --fs-sm (13px)
       14px value    → --fs-sm (13px) for the strip layout; values stay
                       tabular-num via --font-data.
*/
.numbers-panel-host { display: block; margin-bottom: var(--space-section); }

.numbers-panel {
  border-top: 1px solid var(--border);
  padding: var(--space-row) 0 0 0;
}
.numbers-panel-summary {
  /* Make the summary marker subtle. We'll use a custom rotation via
     the open state for a small chevron-style indicator. */
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: baseline;
  gap: 12px;
  user-select: none;
  flex-wrap: wrap;
}
.numbers-panel-summary::-webkit-details-marker { display: none; }
.numbers-panel-summary::after {
  content: '▸';
  color: var(--muted);
  font-size: var(--fs-xs);
  margin-left: auto;
  margin-right: 4px;
  transition: transform 0.15s;
}
.numbers-panel[open] .numbers-panel-summary::after {
  transform: rotate(90deg);
}
.numbers-panel-eyebrow {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--muted);
  font-weight: 600;
  font-family: var(--font-ui);
}
.numbers-panel-hint {
  font-size: var(--fs-xs);
  color: var(--muted);
  font-family: var(--font-ui);
}
.numbers-panel-pin {
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 4px;
  /* Tap target tuned for mobile thumbs. The pin icon is small but the
     hit area needs to be ~28px on a side. */
  padding: 4px 9px;
  cursor: pointer;
  font-size: var(--fs-xs);
  color: var(--muted);
  margin-left: 4px;
  /* Override the ::after arrow positioning push */
  margin-right: 8px;
}
.numbers-panel-pin[aria-pressed="true"] {
  border-color: var(--accent);
  color: var(--accent);
}
.numbers-panel-pin:hover {
  border-color: var(--accent);
  color: var(--accent);
}
.numbers-panel-body {
  padding-top: var(--space-row);
}
.numbers-panel-placeholder {
  font-size: var(--fs-sm);
  color: var(--muted);
  line-height: 1.5;
  font-family: var(--font-ui);
}

/* The horizontal strip layout for metric rows when the panel is open.
   Each row is roughly equal width; wraps on narrow viewports. The
   ACWR row breaks to a full-width row of its own so the band reads. */
.numbers-stat-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 18px 28px;
  align-items: baseline;
}
.numbers-stat-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-family: var(--font-ui);
  flex: 0 1 auto;
}
.numbers-stat-row-link {
  cursor: pointer;
  transition: color 0.12s;
}
.numbers-stat-row-link:hover .numbers-stat-value { color: var(--accent); }
.numbers-stat-label {
  font-size: var(--fs-sm);
  color: var(--muted);
}
.numbers-stat-value {
  font-family: var(--font-data);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
}
/* Form (TSB) row uses a color-coded value. Mirrors the readiness
   band thresholds so Form reads consistently with the Today card. */
.numbers-stat-value-fresh      { color: var(--status-ready); }
.numbers-stat-value-manageable { color: var(--status-caution); }
.numbers-stat-value-fatigued   { color: var(--status-strain); }
.numbers-stat-row-link:hover .numbers-stat-value-fresh,
.numbers-stat-row-link:hover .numbers-stat-value-manageable,
.numbers-stat-row-link:hover .numbers-stat-value-fatigued {
  color: inherit;
}

/* Load ratio (ACWR) row. Special row that pairs the bare ratio with a
   colored band strip and a dot. Full-width so the band reads legibly.
   Layout:
     [label]                    [value]
     [----rose----lime---gold---rose----]
     0      0.8      1.3   1.5
*/
.numbers-acwr-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-family: var(--font-ui);
  flex: 1 1 100%;
  min-width: 220px;
  margin-top: 4px;
}
.numbers-acwr-row.numbers-stat-row-link { cursor: pointer; }
.numbers-acwr-row.numbers-stat-row-link:hover .numbers-stat-value { color: var(--accent); }
.numbers-acwr-header {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
}
.numbers-acwr-strip {
  position: relative;
  height: 6px;
  border-radius: 3px;
  display: flex;
  overflow: visible;
}
.numbers-acwr-seg {
  height: 100%;
}
.numbers-acwr-seg:first-child { border-radius: 3px 0 0 3px; }
.numbers-acwr-seg:last-child  { border-radius: 0 3px 3px 0; }
.numbers-acwr-seg-low  { background: var(--status-strain); opacity: 0.55; }
.numbers-acwr-seg-safe { background: var(--status-ready); }
.numbers-acwr-seg-warn { background: var(--status-caution); }
.numbers-acwr-seg-high { background: var(--status-strain); opacity: 0.85; }
.numbers-acwr-dot {
  position: absolute;
  top: 50%;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--text);
  border: 2px solid var(--bg);
  transform: translate(-50%, -50%);
  pointer-events: none;
}
.numbers-acwr-ticks {
  position: relative;
  height: 12px;
  font-family: var(--font-data);
  font-size: var(--fs-xs);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.numbers-acwr-ticks span {
  position: absolute;
  transform: translateX(-50%);
}
.numbers-acwr-ticks span:nth-child(1) { left: 0;                                  transform: translateX(0);    }
.numbers-acwr-ticks span:nth-child(2) { left: calc(0.8 / 1.9 * 100%);  }
.numbers-acwr-ticks span:nth-child(3) { left: calc(1.3 / 1.9 * 100%);  }
.numbers-acwr-ticks span:nth-child(4) { left: calc(1.5 / 1.9 * 100%);  }

/* ── Race Times block ──────────────────────────────────────────────
   Phase F1. Replaces the race-predictions-strip and absorbs the
   sidebar PR list into a single 4-column table:

       distance · predicted (±CI, trend) · personal best · gap

   No card chrome. The whole block is clickable → Race page.

   Highlight states on rows:
     race-times-row-shape  — predicted within 1% of PR ("in PR shape").
                              Subtle lime tint on the gap text.
     race-times-row-ahead  — predicted faster than PR. Lime gap text.
     (default)              — predicted slower than PR. Muted gap text.
*/
.race-times:empty { display: none; }
.race-times {
  margin-bottom: var(--space-section);
}
.race-times-table {
  cursor: pointer;
  transition: opacity 0.12s;
}
.race-times-table:hover { opacity: 0.95; }

.race-times-header,
.race-times-row {
  display: grid;
  grid-template-columns: 80px 1fr 1fr 1fr;
  gap: 16px;
  align-items: baseline;
  padding: 8px 0;
}
.race-times-header {
  border-bottom: 1px solid var(--border);
  padding-top: 0;
  padding-bottom: 6px;
  margin-bottom: 4px;
}
.race-times-row + .race-times-row {
  border-top: 1px solid var(--border);
}
.race-times-header > span {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.10em;
  color: var(--muted);
  font-weight: 600;
  font-family: var(--font-ui);
}
.race-times-col-dist {
  font-family: var(--font-ui);
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  letter-spacing: 0.02em;
}
.race-times-time {
  font-family: var(--font-data);
  font-size: 18px;
  font-weight: 600;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}
.race-times-ci {
  font-family: var(--font-data);
  font-size: 11px;
  color: var(--muted2);
  font-variant-numeric: tabular-nums;
  margin-left: 4px;
}
.race-times-trend {
  font-family: var(--font-data);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  margin-left: 6px;
}
.race-times-trend-up   { color: var(--lime); }
.race-times-trend-down { color: var(--muted2); }

.race-times-pr-date {
  font-family: var(--font-ui);
  font-size: 11px;
  color: var(--muted2);
  margin-left: 6px;
}

.race-times-gap-shape {
  color: var(--lime);
  font-family: var(--font-ui);
  font-size: 12px;
  font-weight: 600;
}
.race-times-gap-ahead {
  color: var(--lime);
  font-family: var(--font-data);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}
.race-times-gap-behind {
  color: var(--muted2);
  font-family: var(--font-data);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}
.race-times-gap-muted {
  color: var(--muted);
  font-family: var(--font-ui);
  font-size: 11px;
  font-style: italic;
}
.race-times-row-shape .race-times-col-dist,
.race-times-row-shape .race-times-time {
  color: var(--text);
}
.race-times-empty {
  font-size: 12px;
  color: var(--muted);
  font-family: var(--font-ui);
  padding: 8px 0;
}

@media (max-width: 720px) {
  /* On narrow screens the table collapses to a stack. Each row
     becomes a 2-col grid (label / value) with the gap line spanning
     the full width below predicted+PR. */
  .race-times-header { display: none; }
  .race-times-row {
    grid-template-columns: 60px 1fr;
    grid-template-rows: auto auto auto;
    row-gap: 4px;
    column-gap: 12px;
    padding: 10px 0;
  }
  .race-times-col-dist {
    grid-row: 1 / span 3;
    align-self: center;
    font-size: 14px;
  }
  .race-times-col-pred {
    grid-column: 2;
    grid-row: 1;
  }
  .race-times-col-pr {
    grid-column: 2;
    grid-row: 2;
  }
  .race-times-col-pr .race-times-time {
    font-size: 14px;
    color: var(--text2);
  }
  .race-times-col-gap {
    grid-column: 2;
    grid-row: 3;
  }
  .race-times-time { font-size: 18px; }
}

/* ── THIS WEEK sparkline addition ──────────────────────────────────
   Phase C addition: inline mileage sparkline next to the distance
   number in the THIS WEEK row. Pure SVG, ~80px wide, takes over the
   horizontal space the cell value used to leave empty.
*/
.this-week-value-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  /* Allow the sparkline to wrap below the value on narrow viewports
     rather than overflow horizontally. */
  flex-wrap: wrap;
}
.this-week-spark {
  /* Sparkline color is muted by default; the endpoint dot uses
     --accent directly so it pops independently. */
  color: var(--muted2);
  display: inline-flex;
  align-items: center;
  /* Lift slightly so the sparkline sits visually centered with the
     value's number rather than its descender baseline. */
  position: relative;
  top: -4px;
}
.this-week-spark-svg {
  width: 80px;
  height: 24px;
  display: block;
}
@media (max-width: 600px) {
  /* On narrow screens the cell wraps to 2x2 grid; hide sparkline to
     keep the cell tight. */
  .this-week-spark { display: none; }
}

/* ── What's New panel (H5) ─────────────────────────────────────────
   Zone B of the Overview redesign. Sits between the Today card and
   the Last Run line. Surfaces backward-looking detector findings.

   Visual treatment is typographic, not card-like. The Today card
   above carries chrome (border, padding, hero number); What's New is
   a quieter list — eyebrow + 1-3 lines, each with a category dot, a
   sentence, and an optional share icon. No background, no border.

   Hidden via :empty when no detectors fire.
*/
.whats-new:empty { display: none; }
.whats-new {
  margin: 0 0 var(--space-row) 0;
  padding-top: var(--space-row);
  border-top: 1px solid var(--border);
  font-family: var(--font-ui);
}
.whats-new-eyebrow {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--muted);
  font-weight: 600;
  margin-bottom: var(--space-tight);
}
.whats-new-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.whats-new-hidden {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.whats-new-list[data-state="expanded"] .whats-new-hidden {
  margin-top: 0;
}
.whats-new-item {
  display: flex;
  align-items: baseline;
  gap: 10px;
  font-size: var(--fs-sm);
  color: var(--text2);
  line-height: 1.5;
}
.whats-new-text {
  flex: 1 1 auto;
}
/* Category dot. 8px circle, color-keyed to category. Sits just to
   the left of the sentence so the user gets a peripheral color cue
   without the eyebrow chrome of a card. */
.whats-new-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
  background: var(--border2);
  position: relative;
  top: -1px;
}
.whats-new-dot-race    { background: var(--accent); }
.whats-new-dot-pace    { background: var(--teal); }
.whats-new-dot-volume  { background: var(--lime); }
.whats-new-dot-workout { background: var(--gold); }
.whats-new-dot-fitness { background: var(--teal); }
.whats-new-dot-consistency { background: var(--lime); }
.whats-new-dot-misc    { background: var(--border2); }

/* Share button — small icon-style affordance at the end of the line.
   Quiet by default, accent on hover. Matches the language of the
   "Share this" button the pre-H4 Today card used. */
.whats-new-share {
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 4px;
  /* Padding tuned so the effective tap area is ~24x28px on desktop
     and grows on mobile via the font-size scaling. Tap target stays
     usable when the user thumbs at the icon. */
  padding: 4px 9px;
  font-size: var(--fs-xs);
  color: var(--muted);
  cursor: pointer;
  font-family: var(--font-ui);
  flex-shrink: 0;
  line-height: 1.2;
}
.whats-new-share:hover {
  border-color: var(--accent);
  color: var(--accent);
}

/* "Show N more" expander. Quiet text button beneath the list.
   Padded so the tap target is comfortable on mobile (≥30px tall). */
.whats-new-more {
  background: transparent;
  border: none;
  padding: 10px 0 4px 0;
  font-size: var(--fs-xs);
  color: var(--muted);
  cursor: pointer;
  font-family: var(--font-ui);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.whats-new-more:hover {
  color: var(--accent);
}

/* ── Weekly digest tile (Plan 03) ────────────────────────────────────
 *
 * Mounts at the top of the What's New panel, Monday through Wednesday.
 * Single-line summary of the just-completed week with Share and Dismiss
 * actions. Visually distinct from individual findings — uses a quiet
 * box (1px border) so it reads as a distinct "this week" callout
 * rather than a feed item.
 */
.weekly-digest-tile {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-tight);
  padding: var(--space-tight) var(--space-row);
  border: 1px solid var(--border);
  border-radius: var(--r);
  background: var(--surface);
  margin-bottom: var(--space-row);
}
.weekly-digest-tile-eyebrow {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--accent);
  font-weight: 600;
  font-family: var(--font-ui);
  flex-shrink: 0;
}
.weekly-digest-tile-line {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--fs-sm);
  color: var(--text);
  line-height: 1.4;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.weekly-digest-tile-stats {
  color: var(--text);
}
.weekly-digest-tile-insight {
  color: var(--text2);
}
.weekly-digest-tile-actions {
  display: flex;
  gap: 8px;
  flex-shrink: 0;
}
.weekly-digest-tile-share,
.weekly-digest-tile-dismiss {
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 4px 10px;
  font-size: var(--fs-xs);
  color: var(--muted);
  cursor: pointer;
  font-family: var(--font-ui);
}
.weekly-digest-tile-share:hover {
  border-color: var(--accent);
  color: var(--accent);
}
.weekly-digest-tile-dismiss:hover {
  border-color: var(--border2);
  color: var(--text2);
}
/* When the slot is empty, collapse it so it doesn't add stray
   vertical space inside the panel. */
.whats-new-digest-slot:empty {
  display: none;
}

/* ── Overview collapsible block (Plan 03) ────────────────────────────
 *
 * A lightweight <details> wrapper used to collapse secondary surfaces
 * on the Overview (Race Times, etc.) so they don't compete with the
 * Today card on first paint. Lighter than chart-panel-collapsible —
 * no surface chrome, just a clickable summary line that expands to
 * reveal the wrapped content.
 */
details.ov-collapsible {
  margin-bottom: var(--space-row);
}
details.ov-collapsible > summary {
  list-style: none;
  cursor: pointer;
  padding: 10px 0;
  display: flex;
  align-items: baseline;
  gap: 10px;
  user-select: none;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}
details.ov-collapsible[open] > summary {
  border-bottom: none;
}
details.ov-collapsible > summary::-webkit-details-marker {
  display: none;
}
details.ov-collapsible > summary::before {
  content: "›";
  color: var(--muted);
  font-size: var(--fs-base);
  line-height: 1;
  margin-right: 4px;
  transition: transform 0.15s ease;
  display: inline-block;
}
details.ov-collapsible[open] > summary::before {
  transform: rotate(90deg);
}
.ov-collapsible-label {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text2);
  font-weight: 600;
}
.ov-collapsible-hint {
  font-size: var(--fs-xs);
  color: var(--muted);
}
/* "More detail" expander body. Wraps What's New, Numbers, and Race
   Times in a single revealed block. Children get their own existing
   margins; the body itself provides a small top space below the
   summary line so the first child doesn't crowd the bottom border. */
.ov-more-body {
  margin-top: var(--space-row);
  display: flex;
  flex-direction: column;
  gap: var(--space-row);
}

/* ── Distribution chart (Plan 02, Plan 04 cleanup) ────────────────────
 *
 * Probability density chart on the Overview. Two modes:
 *   - Mode A: active goal — eyebrow + headline + KDE curve with
 *             ±1σ shaded band and median/target tick marks above
 *             the curve.
 *   - Mode B: no goal — eyebrow + CTA headline + "Set a goal"
 *             button. No chart; the chart only earns its place
 *             when there's a target to land against.
 *
 * Slot is empty before vo2 fetch completes; the :empty rule
 * collapses it so the layout doesn't flash a gap during load. The
 * painted shell uses .ov-distribution-slot as the outer container
 * (no border, no card chrome — matches the typographic-rhythm
 * pattern the Overview uses elsewhere).
 *
 * Colors: accent + accent-dim for the curve and ±1σ band; --predict
 * for the target tick. All values from tokens.css. Canvas-internal
 * text (Chart.js tick labels) bypasses the no-px rule per project
 * rules (Chart.js font config is out of scope of the type scale).
 */
.ov-distribution-slot:empty {
  display: none;
}
.ov-distribution-slot {
  margin-bottom: var(--space-row);
}
.dist-chart-eyebrow {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--muted);
  font-weight: 600;
  font-family: var(--font-ui);
  margin-bottom: var(--space-tight);
}
.dist-chart-headline {
  font-size: var(--fs-base);
  color: var(--text);
  line-height: 1.4;
  margin-bottom: var(--space-tight);
}
.dist-chart-headline-pending {
  color: var(--muted);
}
.dist-chart-canvas-wrap {
  height: var(--chart-h-compact);
  position: relative;
}
.dist-chart-canvas-wrap canvas {
  width: 100% !important;
  height: 100% !important;
}

/* Mode B CTA. Sits where the chart would otherwise paint. Single
   button, accent-bordered, fills with accent on hover. The CTA
   block has its own top margin so it visually balances the
   headline above it. */
.dist-chart-cta {
  margin-top: var(--space-tight);
}
.dist-chart-cta-btn {
  font-family: var(--font-ui);
  font-size: var(--fs-sm);
  color: var(--accent);
  background: transparent;
  border: 1px solid var(--accent);
  border-radius: 6px;
  padding: 8px 18px;
  cursor: pointer;
  user-select: none;
  transition: background 0.15s, color 0.15s;
}
.dist-chart-cta-btn:hover {
  background: var(--accent);
  color: var(--bg);
}


/* ── "Show on Overview" picker on Race tab (Plan 02) ─────────────────
 *
 * A small panel below the race cards. Lets the user check the
 * distances they want surfaced in the Overview distribution chart's
 * chip switcher. Doesn't affect the Race tab's own card grid.
 */
.overview-distance-picker {
  margin: var(--space-section) 0;
  padding: var(--space-row) 0;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}
.overview-distance-picker-eyebrow {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--muted);
  font-weight: 600;
  font-family: var(--font-ui);
}
.overview-distance-picker-sub {
  font-size: var(--fs-sm);
  color: var(--text2);
  margin: 4px 0 var(--space-tight);
}
.overview-distance-picker-items {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-row);
}
.overview-distance-picker-item {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: var(--fs-sm);
  color: var(--text);
  cursor: pointer;
  user-select: none;
}
.overview-distance-picker-item input[type="checkbox"] {
  margin: 0;
  cursor: pointer;
}


/* ── Pace zones panel ─────────────────────────────────────────────
   Horizontal bar visualization of personal pace zones, derived from
   the user's actual run distribution. Lives on the Training page.

   The bar is a flex row of colored segments. Each segment's width
   is proportional to the pace range it covers. Below the bar, a
   parallel flex row of labels (name + range + count + HR).

   Tones map to zone names: Easy=lime, Steady=accent, Tempo=gold,
   Threshold=rose. For the single-zone "Steady" case we use accent.
   Extra zones (5+) cycle through the same palette.
*/

.zones-meta {
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0.02em;
  color: var(--muted2);
  margin-left: 8px;
  font-size: 10px;
}

/* Body wrapper inside the pace-zones <details> panel. Provides
   the inner padding the chart and labels expect to sit inside. */
.pace-zones-body {
  padding: 16px 20px 20px;
}

/* Chart wrapper: gives the canvas a definite height. Chart.js needs a
   sized parent to render correctly when canvas height is just an
   attribute (which Chart.js overrides on resize otherwise). */
.pace-profile-chart-wrap {
  position: relative;
  width: 100%;
  height: 180px;
  margin-top: 4px;
}

/* Quartile-band labels row beneath the chart. Four cells, evenly
   spaced — NOT proportional to on-screen band width. The labels are
   reference data, not a second axis. */
.pace-profile-bands {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
  margin-top: 10px;
}
.pace-profile-band {
  font-family: var(--font-ui);
  /* Left-anchor a small color swatch to the band name via border-left
     instead of an extra element. Width 3px matches the today-card
     left-border pattern used elsewhere. */
  border-left: 3px solid var(--surface2);
  padding-left: 8px;
  min-width: 0;
}
.pace-profile-band-0 { border-left-color: var(--rose); }
.pace-profile-band-1 { border-left-color: var(--gold); }
.pace-profile-band-2 { border-left-color: var(--teal); }
.pace-profile-band-3 { border-left-color: var(--sky); }

.pace-profile-band-name {
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text);
}
.pace-profile-band-range {
  font-family: var(--font-data);
  font-size: var(--fs-sm);
  color: var(--text2);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-top: 1px;
}

/* One-sentence narration below the band labels. Quiet, informational. */
.pace-profile-narration {
  font-family: var(--font-ui);
  font-size: var(--fs-sm);
  color: var(--text2);
  margin-top: 12px;
  line-height: 1.4;
}

/* Coverage caption: appears between the band labels and the narration
   when a corrected pace mode is active and some runs fell back to raw.
   Footnote-style: smaller, muted, italic. Hidden via inline style when
   coverage is full or mode is raw. */
.pace-profile-coverage {
  font-family: var(--font-ui);
  font-size: var(--fs-xs);
  color: var(--muted2);
  font-style: italic;
  margin-top: 10px;
  line-height: 1.4;
}
.pace-profile-coverage strong {
  font-style: normal;
  color: var(--text2);
  font-weight: 600;
}

/* Loading + placeholder + error states */
.zones-loading,
.zones-error {
  font-size: var(--fs-sm);
  color: var(--muted);
  font-family: var(--font-ui);
  padding: 12px 0;
}
.zones-placeholder {
  font-size: var(--fs-sm);
  color: var(--text2);
  font-family: var(--font-ui);
  line-height: 1.5;
  padding: 16px 0;
  max-width: 540px;
}
.zones-placeholder-count {
  display: inline-block;
  margin-left: 6px;
  font-size: var(--fs-xs);
  color: var(--muted);
}
.zones-sparse-note {
  font-size: var(--fs-xs);
  color: var(--muted2);
  font-style: italic;
  font-family: var(--font-ui);
  margin-top: 4px;
}

/* ── Plan preview (Overview compact) ─────────────────────────────
   Horizontal row of 7 day pills below the Today card. Today's pill
   has the accent border. Tapping a pill opens a picker overlay with
   the day's workout options + a Custom workout form.
*/

.plan-preview-host:empty { display: none; }
.plan-preview-container {
  margin-bottom: var(--space-row);
  display: flow-root;
}

.plan-preview-eyebrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  font-weight: 600;
  font-family: var(--font-ui);
  margin-bottom: 10px;
}

.plan-preview-view-btn {
  background: none;
  border: none;
  font-size: 11px;
  color: var(--accent);
  font-weight: 500;
  cursor: pointer;
  padding: 0;
  font-family: var(--font-ui);
  text-transform: none;
  letter-spacing: 0;
}
.plan-preview-view-btn:hover {
  text-decoration: underline;
}

/* Pills row — fill the available width, equal-sized pills */
.plan-preview-pills {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 6px;
  margin-bottom: 10px;
}

.plan-pill {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 10px 6px;
  border-radius: 8px;
  border: 1px solid var(--border);
  cursor: pointer;
  background: var(--surface);
  transition: background 0.12s, border-color 0.12s;
  min-width: 0;
}
.plan-pill:hover {
  background: var(--surface2);
  border-color: var(--accent);
}
.plan-pill.is-today {
  border-color: var(--accent);
  border-width: 2px;
  padding: 9px 5px;
  background: var(--surface2);
}
.plan-pill.is-static {
  cursor: default;
}
.plan-pill.is-static:hover {
  background: var(--surface);
  border-color: var(--border);
}
.plan-pill.is-today.is-static:hover {
  background: var(--surface2);
  border-color: var(--accent);
}

.plan-pill-dow {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--muted);
  font-family: var(--font-data);
}
.plan-pill.is-today .plan-pill-dow {
  color: var(--accent);
}

.plan-pill-chip {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  padding: 3px 8px;
  border-radius: 10px;
  background: var(--surface2);
  color: var(--text2);
  white-space: nowrap;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}
.plan-pill-chip[data-type="easy"],
.plan-pill-chip[data-type="recovery"] {
  background: rgba(34, 197, 94, 0.14);
  color: #16a34a;
}
.plan-pill-chip[data-type="tempo"],
.plan-pill-chip[data-type="threshold"],
.plan-pill-chip[data-type="workout"] {
  background: rgba(239, 68, 68, 0.14);
  color: #dc2626;
}
.plan-pill-chip[data-type="long"] {
  background: rgba(59, 130, 246, 0.14);
  color: #2563eb;
}
.plan-pill-chip[data-type="race"] {
  background: var(--accent);
  color: var(--bg);
}
.plan-pill-chip[data-type="rest"] {
  background: var(--surface2);
  color: var(--muted2);
}

.plan-pill-dist {
  font-size: 11px;
  font-family: var(--font-data);
  color: var(--text2);
  font-variant-numeric: tabular-nums;
}
.plan-pill.is-today .plan-pill-dist {
  color: var(--text);
  font-weight: 600;
}

.plan-pill-custom-badge {
  font-size: 9px;
  font-style: italic;
  color: var(--muted);
  letter-spacing: 0.04em;
  margin-top: -2px;
}
.plan-pill.is-today .plan-pill-custom-badge {
  color: var(--accent);
}

/* Completed pill — today's run is logged. The "is-today" treatment
   stays (border becomes the today accent) but we layer a left-edge
   bar in the completed token to mark the locked state. */
.plan-pill.is-completed {
  border-left: 3px solid var(--completed);
}
.plan-pill.is-completed:hover {
  border-color: var(--completed);
  border-left-color: var(--completed);
}
.plan-pill.is-completed.is-today {
  /* Today border treatment plus the left-edge bar. */
  border-color: var(--completed);
  border-left-width: 3px;
  background: var(--completed-dim);
}
.plan-pill.is-completed .plan-pill-dist {
  color: var(--completed);
  font-weight: 600;
}
/* Planned-double layered on a completed pill: thin bottom rule in the
   completed token to indicate the planned addition. The chip still
   reflects the completed type; the sub-line carries "+Xmi planned". */
.plan-pill.is-completed.has-planned-double {
  border-bottom: 2px solid var(--completed);
}

.plan-preview-totals {
  font-size: 11px;
  color: var(--muted);
  font-family: var(--font-data);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

.plan-preview-sep { margin: 0 4px; }

/* Caution banner — one-line load/ACWR nudge below the pills.
   Four states: balanced (lime), pushed (gold), overload (rose),
   light (muted). Color comes from the data-state attr. */
.plan-preview-caution {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 10px;
  padding: 8px 10px;
  border-radius: var(--r);
  font-family: var(--font-ui);
  font-size: 12px;
  line-height: 1.35;
  border: 1px solid var(--border);
  background: var(--bg2);
}
.plan-preview-caution-dot {
  font-family: var(--font-data);
  font-weight: 700;
  font-size: 13px;
  line-height: 1;
}
.plan-preview-caution-msg { color: var(--text2); }

.plan-preview-caution[data-state="balanced"] {
  background: rgba(163, 230, 53, 0.08);
  border-color: rgba(163, 230, 53, 0.28);
}
.plan-preview-caution[data-state="balanced"] .plan-preview-caution-dot {
  color: var(--lime);
}
.plan-preview-caution[data-state="pushed"] {
  background: rgba(251, 191, 36, 0.10);
  border-color: rgba(251, 191, 36, 0.32);
}
.plan-preview-caution[data-state="pushed"] .plan-preview-caution-dot {
  color: var(--gold);
}
.plan-preview-caution[data-state="overload"] {
  background: rgba(244, 63, 94, 0.10);
  border-color: rgba(244, 63, 94, 0.32);
}
.plan-preview-caution[data-state="overload"] .plan-preview-caution-dot {
  color: var(--rose);
}
.plan-preview-caution[data-state="light"] {
  background: var(--bg2);
  border-color: var(--border);
}
.plan-preview-caution[data-state="light"] .plan-preview-caution-dot {
  color: var(--muted2);
}

.plan-preview-loading {
  font-size: 11px;
  color: var(--muted2);
  font-family: var(--font-ui);
  font-style: italic;
  padding: 24px 0;
  text-align: center;
}

/* Picker overlay — opens when a pill is tapped */
.plan-picker-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
}

.plan-picker-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  width: 100%;
  max-width: 420px;
  display: flex;
  flex-direction: column;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.25);
}

.plan-picker-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 16px;
  border-bottom: 1px solid var(--border);
}

.plan-picker-title {
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  margin: 0;
}

.plan-picker-close {
  background: transparent;
  border: none;
  color: var(--muted2);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  padding: 0 6px;
}
.plan-picker-close:hover {
  color: var(--text);
}

.plan-picker-options {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 12px;
}

.plan-picker-option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface);
  cursor: pointer;
  font-size: 14px;
  color: var(--text);
  text-align: left;
  font-family: var(--font-ui);
  transition: background 0.12s, border-color 0.12s;
}
.plan-picker-option:hover {
  background: var(--surface2);
  border-color: var(--accent);
}
.plan-picker-option.is-current {
  border-color: var(--accent);
  background: var(--surface2);
}

.plan-picker-option-label {
  font-weight: 600;
}

.plan-picker-option-meta {
  display: flex;
  align-items: center;
  gap: 10px;
}

.plan-picker-option-dist {
  font-size: 12px;
  color: var(--muted);
  font-family: var(--font-data);
  font-variant-numeric: tabular-nums;
}

.plan-picker-option-badge {
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--accent);
  font-weight: 700;
}

/* Picker view container — holds either the options list view or the
   custom workout form view; the picker swaps content here on Back/Custom. */
.plan-picker-card-body {
  display: flex;
  flex-direction: column;
}

/* Divider between options and "Custom workout…" button */
.plan-picker-divider {
  height: 1px;
  background: var(--border);
  margin: 4px 12px 0;
}

.plan-picker-custom-trigger {
  background: transparent;
  border: none;
  color: var(--accent);
  font-size: 13px;
  font-weight: 500;
  font-family: var(--font-ui);
  cursor: pointer;
  padding: 12px;
  text-align: center;
}
.plan-picker-custom-trigger:hover {
  text-decoration: underline;
}

.plan-picker-reset {
  background: transparent;
  border: none;
  color: var(--muted);
  font-size: 12px;
  font-family: var(--font-ui);
  cursor: pointer;
  padding: 8px 12px 12px;
  text-align: center;
}
.plan-picker-reset:hover {
  color: var(--text2);
  text-decoration: underline;
}

/* Custom workout form view */
.plan-picker-form {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 12px;
}

.plan-picker-form-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.plan-picker-form-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
  font-weight: 600;
  font-family: var(--font-ui);
}

.plan-picker-form-field select,
.plan-picker-form-field input[type="number"] {
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 8px 10px;
  font-size: 14px;
  font-family: var(--font-ui);
  color: var(--text);
  width: 100%;
  box-sizing: border-box;
}
.plan-picker-form-field select:focus,
.plan-picker-form-field input[type="number"]:focus {
  outline: none;
  border-color: var(--accent);
}

.plan-picker-form-pace {
  display: flex;
  align-items: center;
  gap: 6px;
}
.plan-picker-form-pace input {
  width: 64px;
  text-align: center;
  font-family: var(--font-data);
  font-variant-numeric: tabular-nums;
}
.plan-picker-form-pace span {
  color: var(--muted);
  font-family: var(--font-data);
}

.plan-picker-form-error {
  font-size: 12px;
  color: #dc2626;
  font-family: var(--font-ui);
}

.plan-picker-form-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 4px;
}

.plan-picker-back-link {
  background: transparent;
  border: none;
  color: var(--muted);
  font-size: 13px;
  font-family: var(--font-ui);
  cursor: pointer;
  padding: 0;
}
.plan-picker-back-link:hover {
  color: var(--text);
}

.plan-picker-form-save {
  background: var(--accent);
  color: var(--bg);
  border: none;
  border-radius: 4px;
  padding: 8px 18px;
  font-size: 13px;
  font-weight: 600;
  font-family: var(--font-ui);
  cursor: pointer;
  margin-left: auto;
}
.plan-picker-form-save:hover {
  filter: brightness(1.1);
}


/* ── Plan tab (full grid) ────────────────────────────────────────
   Dedicated PLAN page. Day cards in a vertical stack with full
   detail per day. Customize modal floats over the page.
*/

#page-plan .page-inner {
  max-width: 800px;
  margin: 0 auto;
}

.plan-grid {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-top: 16px;
}

/* ── Plan day cards — match the Overview pill aesthetic ────────────
   Same color tokens as .plan-pill-chip so the type colors are
   identical across both surfaces. Cards are tappable rows; tap opens
   the same picker overlay used by the Overview pills. */
.plan-day-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 12px 14px;
  position: relative;
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.plan-day-card:hover {
  background: var(--surface2);
  border-color: var(--accent);
}
.plan-day-card:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.plan-day-card.is-today {
  border-color: var(--accent);
  border-width: 2px;
  padding: 11px 13px;
  background: var(--surface2);
}
.plan-day-card.is-race {
  cursor: default;
}
.plan-day-card.is-race:hover {
  background: var(--surface);
  border-color: var(--border);
}
.plan-day-card.is-today.is-race {
  background: var(--surface2);
  border-color: var(--accent);
}

.plan-day-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.plan-day-header-badges {
  display: flex;
  gap: 6px;
  align-items: center;
}

.plan-day-date {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
  font-family: var(--font-data);
}
.plan-day-card.is-today .plan-day-date {
  color: var(--accent);
}

.plan-day-today-badge {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--accent);
  font-family: var(--font-ui);
}

.plan-day-badge {
  display: inline-block;
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 2px 7px;
  border-radius: 10px;
  background: var(--surface2);
  color: var(--muted);
  font-weight: 600;
  font-family: var(--font-ui);
}
.plan-day-badge--pick   { background: rgba(6, 182, 212, 0.14);  color: var(--accent); }
.plan-day-badge--custom { background: rgba(245, 158, 11, 0.14); color: var(--gold); }
.plan-day-badge--race   { background: var(--accent); color: var(--bg); }
.plan-day-badge--completed {
  background: var(--completed-dim);
  color: var(--completed);
}

/* Completed day card — today's run is logged. Same left-edge bar
   treatment as the Overview pill so the surfaces match. */
.plan-day-card.is-completed {
  border-left: 3px solid var(--completed);
}
.plan-day-card.is-completed:hover {
  border-color: var(--completed);
}
.plan-day-card.is-completed.is-today {
  border-color: var(--completed);
  border-left-width: 3px;
  background: var(--completed-dim);
}
.plan-day-card.is-completed.has-planned-double {
  border-bottom: 2px solid var(--completed);
}

.plan-day-body {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

/* Type chip — matches .plan-pill-chip exactly so the colors are
   consistent between the Overview pill and the Plan tab card. */
.plan-day-chip {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  padding: 3px 8px;
  border-radius: 10px;
  background: var(--surface2);
  color: var(--text2);
  white-space: nowrap;
  font-family: var(--font-ui);
}
.plan-day-chip[data-type="easy"],
.plan-day-chip[data-type="recovery"] {
  background: rgba(34, 197, 94, 0.14);
  color: #16a34a;
}
.plan-day-chip[data-type="tempo"],
.plan-day-chip[data-type="threshold"],
.plan-day-chip[data-type="workout"] {
  background: rgba(239, 68, 68, 0.14);
  color: #dc2626;
}
.plan-day-chip[data-type="long"] {
  background: rgba(59, 130, 246, 0.14);
  color: #2563eb;
}
.plan-day-chip[data-type="race"] {
  background: var(--accent);
  color: var(--bg);
}
.plan-day-chip[data-type="rest"] {
  background: var(--surface2);
  color: var(--muted2);
}

.plan-day-summary {
  font-size: 13px;
  font-family: var(--font-data);
  color: var(--text2);
  font-variant-numeric: tabular-nums;
}
.plan-day-summary--rest {
  font-style: italic;
  color: var(--muted);
}

.plan-day-reasoning {
  font-size: 12px;
  color: var(--muted);
  line-height: 1.45;
  font-family: var(--font-ui);
}

.plan-day-structure {
  font-size: 11px;
  color: var(--muted2);
  font-style: italic;
  font-family: var(--font-ui);
  line-height: 1.4;
}


/* Totals + footer */
.plan-totals {
  margin-top: 24px;
  padding: 12px 16px;
  background: var(--surface2);
  border-radius: 6px;
}
.plan-totals-eyebrow {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  font-weight: 600;
  margin-bottom: 6px;
}
.plan-totals-line {
  font-size: 14px;
  font-family: var(--font-data);
  color: var(--text);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  margin-bottom: 2px;
}
.plan-totals-distribution {
  font-size: 12px;
  color: var(--muted);
  font-family: var(--font-data);
}
.plan-totals-acwr {
  font-size: 11px;
  color: var(--muted2);
  font-family: var(--font-data);
  margin-top: 4px;
}

.plan-footer {
  margin-top: 24px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
  text-align: center;
}

.plan-hide-toggle {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--muted);
  font-size: 12px;
  padding: 6px 14px;
  border-radius: 3px;
  cursor: pointer;
  font-family: var(--font-ui);
}
.plan-hide-toggle:hover {
  color: var(--text);
  border-color: var(--text);
}

.plan-loading,
.plan-error {
  font-size: 13px;
  color: var(--muted);
  font-style: italic;
  padding: 24px 0;
  text-align: center;
}

.plan-cold {
  padding: 32px 16px;
  text-align: center;
}
.plan-cold p {
  font-size: 14px;
  color: var(--muted);
  margin: 0 0 8px;
}
.plan-cold-count {
  font-size: 12px;
  color: var(--muted2);
}

.plan-hidden-state {
  padding: 32px 16px;
  text-align: center;
}
.plan-hidden-message {
  font-size: 14px;
  color: var(--muted);
  margin-bottom: 12px;
}
.plan-show-again {
  background: transparent;
  border: 1px solid var(--accent);
  color: var(--accent);
  font-size: 12px;
  padding: 6px 14px;
  border-radius: 3px;
  cursor: pointer;
  font-family: var(--font-ui);
}
.plan-show-again:hover {
  background: var(--accent);
  color: var(--bg);
}


/* ── Customize modal ─────────────────────────────────────────── */

.plan-modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
}

.plan-modal {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 6px;
  width: 100%;
  max-width: 420px;
  display: flex;
  flex-direction: column;
}

.plan-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 16px;
  border-bottom: 1px solid var(--border);
}

.plan-modal-title {
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
}

.plan-modal-close {
  background: transparent;
  border: none;
  color: var(--muted2);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  padding: 0 6px;
}
.plan-modal-close:hover {
  color: var(--text);
}

.plan-modal-body {
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.plan-modal-label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
  font-weight: 600;
}

.plan-modal-type,
.plan-modal-distance,
.plan-modal-pace,
.plan-modal-notes {
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 8px 10px;
  border-radius: 3px;
  font-family: var(--font-ui);
  font-size: 13px;
  text-transform: none;
  letter-spacing: normal;
  font-weight: normal;
}
.plan-modal-notes {
  resize: vertical;
  min-height: 50px;
  font-family: var(--font-ui);
}

.plan-modal-error {
  font-size: 12px;
  color: var(--rose, #f43f5e);
  padding: 4px 0;
}

.plan-modal-actions {
  padding: 12px 16px;
  border-top: 1px solid var(--border);
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}

.plan-modal-cancel,
.plan-modal-save {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--muted);
  font-size: 12px;
  padding: 6px 14px;
  border-radius: 3px;
  cursor: pointer;
  font-family: var(--font-ui);
}
.plan-modal-save {
  background: var(--accent);
  color: var(--bg);
  border-color: var(--accent);
}
.plan-modal-cancel:hover {
  color: var(--text);
  border-color: var(--text);
}
.plan-modal-save:hover {
  filter: brightness(1.1);
}

/* ── Phase G plan tab — additions for missing classes ────────────────
   The existing plan-* CSS in this file covers the compact preview
   and partial day-card styles. This block fills the gaps used by
   pages/plan.js: modal overlay, dimmed banner, toggle button,
   container layout, option-fit variants, and totals grid.
*/

.plan-content { display: flex; flex-direction: column; gap: var(--space-row, 16px); }
.plan-content.is-dimmed { opacity: 0.6; }

.plan-loading, .plan-error {
  font-size: 12px;
  color: var(--muted);
  font-family: var(--font-ui);
  font-style: italic;
  padding: 12px 0;
}

.plan-dimmed-banner {
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 12px;
  font-size: 12px;
  color: var(--muted);
  font-family: var(--font-ui);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}

.plan-show-again,
.plan-toggle-button {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--muted);
  font-size: 11px;
  padding: 4px 10px;
  border-radius: 3px;
  cursor: pointer;
  font-family: var(--font-ui);
  transition: color 0.12s, border-color 0.12s;
}
.plan-show-again:hover,
.plan-toggle-button:hover {
  color: var(--accent);
  border-color: var(--accent);
}

.plan-header-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 4px;
}

.plan-phase-pill {
  display: inline-block;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--accent);
  background: var(--accent-bg, rgba(255, 60, 172, 0.12));
  padding: 3px 10px;
  border-radius: 12px;
  font-weight: 600;
  font-family: var(--font-ui);
}

/* Plan load card — sits at top of Plan tab. ATL/CTL trajectory +
   ACWR caution. State-driven left border + accent color. */
.plan-load-card {
  border: 1px solid var(--border);
  border-left: 3px solid var(--muted2);
  border-radius: var(--r);
  padding: 12px 14px 10px;
  background: var(--bg2);
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.plan-load-card[data-state="balanced"] { border-left-color: var(--lime); }
.plan-load-card[data-state="pushed"]   { border-left-color: var(--gold); }
.plan-load-card[data-state="overload"] { border-left-color: var(--rose); }
.plan-load-card[data-state="light"]    { border-left-color: var(--muted2); }

.plan-load-eyebrow {
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--muted);
  font-weight: 600;
  font-family: var(--font-ui);
}

.plan-load-numbers {
  display: flex;
  gap: 24px;
  flex-wrap: wrap;
  font-family: var(--font-data);
  font-variant-numeric: tabular-nums;
}
.plan-load-num {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.plan-load-num-label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  font-family: var(--font-ui);
  font-weight: 600;
}
.plan-load-num-value {
  font-size: 16px;
  color: var(--text);
  font-weight: 600;
}
.plan-load-num-delta {
  font-size: 11px;
  color: var(--muted2);
}
.plan-load-num-delta--state {
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 600;
}
.plan-load-card[data-state="balanced"] .plan-load-num-delta--state { color: var(--lime); }
.plan-load-card[data-state="pushed"]   .plan-load-num-delta--state { color: var(--gold); }
.plan-load-card[data-state="overload"] .plan-load-num-delta--state { color: var(--rose); }
.plan-load-card[data-state="light"]    .plan-load-num-delta--state { color: var(--muted2); }

.plan-load-spark {
  width: 100%;
  height: 80px;
  display: block;
}
.plan-load-sparkline {
  width: 100%;
  height: 100%;
  display: block;
}

.plan-load-caution {
  font-size: 12px;
  line-height: 1.45;
  color: var(--text2);
  font-family: var(--font-ui);
}

/* Weekly load preview — single horizontal bar comparing planned
   weekly TRIMP against the trailing 4-week mean. Sits above the
   plan-load-card (Projected Load) so the simplest signal lands
   first. CSS-only bar; no chart library. */
.plan-week-preview {
  border: 1px solid var(--border);
  border-radius: var(--r);
  padding: 12px 14px 10px;
  background: var(--bg2);
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.plan-week-preview-eyebrow {
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--muted);
  font-weight: 600;
  font-family: var(--font-ui);
}

.plan-week-preview-bar {
  position: relative;
  width: 100%;
  height: 8px;
  background: var(--bg3);
  border-radius: 4px;
  overflow: hidden;
}

.plan-week-preview-fill {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  background: var(--ctl);
  border-radius: 4px;
  transition: width 0.3s ease;
}

/* Recent-average marker — vertical tick at 50% of the bar (which is
   where 100% of recent average lands by construction). Rendered on
   top of the fill so it's always visible. */
.plan-week-preview-marker {
  position: absolute;
  top: -2px;
  bottom: -2px;
  left: 50%;
  width: 2px;
  background: var(--muted2);
  transform: translateX(-1px);
}

.plan-week-preview-sentence {
  font-size: 12px;
  line-height: 1.45;
  color: var(--text2);
  font-family: var(--font-ui);
}

/* Fill color by state. Neutral (within ±10%) stays at --ctl. High
   (≥15% above) shifts to --effort to flag elevated load. Low (≥15%
   below) shifts to --teal — a calmer color than the warning hue. */
.plan-week-preview[data-state="high"] .plan-week-preview-fill { background: var(--effort); }
.plan-week-preview[data-state="low"]  .plan-week-preview-fill { background: var(--teal); }

.plan-days-eyebrow {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  font-weight: 600;
  font-family: var(--font-ui);
  margin-bottom: 6px;
}

/* ── 7-column compact week row (desktop ≥700px) ─────────────────── */
.plan-days-row {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 6px;
}

.plan-day-compact {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 8px 6px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s;
  min-width: 0;
  text-align: center;
}
.plan-day-compact:hover {
  background: var(--surface2);
  border-color: var(--accent);
}
.plan-day-compact:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.plan-day-compact.is-today {
  border-color: var(--accent);
  background: var(--surface2);
}
.plan-day-compact.is-race {
  cursor: pointer;
}
.plan-day-compact.is-completed {
  border-left: 3px solid var(--completed);
  padding-left: 4px;
}
.plan-day-compact.is-completed.is-today {
  background: var(--completed-dim);
  border-color: var(--completed);
}
.plan-day-compact.has-planned-double {
  border-bottom: 2px solid var(--completed);
}

/* Selected card: a class is applied from JS (see _selectDay in
   plan.js). Sits on top of the today/completed states. */
.plan-day-compact.is-selected {
  border-color: var(--accent);
  background: var(--accent-dim);
}
.plan-day-compact.is-completed.is-selected {
  border-color: var(--completed);
  background: var(--completed-dim);
}

.plan-days-row .plan-day-compact {
  position: relative;
}

.plan-day-compact-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
}
.plan-day-compact-dow {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--muted);
  font-family: var(--font-data);
}
.plan-day-compact.is-today .plan-day-compact-dow {
  color: var(--accent);
}
.plan-day-compact-date {
  font-size: var(--fs-xs);
  color: var(--muted2);
  font-family: var(--font-data);
}
.plan-day-compact-today {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--accent);
  font-family: var(--font-ui);
}
.plan-day-compact-chip {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.02em;
  padding: 2px 6px;
  border-radius: 8px;
  background: var(--surface2);
  color: var(--text2);
  white-space: nowrap;
  font-family: var(--font-ui);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}
.plan-day-compact-chip[data-type="easy"],
.plan-day-compact-chip[data-type="recovery"] {
  background: rgba(34, 197, 94, 0.14);
  color: #16a34a;
}
.plan-day-compact-chip[data-type="tempo"],
.plan-day-compact-chip[data-type="threshold"],
.plan-day-compact-chip[data-type="workout"] {
  background: rgba(239, 68, 68, 0.14);
  color: #dc2626;
}
.plan-day-compact-chip[data-type="long"] {
  background: rgba(59, 130, 246, 0.14);
  color: #2563eb;
}
.plan-day-compact-chip[data-type="race"] {
  background: var(--accent);
  color: var(--bg);
}
.plan-day-compact-chip[data-type="rest"] {
  background: var(--surface2);
  color: var(--muted2);
}
.plan-day-compact-line1 {
  font-size: var(--fs-sm);
  font-family: var(--font-data);
  color: var(--text);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.plan-day-compact-line2 {
  font-size: var(--fs-xs);
  font-family: var(--font-data);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── Detail panel (below the row, replaces per-card reasoning) ───── */
.plan-day-detail {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 14px 16px;
  min-height: 60px;
}
.plan-day-detail:empty {
  display: none;
}
.plan-day-detail-inner {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.plan-day-detail-head {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.plan-day-detail-day {
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text);
  font-family: var(--font-data);
}
.plan-day-detail-chip {
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.02em;
  padding: 3px 8px;
  border-radius: 10px;
  background: var(--surface2);
  color: var(--text2);
  font-family: var(--font-ui);
}
.plan-day-detail-chip[data-type="easy"],
.plan-day-detail-chip[data-type="recovery"] {
  background: rgba(34, 197, 94, 0.14);
  color: #16a34a;
}
.plan-day-detail-chip[data-type="tempo"],
.plan-day-detail-chip[data-type="threshold"],
.plan-day-detail-chip[data-type="workout"] {
  background: rgba(239, 68, 68, 0.14);
  color: #dc2626;
}
.plan-day-detail-chip[data-type="long"] {
  background: rgba(59, 130, 246, 0.14);
  color: #2563eb;
}
.plan-day-detail-chip[data-type="race"] {
  background: var(--accent);
  color: var(--bg);
}
.plan-day-detail-chip[data-type="rest"] {
  background: var(--surface2);
  color: var(--muted2);
}
.plan-day-detail-summary {
  font-size: var(--fs-sm);
  font-family: var(--font-data);
  font-variant-numeric: tabular-nums;
  color: var(--text2);
}
.plan-day-detail-reasoning {
  font-size: var(--fs-sm);
  color: var(--text2);
  line-height: 1.5;
  font-family: var(--font-ui);
}
.plan-day-detail-structure {
  font-size: var(--fs-xs);
  color: var(--muted2);
  font-style: italic;
  font-family: var(--font-ui);
  line-height: 1.4;
}
.plan-day-detail-actions {
  margin-top: 4px;
  display: flex;
  gap: 8px;
}
.plan-day-detail-btn {
  background: transparent;
  border: 1px solid var(--border2);
  color: var(--text2);
  font-size: var(--fs-xs);
  font-family: var(--font-ui);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 7px 14px;
  border-radius: 6px;
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.plan-day-detail-btn:hover {
  background: var(--accent-dim);
  border-color: var(--accent);
  color: var(--accent);
}
.plan-day-detail-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.plan-day-detail-locked {
  font-size: var(--fs-xs);
  color: var(--muted);
  font-family: var(--font-ui);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* ── Mobile stack (visible <700px) ─────────────────────────────── */
.plan-days-stack {
  display: none;
  flex-direction: column;
  gap: 8px;
}

/* Legacy alias retained in case any other code path renders .plan-days */
.plan-days {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

@media (max-width: 699px) {
  .plan-days-row,
  .plan-day-detail {
    display: none;
  }
  .plan-days-stack {
    display: flex;
  }
}

/* Totals block */
.plan-totals {
  margin-top: 8px;
  padding: 16px;
  background: var(--surface2);
  border-radius: 4px;
}

.plan-totals-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px 24px;
}
@media (min-width: 700px) {
  .plan-totals-grid { grid-template-columns: repeat(4, 1fr); }
}

.plan-totals-grid > div {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.plan-totals-num {
  font-family: var(--font-data);
  font-size: 18px;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  font-weight: 600;
}

.plan-totals-label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
  font-family: var(--font-ui);
}

.plan-footer {
  margin-top: 8px;
  display: flex;
  justify-content: flex-end;
}

/* ── Customize modal ─────────────────────────────────────────────── */

.plan-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
}

.plan-modal-pace-input {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.plan-modal-pace-input input {
  width: 56px;
  padding: 6px 8px;
  font-family: var(--font-data);
  font-variant-numeric: tabular-nums;
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: 3px;
  color: var(--text);
}

/* ── Account drawer ──────────────────────────────────────────────────
   Right-side slide-out for profile / preferences / data actions.
   Opened by clicking #athlete-pill. Mirrors .nav-drawer behavior
   (transform + overlay) but anchored to the right edge.

   Sections inside #account-drawer-body are rendered by
   account-drawer.js. They use .account-section / .account-section-title
   / .account-row to keep spacing rhythm consistent. Tokens only — no
   hardcoded hex or px sizes outside the structural sizes (drawer width,
   padding rhythm). */
.athlete-pill { cursor: pointer; transition: border-color .15s, background .15s; }
.athlete-pill:hover { border-color: var(--border2); background: var(--surface2); }
.athlete-pill:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.account-overlay {
  position: fixed; inset: 0; z-index: 9990;
  background: rgba(0,0,0,.55);
  opacity: 0; pointer-events: none;
  transition: opacity .2s ease;
}
.account-overlay.open { opacity: 1; pointer-events: auto; }

.account-drawer {
  position: fixed; top: 0; right: 0; bottom: 0;
  z-index: 9999; isolation: isolate;
  width: min(92vw, 380px);
  background: var(--surface);
  border-left: 1px solid var(--border);
  display: flex; flex-direction: column;
  padding: 16px 0 max(16px, env(safe-area-inset-bottom));
  transform: translateX(100%);
  transition: transform .22s cubic-bezier(.4,0,.2,1);
  -webkit-overflow-scrolling: touch;
  overflow-y: auto;
}
.account-drawer.open { transform: translateX(0); }

.account-drawer-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 0 20px 16px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 8px;
  flex-shrink: 0;
}
.account-drawer-title {
  font-family: var(--font-ui);
  font-size: 14px; font-weight: 600;
  color: var(--text);
}
.account-close {
  background: none; border: none;
  color: var(--muted); font-size: 22px; line-height: 1;
  cursor: pointer; padding: 4px 8px;
  border-radius: var(--r);
  transition: color .15s, background .15s;
}
.account-close:hover { color: var(--text); background: var(--surface2); }

.account-drawer-body {
  display: flex; flex-direction: column;
  gap: var(--space-section);
  padding: 16px 20px;
}

.account-section { display: flex; flex-direction: column; gap: var(--space-tight); }
.account-section-title {
  font-family: var(--font-ui);
  font-size: 10px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted); font-weight: 500;
}

/* Identity strip — large photo + name + Strava ID. */
.account-identity {
  display: flex; align-items: center; gap: 14px;
  padding-bottom: var(--space-row);
  border-bottom: 1px solid var(--border);
}
.account-identity-photo {
  width: 56px; height: 56px;
  border-radius: 50%; object-fit: cover;
  background: var(--surface2);
  border: 1px solid var(--border);
  flex-shrink: 0;
}
.account-identity-photo-fallback {
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-ui);
  font-size: 20px; font-weight: 600;
  color: var(--muted);
}
.account-identity-text { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.account-identity-name {
  font-family: var(--font-ui);
  font-size: 16px; font-weight: 600;
  color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.account-identity-meta {
  font-size: 11px; color: var(--muted);
  font-family: var(--font-data);
}

/* Profile inputs row — Age + Max HR side by side. */
.account-row { display: flex; gap: 12px; align-items: flex-end; }
.account-field { display: flex; flex-direction: column; gap: 6px; flex: 1; min-width: 0; }
.account-field-label {
  font-size: 11px; color: var(--muted);
  font-family: var(--font-ui);
}
.account-field-input {
  background: var(--bg2);
  border: 1px solid var(--border);
  border-radius: var(--r);
  color: var(--text);
  font-family: var(--font-data);
  font-size: 13px;
  padding: 7px 10px;
  width: 100%;
}
.account-field-input:focus {
  outline: none; border-color: var(--accent);
}
.account-field-hint {
  font-size: 10px; color: var(--muted); margin-top: 2px;
}

/* Theme picker — three pill buttons. Mirrors the existing .pill-toggle
   pattern but inline in the drawer. */
.account-theme-toggle {
  display: inline-flex; gap: 0;
  border: 1px solid var(--border);
  border-radius: var(--r);
  padding: 2px;
  background: var(--bg2);
  width: fit-content;
}
.account-theme-btn {
  background: none; border: none;
  color: var(--muted);
  font-family: var(--font-ui);
  font-size: 11px; font-weight: 500;
  padding: 6px 14px;
  cursor: pointer;
  border-radius: 6px;
  transition: background .15s, color .15s;
}
.account-theme-btn:hover { color: var(--text); }
.account-theme-btn.is-active { background: var(--surface2); color: var(--text); }

/* Tier display */
.account-tier-line {
  display: flex; align-items: center; justify-content: space-between;
  font-size: 13px;
}
.account-tier-name { color: var(--text); font-weight: 500; }
.account-tier-action {
  background: none; border: none;
  color: var(--accent);
  font-family: var(--font-ui);
  font-size: 12px;
  cursor: pointer; padding: 0;
}
.account-tier-action:hover { text-decoration: underline; }

/* Action rows — Re-sync / Import GPX / Delete / Disconnect.
   Each action gets a row with a label and a button. Stack vertically
   for clarity; the drawer is narrow. */
.account-action {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px;
}
.account-action-label {
  font-size: 13px; color: var(--text);
}
.account-action-sub {
  font-size: 11px; color: var(--muted); margin-top: 2px;
}
.account-profile-link {
  color: var(--accent);
  text-decoration: none;
  word-break: break-all;
}
.account-profile-link:hover { text-decoration: underline; }
.account-action-btn {
  background: none;
  border: 1px solid var(--border);
  color: var(--text2);
  font-family: var(--font-data);
  font-size: 10px; letter-spacing: .08em; text-transform: uppercase;
  padding: 6px 12px;
  border-radius: 6px;
  cursor: pointer;
  white-space: nowrap;
  transition: border-color .15s, color .15s;
}
.account-action-btn:hover { border-color: var(--border2); color: var(--text); }
.account-action-btn.danger:hover { border-color: var(--rose); color: var(--rose); }

.account-section-divider {
  height: 1px; background: var(--border); margin: 4px 0;
}

/* ── LANDING (auth + connect states) ─────────────────────────────────
   Both states render into #connect-screen via js/landing.js. The
   wrapper provides bird background + centered flex. These rules only
   style the action cluster + inline email form + the small auth-card
   used by /forgot-password, /reset-password, /verify-email. */

.landing-headline { display:flex; flex-direction:column; gap:16px; align-items:center; text-align:center; z-index:1; }

.landing-actions {
  display:flex; flex-direction:column; align-items:center; gap:12px;
  width:100%; max-width:340px;
  z-index:1;
}

.landing-notice {
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text2);
  font-size: 13px;
  padding: 10px 14px;
  border-radius: var(--r);
  width: 100%;
  text-align: center;
  margin: 0;
}

.landing-error {
  background: rgba(244,63,94,.10);
  border: 1px solid var(--rose);
  color: var(--rose);
  font-size: 13px;
  padding: 10px 14px;
  border-radius: var(--r);
  width: 100%;
  text-align: center;
}

/* Primary: Continue with Google. White surface, dark text per Google
   brand guidelines. Same pill shape as the Strava button. */
.landing-google {
  display:inline-flex; align-items:center; justify-content:center; gap:10px;
  background: #ffffff;
  color: #1f1f1f;
  font-family: var(--font-ui);
  font-size: 14px;
  font-weight: 600;
  padding: 12px 28px;
  border-radius: 999px;
  border: 1px solid var(--border2);
  cursor: pointer;
  width: 100%;
  height: 48px;
  transition: transform .15s, border-color .15s, box-shadow .15s;
}
.landing-google:hover {
  transform: translateY(-1px);
  border-color: var(--accent);
  box-shadow: 0 0 24px var(--accent-dim);
}

/* Small text links: "Sign up with email", mode toggle, forgot, skip. */
.landing-link-small {
  color: var(--text2);
  font-family: var(--font-ui);
  font-size: 13px;
  cursor: pointer;
  text-decoration: none;
  padding: 4px 8px;
  transition: color .15s;
}
.landing-link-small:hover { color: var(--text); text-decoration: underline; }
.landing-link-bottom { margin-top: 4px; color: var(--muted2); }

/* Inline email form. Hidden by default; revealed by clicking
   "Sign up with email" or "Log in" toggle. */
.landing-email-form {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 100%;
  padding: 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r);
}
.landing-email-form[hidden] { display: none; }

.landing-email-form input {
  width: 100%;
  padding: 10px 12px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--r);
  color: var(--text);
  font-family: var(--font-ui);
  font-size: 14px;
  box-sizing: border-box;
  transition: border-color .15s;
}
.landing-email-form input:focus {
  outline: none;
  border-color: var(--accent);
}

.landing-hint {
  font-size: 11px;
  color: var(--muted);
  font-family: var(--font-ui);
  margin-top: -2px;
}

.landing-submit {
  background: var(--accent);
  color: #fff;
  font-family: var(--font-ui);
  font-size: 14px;
  font-weight: 600;
  padding: 10px 20px;
  border-radius: var(--r);
  border: none;
  cursor: pointer;
  margin-top: 4px;
  transition: filter .15s, transform .15s;
}
.landing-submit:hover { filter: brightness(1.10); transform: translateY(-1px); }
.landing-submit:disabled { opacity: .6; cursor: progress; }

.landing-skip-hint {
  font-size: 11px;
  color: var(--muted);
  margin: -4px 0 0;
  font-family: var(--font-ui);
}

/* ── AUTH-CARD (rare paths: /forgot-password, /reset-password, /verify-email) ──
   These are reached via email links and stand alone on top of the bird
   background. Same visual language as the landing email form, but in
   a centered card with its own title since they're not in the main
   funnel. */

#auth-screen {
  position: relative;
  z-index: 1;
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  padding: 40px;
}

.auth-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: 100%;
  max-width: 360px;
  padding: 28px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  text-align: left;
}

.auth-title {
  font-family: var(--font-ui);
  font-size: 20px;
  font-weight: 600;
  color: var(--text);
  margin: 0 0 4px;
  letter-spacing: -0.01em;
}

.auth-notice {
  color: var(--text2);
  font-size: 13px;
  line-height: 1.5;
  margin: 0;
}

.auth-error {
  background: rgba(244,63,94,.10);
  border: 1px solid var(--rose);
  color: var(--rose);
  font-size: 13px;
  padding: 8px 12px;
  border-radius: var(--r);
}

.auth-success {
  background: var(--accent-dim);
  border: 1px solid var(--accent);
  color: var(--text);
  font-size: 13px;
  padding: 8px 12px;
  border-radius: var(--r);
}

.auth-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 12px;
  color: var(--text2);
  font-family: var(--font-ui);
}

.auth-field input {
  width: 100%;
  padding: 10px 12px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--r);
  color: var(--text);
  font-family: var(--font-ui);
  font-size: 14px;
  box-sizing: border-box;
  transition: border-color .15s;
}
.auth-field input:focus { outline: none; border-color: var(--accent); }

.auth-hint {
  font-size: 11px;
  color: var(--muted);
}

.auth-submit {
  background: var(--accent);
  color: #fff;
  font-family: var(--font-ui);
  font-size: 14px;
  font-weight: 600;
  padding: 10px 20px;
  border-radius: var(--r);
  border: none;
  cursor: pointer;
  margin-top: 4px;
  transition: filter .15s, transform .15s;
}
.auth-submit:hover { filter: brightness(1.10); transform: translateY(-1px); }
.auth-submit:disabled { opacity: .6; cursor: progress; }

.auth-link {
  color: var(--accent);
  font-size: 13px;
  cursor: pointer;
  text-decoration: none;
}
.auth-link:hover { text-decoration: underline; }

.auth-switch {
  font-size: 13px;
  color: var(--text2);
  margin: 4px 0 0;
  text-align: center;
}
.auth-switch a {
  color: var(--accent);
  cursor: pointer;
}
.auth-switch a:hover { text-decoration: underline; }

/* ── DEMO BANNER ────────────────────────────────────────────────────
   Persistent top strip shown to visitors using a demo persona. Mounted
   in #demo-banner-host (created lazily by demo-banner.js as the first
   body child) so it sits above the app header without restructuring
   the existing layout.

   Three pieces in a flex row: text (flex-grow), switcher, sign-up CTA.
   On narrow viewports the switcher labels shrink; below 480px the
   "Switch:" prefix hides to save room. */
#demo-banner-host { width: 100%; }
.demo-banner {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 8px 20px;
  background: linear-gradient(90deg, rgba(6,182,212,.16), rgba(167,139,250,.10));
  border-bottom: 1px solid var(--border);
  font-family: var(--font-ui);
  font-size: 12px;
  line-height: 1.5;
  color: var(--text);
}
.demo-banner-text { flex: 1; color: var(--text2); min-width: 0; }
.demo-banner-text strong { color: var(--text); font-weight: 600; margin-right: 4px; }

/* Switcher: small inline cluster between text and CTA. */
.demo-banner-switcher {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}
.demo-switcher-label {
  color: var(--muted);
  font-size: 11px;
  letter-spacing: .04em;
  text-transform: uppercase;
}
.demo-switcher-link {
  color: var(--accent);
  font-size: 12px;
  text-decoration: none;
  padding: 3px 8px;
  border-radius: 6px;
  transition: color .12s, background .12s;
}
.demo-switcher-link:hover {
  color: var(--accent-hi);
  background: var(--accent-dim);
}
.demo-switcher-current {
  color: var(--text);
  font-size: 12px;
  font-weight: 600;
  padding: 3px 8px;
  border-radius: 6px;
  background: var(--accent-dim);
}

.demo-banner-cta {
  background: var(--accent);
  color: var(--bg);
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .04em;
  padding: 6px 14px;
  border-radius: 999px;
  text-decoration: none;
  white-space: nowrap;
  flex-shrink: 0;
  transition: opacity .12s, transform .12s;
}
.demo-banner-cta:hover { opacity: .88; transform: translateY(-1px); }

@media (max-width: 700px) {
  .demo-banner { padding: 6px 12px; gap: 10px; font-size: 11px; }
  .demo-switcher-label { display: none; }
  .demo-switcher-link, .demo-switcher-current { font-size: 11px; padding: 2px 6px; }
  .demo-banner-cta { font-size: 10px; padding: 5px 11px; }
}
@media (max-width: 480px) {
  .demo-banner { flex-wrap: wrap; }
  .demo-banner-text { flex-basis: 100%; }
}

/* ── LANDING DEMO LINK ──────────────────────────────────────────────
   The "See a live demo" link on State A of the landing. Sits between
   the Google button (primary) and the "Sign up with email" small link
   (secondary). Visual weight in between: accent color and arrow chevron
   to read as an actual affordance, but no button chrome to avoid
   competing with Google. */
.landing-demo-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--accent);
  font-family: var(--font-ui);
  font-size: 13px;
  font-weight: 500;
  text-decoration: none;
  padding: 6px 10px;
  border-radius: 6px;
  transition: color .15s, background .15s, transform .15s;
}
.landing-demo-link::after {
  content: "→";
  font-size: 13px;
  transition: transform .15s;
}
.landing-demo-link:hover {
  color: var(--accent-hi);
  background: var(--accent-dim);
}
.landing-demo-link:hover::after {
  transform: translateX(2px);
}

/* ───────────────────────────────────────────────────────────────────
   Utility class library — added by Plan 0c (inline-style audit).
   These exist to replace recurring `style="...px..."` declarations
   in JS render code and index.html. Tracked in /STYLE_AUDIT.md.

   Naming convention:
     .text-<color>-<size>     typography helpers
     .panel-<state>           panel-internal state messages
     .row-controls-<align>    flex rows for section-head controls
     .ctrl-grow-<n>           min-width grow controls for pill rows
     .chart-caption-meta      footer captions under chart bodies
     .chart-legend-strip      10-12px legend strips at panel bottom
     .chart-panel-pad-*       chart-panel inner padding
     .input-w-*               fixed-width inputs in section heads
     .btn-icon-sm[-rose]      compact icon buttons (admin)

   Each class uses tokens.css variables; no literal hex or font stacks.
   ─────────────────────────────────────────────────────────────────── */

/* Typography helpers */
.text-muted-sm  { font-size: var(--fs-sm); color: var(--muted); }
.text-muted-xs  { font-size: var(--fs-xs); color: var(--muted); }
.text-rose-sm   { font-size: var(--fs-sm); color: var(--rose); }
.text-data-sm   { font-family: var(--font-data); font-size: var(--fs-sm); }

/* Help-cursor + dotted underline (carried over from inline use in race.js). */
.help-cursor    { cursor: help; border-bottom: 1px dotted var(--muted); }

/* Centered empty/error states inside a panel body. */
.panel-empty-muted {
  text-align: center;
  color: var(--muted);
  padding: 14px;
}
.panel-empty-rose {
  text-align: center;
  color: var(--rose);
  padding: 14px;
}

/* Section-head control rows. End-aligned, centered, or wrap variants. */
.row-controls-end    { display: flex; gap: 10px; align-items: flex-end; }
.row-controls-center { display: flex; gap: 10px; align-items: center; }
.row-controls-wrap   {
  display: flex;
  gap: 8px;
  align-items: flex-end;
  flex-wrap: wrap;
}

/* Flex-grow controls with a minimum width before they collapse. */
.ctrl-grow-200 { flex: 1; min-width: 200px; }
.ctrl-grow-160 { flex: 1; min-width: 160px; }

/* Compact numeric inputs that live inside controls bars. */
.input-w-age   { width: 70px; }
.input-w-hrmax { width: 80px; }

/* Chart-caption helpers. */
.chart-caption-meta {
  font-size: var(--fs-xs);
  color: var(--muted);
  margin: -12px 0 18px;
  padding: 0 4px;
  line-height: 1.6;
}
.chart-legend-strip {
  padding: 4px 20px 16px;
  font-size: var(--fs-xs);
  color: var(--muted);
}

/* Chart-panel inner padding variants. */
.chart-panel-pad        { padding: 0 20px 16px; }
.chart-panel-pad-end    { padding: 0 20px 20px; }
.chart-panel-pad-top    { padding: 16px 20px 12px; }
.chart-panel-pad-bottom { padding: 0 20px 12px; }

/* Compact icon buttons (admin grant/revoke buttons, etc). */
.btn-icon-sm      { font-size: var(--fs-xs); padding: 4px 8px; }
.btn-icon-sm-rose { font-size: var(--fs-xs); padding: 4px 8px; color: var(--rose); }

/* Stat-strip value, compact variant. Used when the cell content is
   label-y (e.g. "5K in 18:24") rather than a pure number; the
   strip's default value size (var(--fs-lg)) reads too loud. Lands
   on var(--fs-base) which is the nearest scale slot to the legacy
   inline 14px override. */
.stat-strip-value--compact { font-size: var(--fs-base); }

/* Semantic color modifiers for stat-strip values. Use these in place
   of inline style="color:var(--teal)" / style="color:var(--rose)".
   They reference the dedicated --ctl and --atl tokens (which adjust
   between light and dark mode) rather than the raw color tokens, so
   the relationship "this cell is the Fitness number" is encoded in
   the class name. Used on the Fatigue page top strip and projection
   summary strip. */
.stat-strip-value-ctl { color: var(--ctl); }
.stat-strip-value-atl { color: var(--atl); }

/* ── Heart Rate hero block ────────────────────────────────────────
   Single-number hero for the Z2 percentage at the top of the HR
   page. Mirrors the Overview readiness hero pattern: one large
   number with a label underneath, then the remaining strip cells
   in a smaller row below. The label carries the "what this is OF"
   narration plus the resolved max HR for sanity-check. */
.hr-hero {
  margin-bottom: var(--space-tight);
  padding: 18px 20px 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
}
.hr-hero-value {
  font-family: var(--font-data);
  font-size: var(--fs-xl);
  font-weight: 600;
  color: var(--text);
  line-height: 1;
  letter-spacing: -0.02em;
}
.hr-hero-unit {
  font-size: var(--fs-base);
  color: var(--muted);
  margin-left: 2px;
  font-weight: 500;
  letter-spacing: 0;
}
.hr-hero-label {
  font-size: var(--fs-sm);
  color: var(--text2);
  font-family: var(--font-ui);
  line-height: 1.4;
}
.hr-hero-link {
  color: var(--accent);
  text-decoration: underline;
  cursor: pointer;
  font-size: var(--fs-sm);
}
.hr-hero-link:hover { color: var(--accent-hi); }

/* Race-page-specific (used by /js/pages/race.js).
   These are race-specific enough that they don't belong in the
   general utility set above, but extracting them out of inline
   styles keeps race.js readable. */
.race-card-empty-fullrow {
  color: var(--muted);
  font-size: var(--fs-sm);
  grid-column: 1 / -1;
}
.race-card-meta {
  font-size: var(--fs-xs);
  color: var(--muted);
  margin-top: 6px;
}
.goal-prob-panel-head { margin-bottom: 14px; }
.goal-prob-chart-host { height: 140px; position: relative; }

/* VO2-debug-table cells used by the Race page's per-run diagnostics
   panel. Specific enough to live as a small named family. The
   bottom-border + padding pattern matches other debug tables in
   the codebase (admin user table). */
.vo2-debug-th {
  text-align: left;
  padding: 6px 10px;
  border-bottom: 1px solid var(--surface2);
  font-weight: 600;
  font-size: var(--fs-xs);
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  user-select: none;
}
.vo2-debug-th.is-sortable { cursor: pointer; }
.vo2-debug-td {
  padding: 6px 10px;
  border-bottom: 1px solid var(--surface2);
  font-size: var(--fs-sm);
}
.vo2-debug-td--name {
  max-width: 200px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.vo2-debug-td-muted {
  padding: 6px 10px;
  font-size: var(--fs-sm);
  color: var(--muted);
}
.vo2-debug-row-rejected { opacity: 0.4; }
.vo2-debug-footnote {
  font-size: var(--fs-xs);
  color: var(--muted);
  margin-top: 12px;
  line-height: 1.6;
}
.vo2-debug-scroll { overflow-x: auto; }
.vo2-debug-table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--font-data);
}

/* ── Numbers panel: 4-week avg delta styling ──────────────────────
   The new 4-week-avg row pairs a distance value with a signed
   percentage delta. Delta uses lime/rose/muted to mirror the
   form-color treatment elsewhere in the panel. */
.numbers-stat-valuewrap {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
}
.numbers-stat-delta {
  font-family: var(--font-data);
  font-size: var(--fs-xs);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}
.numbers-stat-delta-up   { color: var(--status-ready); }
.numbers-stat-delta-down { color: var(--status-strain); }
.numbers-stat-delta-flat { color: var(--muted); }

/* #12: trend sparkline inside a vitals row (Fitness / Form). Muted so it
   reads as supporting context, not a second value. */
.numbers-stat-spark { width: 44px; height: 14px; opacity: 0.65; align-self: center; flex: 0 0 auto; }
.numbers-stat-spark-line { fill: none; stroke: var(--muted); stroke-width: 1.25; vector-effect: non-scaling-stroke; }
.numbers-stat-spark-dot  { fill: var(--text2); }

/* ── Numbers panel: pin icon (SVG, no emoji) ───────────────────── */
.numbers-panel-pin-icon {
  display: inline-block;
  vertical-align: middle;
}
.numbers-panel-pin {
  /* Tighten the padding now that the icon is small and centered.
     The original padding (4px 9px) was tuned around the emoji
     glyph; for an SVG icon a square hit area reads better. */
  padding: 4px 6px;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* ── Numbers rail (desktop ≥960px) ──────────────────────────────────
   Persistent right-column panel inside the .overview-upper-grid.
   Breakpoint visibility for .numbers-rail-host lives in layout.css;
   these rules style the rail's internal layout regardless of
   viewport. */
.numbers-rail {
  display: block;
  position: sticky;
  top: var(--space-section);
  padding: var(--space-row) 0 0 0;
  border-top: 1px solid var(--border);
  font-family: var(--font-ui);
}
.numbers-rail-eyebrow-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: var(--space-row);
}
.numbers-rail-eyebrow {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--muted);
  font-weight: 600;
}
.numbers-rail-toggle {
  background: transparent;
  border: none;
  padding: 4px;
  cursor: pointer;
  color: var(--muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  border-radius: 4px;
  transition: color 0.12s, background 0.12s;
}
.numbers-rail-toggle:hover {
  color: var(--text);
  background: var(--surface2);
}
.numbers-rail-rows {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.numbers-rail-rows .numbers-stat-row {
  /* In the rail, rows are full-width with label left, value right.
     The base .numbers-stat-row uses gap:8px and baseline alignment,
     which works inline but cramps in a column. Override here to
     justify the row so label and value sit on opposite sides. */
  justify-content: space-between;
  gap: 12px;
}
.numbers-rail-acwr {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px dashed var(--border);
}
/* The ACWR row inside the rail doesn't need flex:1 1 100% (the rail
   parent isn't a flex strip). Reset the min-width too so it fits the
   240px column comfortably. */
.numbers-rail-acwr .numbers-acwr-row {
  flex: 0 1 auto;
  min-width: 0;
  margin-top: 0;
}

/* ── Numbers strip + drawer (640–959px) ────────────────────────────
   At intermediate viewport widths the rail collapses to a thin
   36px-wide strip fixed to the right edge of the viewport. Clicking
   the strip slides out a drawer with the same content as the inline
   rail. The drawer is a panel, not a modal: no backdrop dim, no
   focus trap, dismisses on click-outside or Esc. */

.numbers-rail-strip-host {
  /* Position the strip itself fixed against the viewport so it
     stays in place as the page scrolls. The visible button inside
     is what renders the strip's chrome. */
  position: fixed;
  top: 0;
  right: 0;
  height: 100vh;
  width: 36px;
  z-index: 50;
  pointer-events: none; /* Re-enabled on the button below. */
}
.numbers-rail-strip {
  pointer-events: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  width: 36px;
  height: 100%;
  background: var(--surface);
  border: none;
  border-left: 1px solid var(--border);
  color: var(--muted);
  cursor: pointer;
  font-family: var(--font-ui);
  transition: color 0.15s, background 0.15s;
}
.numbers-rail-strip:hover {
  background: var(--surface2);
  color: var(--text);
}
.numbers-rail-strip-chev {
  font-size: var(--fs-base);
  line-height: 1;
}
.numbers-rail-strip-label {
  /* Rotated text reading bottom-to-top. Letter spacing widened so
     the short word reads cleanly at this height. */
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  font-size: var(--fs-xs);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  font-weight: 600;
}

.numbers-rail-drawer {
  position: fixed;
  top: 0;
  right: 36px; /* Sits flush against the strip. */
  height: 100vh;
  width: 280px;
  z-index: 51;
  background: var(--surface);
  border-left: 1px solid var(--border);
  box-shadow: -8px 0 24px rgba(0,0,0,0.18);
  transform: translateX(100%);
  transition: transform 0.18s ease-out;
  overflow-y: auto;
}
.numbers-rail-drawer.open {
  transform: translateX(0);
}
.numbers-rail-drawer-inner {
  /* Reuse the rail's internal styling so the drawer content looks
     identical to the inline rail content. The padding accounts for
     the drawer's fixed-position context (no parent grid padding). */
  padding: var(--space-section) var(--space-row);
  font-family: var(--font-ui);
}
.numbers-rail-drawer-inner .numbers-rail-rows {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.numbers-rail-drawer-inner .numbers-rail-rows .numbers-stat-row {
  justify-content: space-between;
  gap: 12px;
}
.numbers-rail-drawer-inner .numbers-rail-acwr {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px dashed var(--border);
}
.numbers-rail-drawer-inner .numbers-rail-acwr .numbers-acwr-row {
  flex: 0 1 auto;
  min-width: 0;
  margin-top: 0;
}

/* ── Run-detail modal ──────────────────────────────────────────────
   Wraps the existing .modal-overlay/.modal chrome. The modal body
   shows the same expanded run content used by the Activity Log
   table, so the modal needs more width than the default 680px. */
.run-detail-modal {
  max-width: 760px;
}
.run-detail-modal-sub {
  font-size: var(--fs-xs);
  color: var(--muted);
  font-family: var(--font-ui);
  margin-top: 2px;
}

/* ── Customize public profile modal ──────────────────────────────────
   Reuses the .pr-card-modal-overlay shell (overlay + modal box). These
   rules style the per-section toggle list inside the modal body. */
.cust-prof-list {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border);
  border-radius: var(--r);
  overflow: hidden;
}
.cust-prof-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 14px;
  border-top: 1px solid var(--border);
  cursor: pointer;
}
.cust-prof-row:first-child { border-top: none; }
.cust-prof-row.is-disabled {
  cursor: not-allowed;
  opacity: 0.6;
}
.cust-prof-row-label {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.cust-prof-row-name {
  font-size: var(--fs-sm);
  color: var(--text);
}
.cust-prof-row-hint {
  font-size: var(--fs-xs);
  color: var(--muted);
}
.cust-prof-row-control {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}
.cust-prof-pro-tag {
  font-size: var(--fs-xs);
  color: var(--accent);
  font-family: var(--font-data);
  letter-spacing: .04em;
  text-transform: uppercase;
}
.cust-prof-toggle {
  width: 18px;
  height: 18px;
  cursor: pointer;
}
.cust-prof-toggle:disabled { cursor: not-allowed; }
.cust-prof-status {
  font-size: var(--fs-xs);
  color: var(--muted);
  margin-top: 10px;
  min-height: 1em;
}
.cust-prof-status.is-error {
  color: var(--atl);
}

/* ── Global site footer ────────────────────────────────────────────────
   Renders at the bottom of <body>, outside every screen container, so
   privacy/terms/methodology links appear on the landing, auth screens,
   and the app alike. Strava "Powered by" mark stays in the in-app
   footer where Strava data renders. */
.site-footer {
  max-width: 1380px;
  margin: 40px auto 20px;
  padding: 16px 28px;
  border-top: 1px solid var(--border);
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
  gap: 16px;
  font-size: var(--fs-sm);
  color: var(--muted);
}
.site-footer a {
  color: var(--muted);
  text-decoration: none;
}
.site-footer a:hover {
  color: var(--text2);
}

/* ── Landing hero v2: 2-column with KDE widget ─────────────────────────
   The hero is the headline + the right-rail KDE widget. Below it,
   #landing-deep holds three feature sections (HAP, readiness, insight).
   At narrow viewports the grid collapses to single column. */

.landing-hero-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 32px;
  width: 100%;
  max-width: 1080px;
  margin: 0 auto;
  align-items: center;
}
@media (min-width: 880px) {
  .landing-hero-grid {
    grid-template-columns: minmax(280px, 380px) minmax(420px, 600px);
    gap: 48px;
    align-items: start;
  }
}
.landing-hero-controls {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
}
@media (min-width: 880px) {
  .landing-hero-controls { align-items: flex-start; }
}
.landing-hero-widget {
  width: 100%;
  max-width: 600px;
  margin: 0 auto;
}

/* ── KDE widget ──────────────────────────────────────────────────────── */

.lkw-shell {
  display: flex;
  flex-direction: column;
  gap: 18px;
  padding: 20px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r);
}
.lkw-controls {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.lkw-personas {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 6px;
}
@media (max-width: 520px) {
  .lkw-personas { grid-template-columns: repeat(2, 1fr); }
}
.lkw-persona {
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--r);
  color: var(--text2);
  font-family: var(--font-ui);
  font-size: var(--fs-xs);
  padding: 8px 10px;
  cursor: pointer;
  transition: border-color .15s, color .15s, background .15s;
  text-align: center;
  line-height: 1.2;
}
.lkw-persona:hover {
  color: var(--text);
  border-color: var(--text2);
}
.lkw-persona[aria-checked="true"] {
  background: var(--accent-dim);
  border-color: var(--accent);
  color: var(--text);
}
.lkw-slider-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.lkw-slider-label {
  font-size: var(--fs-xs);
  color: var(--muted);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.lkw-slider {
  width: 100%;
  accent-color: var(--accent);
}
.lkw-slider-marks {
  display: flex;
  justify-content: space-between;
  font-size: var(--fs-xs);
  color: var(--text2);
}
.lkw-slider-sub {
  font-size: var(--fs-xs);
  color: var(--muted);
  font-family: var(--font-data);
  text-align: center;
}
.lkw-chart {
  width: 100%;
  height: auto;
  display: block;
}
.lkw-fill { fill: var(--accent-dim); }
.lkw-line { stroke: var(--accent); stroke-width: 1.5; fill: none; }
.lkw-median {
  stroke: var(--text2);
  stroke-width: 1;
  stroke-dasharray: 3 3;
}
.lkw-tick {
  fill: var(--muted);
  font-family: var(--font-data);
  font-size: 11px;
  text-anchor: middle;
}
.lkw-caption {
  font-size: var(--fs-sm);
  color: var(--text2);
  line-height: 1.5;
  text-align: left;
}
.lkw-cap-strong {
  color: var(--text);
  font-family: var(--font-data);
}

/* ── Feature sections (#landing-deep) ───────────────────────────────── */

.landing-deep {
  width: 100%;
  position: relative;
  z-index: 1;
}
.ldf-section {
  padding: 80px 24px;
  border-top: 1px solid var(--border);
  background: var(--bg);
}
.ldf-section-inner {
  max-width: 720px;
  margin: 0 auto;
  text-align: left;
}
.ldf-eyebrow {
  font-family: var(--font-data);
  font-size: var(--fs-xs);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 12px;
}
.ldf-subhead {
  font-family: var(--font-ui);
  font-size: clamp(24px, 3.5vw, 36px);
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.01em;
  color: var(--text);
  margin: 0 0 16px 0;
}
.ldf-body {
  font-size: var(--fs-base);
  line-height: 1.6;
  color: var(--text2);
  margin: 0 0 28px 0;
}
.ldf-visual {
  border: 1px solid var(--border);
  border-radius: var(--r);
  background: var(--surface);
  padding: 20px;
}

/* HAP run-row visual */
.ldf-hap-visual { padding: 20px 16px; }
.ldf-run-row {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 12px;
  align-items: center;
}
@media (max-width: 640px) {
  .ldf-run-row {
    grid-template-columns: repeat(2, 1fr);
  }
}
.ldf-run-cell {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.ldf-run-cell-eyebrow {
  font-family: var(--font-data);
  font-size: var(--fs-xs);
  letter-spacing: 0.08em;
  color: var(--muted);
}
.ldf-run-cell-val {
  font-family: var(--font-data);
  font-size: var(--fs-base);
  color: var(--text);
}
.ldf-conditions {
  display: flex;
  gap: 8px;
  align-items: baseline;
}
.ldf-temp { color: var(--orange); }
.ldf-humidity {
  font-size: var(--fs-xs);
  color: var(--muted);
}
.ldf-run-callout {
  padding: 8px 12px;
  background: var(--accent-dim);
  border-radius: var(--r);
}
.ldf-run-callout .ldf-run-cell-eyebrow { color: var(--accent); }
.ldf-hap-val {
  color: var(--accent-hi);
  font-weight: 600;
}
.ldf-run-note {
  margin-top: 16px;
  font-size: var(--fs-sm);
  color: var(--muted);
  font-style: normal;
}

/* Readiness card visual */
.ldf-readiness-visual {
  padding: 24px;
}
.ldf-readiness-card {
  max-width: 360px;
  margin: 0 auto;
}
.ldf-readiness-eyebrow {
  font-family: var(--font-data);
  font-size: var(--fs-xs);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  text-align: center;
  margin-bottom: 8px;
}
.ldf-readiness-score {
  font-family: var(--font-data);
  font-size: 64px;
  font-weight: 600;
  line-height: 1;
  color: var(--accent);
  text-align: center;
  margin-bottom: 20px;
}
.ldf-readiness-bars {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 16px;
}
.ldf-bar-row {
  display: grid;
  grid-template-columns: 80px 1fr 36px;
  gap: 10px;
  align-items: center;
}
.ldf-bar-label {
  font-family: var(--font-data);
  font-size: var(--fs-xs);
  color: var(--muted);
  letter-spacing: 0.08em;
}
.ldf-bar-track {
  height: 6px;
  background: var(--border);
  border-radius: 3px;
  overflow: hidden;
}
.ldf-bar-fill {
  height: 100%;
  background: var(--accent);
  border-radius: 3px;
}
.ldf-bar-val {
  font-family: var(--font-data);
  font-size: var(--fs-sm);
  color: var(--text);
  text-align: right;
}
.ldf-readiness-note {
  font-size: var(--fs-sm);
  color: var(--text2);
  text-align: center;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}

/* Insight lines visual */
.ldf-insight-visual {
  display: flex;
  flex-direction: column;
  gap: 0;
  padding: 0;
}
.ldf-insight-line {
  display: grid;
  grid-template-columns: 90px 1fr;
  gap: 16px;
  padding: 16px 20px;
  border-top: 1px solid var(--border);
  align-items: baseline;
}
.ldf-insight-line:first-child { border-top: none; }
.ldf-insight-eyebrow {
  font-family: var(--font-data);
  font-size: var(--fs-xs);
  letter-spacing: 0.08em;
  color: var(--muted);
}
.ldf-insight-text {
  font-family: var(--font-ui);
  font-size: var(--fs-base);
  color: var(--text);
  line-height: 1.5;
}


/* ── Today's Number (Plan A / Phase 5) ──────────────────────────────
   One-stat strip below the THIS WEEK row. Same typographic-rhythm
   philosophy as THIS WEEK: no card chrome, just text laid out on
   the page. Eyebrow, headline, optional subline stacked vertically.

   Hidden via :empty when no rule fires (renderTodaysNumber clears
   the host) or on cold-start accounts (maturity gate). The :empty
   rule keeps the layout tight without a height-flash.
*/
.todays-number {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 20px;
  padding: 12px 0;
  border-top: 1px solid var(--border);
}
.todays-number:empty {
  display: none;
  margin: 0;
  padding: 0;
  border: none;
}

.todays-number-eyebrow {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  font-weight: 600;
  font-family: var(--font-ui);
}

.todays-number-headline {
  font-size: var(--fs-base);
  font-weight: 500;
  color: var(--text);
  line-height: 1.4;
}

.todays-number-subline {
  font-size: var(--fs-sm);
  color: var(--text2);
  line-height: 1.5;
}


/* ── Mode B race-time strip (Plan A / Phase 6) ──────────────────────
   Replaces the bare CTA when no goal is set. Four rows: 5K / 10K /
   Half marathon / Marathon, each showing median + 90% CI. A small
   secondary CTA below the rows still routes to the Race tab.

   Visual goal: dense and informational, not flashy. Reads like a
   row in the Race tab grid, just compressed. Falls back to .dist-
   chart-mode-b's empty-state styling automatically when no rows
   render (predictions endpoint unavailable / cold-start).
*/
.dist-chart-mode-b-strip .dist-chart-headline {
  margin-bottom: var(--space-tight);
}

.dist-chart-mb-rows {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: var(--space-tight);
}

.dist-chart-mb-row {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 6px 0;
  border-top: 1px solid var(--border);
}
.dist-chart-mb-row:first-child {
  border-top: none;
}

.dist-chart-mb-label {
  font-size: var(--fs-sm);
  color: var(--text2);
  flex: 0 0 110px;  /* enough room for "Half marathon" without wrap */
  font-family: var(--font-ui);
}

.dist-chart-mb-median {
  font-size: var(--fs-base);
  color: var(--text);
  font-feature-settings: 'tnum';
  font-weight: 500;
}

.dist-chart-mb-ci {
  font-size: var(--fs-xs);
  color: var(--muted);
  font-feature-settings: 'tnum';
  margin-left: auto;
}

/* Secondary CTA — text-style link beneath the rows. Less visually
   loud than the original button. The Mode A path's bigger button
   is still appropriate when no predictions are available; this
   smaller link sits below an already-informative strip. */
.dist-chart-mb-foot {
  margin-top: 4px;
}
.dist-chart-mb-cta-link {
  font-family: var(--font-ui);
  font-size: var(--fs-sm);
  color: var(--accent);
  background: none;
  border: none;
  padding: 4px 0;
  cursor: pointer;
  text-align: left;
}
.dist-chart-mb-cta-link:hover {
  text-decoration: underline;
}


/* ── Mode A trajectory row (Plan A / Phase 6) ──────────────────────
   One-sentence callout beneath the Mode A chart showing how the
   under-target probability has moved over the last four weeks.
   Visually subordinate to both the headline and the chart — small
   font, muted color, no separator line. Empty when the timeline
   endpoint hasn't resolved yet or returned insufficient data;
   :empty rule keeps the layout tight.
*/
.dist-chart-trajectory {
  font-size: var(--fs-sm);
  color: var(--text2);
  line-height: 1.5;
  margin-top: 8px;
  font-feature-settings: 'tnum';
}
.dist-chart-trajectory:empty {
  display: none;
}

/* ── Strava-as-signup additions (landing State A + auth screens) ──────
   Two classes added for the new primary-Strava landing flow:
     .landing-strava-primary  — wraps the official "Connect with Strava"
                                SVG button in the State A hero column
     .auth-or                 — "or" divider between Google button and
                                email form on /login and /create-account
*/

.landing-strava-primary {
  display: inline-block;
  text-decoration: none;
  transition: transform .15s, filter .15s;
  line-height: 0; /* prevent baseline space under the inline-block <img> */
}
.landing-strava-primary:hover {
  transform: translateY(-1px);
  filter: brightness(1.05);
}
.landing-strava-primary img {
  display: block;
  height: 48px;
  width: auto;
}

.auth-or {
  text-align: center;
  color: var(--text2);
  font-family: var(--font-ui);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin: 16px 0 8px;
  position: relative;
}
.auth-or::before,
.auth-or::after {
  content: "";
  position: absolute;
  top: 50%;
  width: calc(50% - 24px);
  height: 1px;
  background: var(--border);
}
.auth-or::before { left: 0; }
.auth-or::after  { right: 0; }

.auth-google {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  height: 40px;
  background: var(--surface);
  color: var(--text);
  font-family: var(--font-ui);
  font-size: var(--fs-sm);
  font-weight: 500;
  border: 1px solid var(--border);
  border-radius: 8px;
  cursor: pointer;
  transition: background .15s, border-color .15s;
}
.auth-google:hover {
  background: var(--surface2, var(--surface));
  border-color: var(--border2, var(--border));
}

/* Strava is now a first-class entry point (create-or-login), so it
   leads the auth card as the primary, branded button. */
.auth-strava {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  height: 40px;
  margin-bottom: 10px;
  background: #fc4c02;
  color: #fff;
  font-family: var(--font-ui);
  font-size: var(--fs-sm);
  font-weight: 600;
  border: 1px solid #fc4c02;
  border-radius: 8px;
  cursor: pointer;
  text-decoration: none;
  transition: filter .15s;
}
.auth-strava:hover { filter: brightness(1.06); }


/* ─────────────────────────────────────────────────────────────────
   PLAN — macroplan view (Phase 2)
   Chooser, creation modal, active-plan layout, weekly skeleton,
   warnings, kebab menu. All sizes from the 5-step type scale and
   the spacing tokens in tokens.css.
   ───────────────────────────────────────────────────────────────── */

/* ── Chooser ──────────────────────────────────────────────────── */

.plan-chooser {
  display: flex;
  flex-direction: column;
  gap: var(--space-section);
  padding: var(--space-row) 0;
}
.plan-chooser-heading {
  font-size: var(--fs-lg);
  font-weight: 500;
  color: var(--text);
  font-family: var(--font-ui);
}
.plan-chooser-cards {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-row);
}
@media (min-width: 800px) {
  .plan-chooser-cards {
    grid-template-columns: 1fr 1fr;
  }
}
.plan-chooser-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-tight);
  padding: var(--space-row);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  cursor: pointer;
  text-align: left;
  color: var(--text);
  font-family: var(--font-ui);
  transition: border-color .15s, background .15s;
}
.plan-chooser-card:hover {
  border-color: var(--border2);
  background: var(--surface2);
}
.plan-chooser-card:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.plan-chooser-card-title {
  font-size: var(--fs-base);
  font-weight: 500;
  color: var(--text);
}
.plan-chooser-card-desc {
  font-size: var(--fs-sm);
  color: var(--text2);
  line-height: 1.4;
}
.plan-chooser-card-meta {
  font-size: var(--fs-xs);
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.plan-chooser-card-none {
  background: var(--bg1);
}
.plan-chooser-anchor-low {
  font-size: var(--fs-sm);
  color: var(--text2);
  padding: var(--space-row);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r);
  line-height: 1.5;
}

/* ── Creation modal (extends goal-modal-* base) ───────────────── */

.plan-create-modal { max-width: 480px; }
.plan-modal-microcopy {
  font-size: var(--fs-sm);
  color: var(--text2);
  margin-bottom: var(--space-row);
  line-height: 1.4;
}
.plan-modal-static {
  font-size: var(--fs-sm);
  color: var(--text);
  flex: 1;
}
.plan-modal-info {
  font-size: var(--fs-sm);
  color: var(--text2);
  padding: var(--space-tight);
  background: var(--bg2);
  border: 1px solid var(--border);
  border-radius: var(--r);
  line-height: 1.4;
}
.plan-modal-error {
  font-size: var(--fs-sm);
  color: var(--rose);
  margin-top: var(--space-tight);
}
.plan-modal-unit-suffix {
  font-size: var(--fs-sm);
  color: var(--muted);
  margin-left: 6px;
}
.plan-create-modal input[type="number"] {
  flex: 1;
  padding: 6px 8px;
  background: var(--bg2);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text);
  font-family: var(--font-data, var(--font-ui));
  font-size: var(--fs-sm);
}
.plan-abandon-modal { max-width: 380px; }
.plan-abandon-confirm {
  background: var(--rose);
  color: var(--text);
  border-color: var(--rose);
}

/* ── Active-plan view ─────────────────────────────────────────── */

.plan-active {
  display: flex;
  flex-direction: column;
  gap: var(--space-row);
  font-family: var(--font-ui);
}
.plan-active-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-row);
  padding-bottom: var(--space-tight);
  border-bottom: 1px solid var(--border);
}
.plan-active-header-text {
  font-size: var(--fs-sm);
  color: var(--text);
  font-weight: 500;
}
.plan-active-kebab-wrap {
  position: relative;
}
.plan-active-kebab {
  background: none;
  border: none;
  color: var(--muted);
  font-size: var(--fs-lg);
  line-height: 1;
  cursor: pointer;
  padding: 4px 8px;
}
.plan-active-kebab:hover { color: var(--text); }
.plan-active-kebab-menu {
  position: absolute;
  right: 0;
  top: 100%;
  margin-top: 4px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r);
  min-width: 140px;
  z-index: 100;
  box-shadow: 0 4px 12px rgba(0,0,0,.2);
}
.plan-active-kebab-item {
  display: block;
  width: 100%;
  background: none;
  border: none;
  color: var(--text);
  text-align: left;
  padding: 8px 12px;
  font-size: var(--fs-sm);
  font-family: var(--font-ui);
  cursor: pointer;
}
.plan-active-kebab-item:hover { background: var(--surface2); }

.plan-active-warning {
  font-size: var(--fs-sm);
  color: var(--text);
  padding: var(--space-tight) var(--space-row);
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--gold);
  border-radius: var(--r);
  line-height: 1.4;
}
.plan-active-warning[data-code="volume_unreachable"] {
  border-left-color: var(--rose);
}
.plan-active-warning[data-code="horizon_too_aggressive"] {
  border-left-color: var(--rose);
}

.plan-active-current-eyebrow {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
  padding: var(--space-tight) 0;
}

.plan-active-no-current-week {
  font-size: var(--fs-sm);
  color: var(--text2);
  padding: var(--space-row);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r);
  text-align: center;
}

.plan-active-current-fallback {
  padding: var(--space-row);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r);
  display: flex;
  flex-direction: column;
  gap: var(--space-tight);
}

.plan-active-current-fallback-target {
  font-size: var(--fs-md);
  color: var(--text);
  font-weight: 600;
}

.plan-active-current-fallback-note {
  font-size: var(--fs-sm);
  color: var(--muted);
}

/* ── Weekly skeleton list (read-only in Phase 2) ──────────────── */

.plan-active-skeleton {
  display: flex;
  flex-direction: column;
  gap: var(--space-tight);
  margin-top: var(--space-section);
}
.plan-active-skeleton-eyebrow {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
}
.plan-active-skeleton-empty {
  font-size: var(--fs-sm);
  color: var(--muted);
  padding: var(--space-row);
  text-align: center;
}

/* ── Phase color tokens (theme-aware, shared by arc + cards) ──── */
.plan-week-card--base,     .plan-block-arc-bar--base     { --phase-color: var(--sky); }
.plan-week-card--build,    .plan-block-arc-bar--build    { --phase-color: var(--lime); }
.plan-week-card--peak,     .plan-block-arc-bar--peak     { --phase-color: var(--gold); }
.plan-week-card--taper,    .plan-block-arc-bar--taper    { --phase-color: var(--violet); }
.plan-week-card--race,     .plan-block-arc-bar--race     { --phase-color: var(--rose); }
.plan-week-card--recovery, .plan-block-arc-bar--recovery { --phase-color: var(--muted2); }

/* ── Block arc (whole-block overview) ─────────────────────────── */
.plan-block-arc-wrap {
  display: flex;
  flex-direction: column;
  gap: var(--space-tight);
}
.plan-block-arc {
  display: flex;
  align-items: stretch;
  gap: 3px;
  height: 84px;
}
.plan-block-arc-col {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.plan-block-arc-bar-area {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: flex-end;
  justify-content: center;
}
.plan-block-arc-bar {
  width: 100%;
  max-width: 22px;
  background: var(--phase-color, var(--accent));
  border-radius: 2px 2px 0 0;
  opacity: 0.85;
}
.plan-block-arc-bar--complete { opacity: 0.4; }
.plan-block-arc-bar--current {
  opacity: 1;
  outline: 2px solid var(--text);
  outline-offset: 1px;
}
.plan-block-arc-tick {
  height: 14px;
  line-height: 14px;
  text-align: center;
  font-size: 9px;
  color: var(--muted);
  font-family: var(--font-data, var(--font-ui));
}

/* ── Weekly grid ──────────────────────────────────────────────── */
.plan-active-week-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: var(--space-tight);
  align-items: start;
}
.plan-week-card {
  border: 1px solid var(--border);
  border-left: 3px solid var(--phase-color, var(--accent));
  border-radius: var(--r);
  background: var(--surface);
  padding: 10px 12px;
  font-size: var(--fs-sm);
}
.plan-week-card--complete {
  background: var(--bg1);
  color: var(--text2);
}
.plan-week-card--current {
  box-shadow: 0 0 0 2px var(--accent);
}
.plan-week-card-summary {
  cursor: pointer;
  list-style: none;
  display: block;
}
.plan-week-card-summary::-webkit-details-marker { display: none; }
.plan-week-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
}
.plan-week-card-headright {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.plan-week-card-num {
  font-weight: 600;
  color: var(--text);
  font-size: var(--fs-xs);
  letter-spacing: 0.04em;
}
.plan-week-card-phase {
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--phase-color, var(--accent));
  font-weight: 600;
}
.plan-week-card-caret {
  color: var(--muted);
  font-size: var(--fs-sm);
  transition: transform .15s;
}
.plan-week-card[open] .plan-week-card-caret { transform: rotate(90deg); }
.plan-week-card-range {
  color: var(--muted2);
  font-size: var(--fs-xs);
  margin-top: 2px;
}
.plan-week-card-dist {
  font-family: var(--font-data, var(--font-ui));
  font-size: var(--fs-md);
  color: var(--text);
  margin: 3px 0 6px;
}
.plan-week-card-bar {
  height: 4px;
  background: var(--bg1);
  border-radius: 2px;
  overflow: hidden;
}
.plan-week-card-bar-fill {
  display: block;
  height: 100%;
  background: var(--phase-color, var(--accent));
}
.plan-week-card-status {
  margin-top: 6px;
  font-size: var(--fs-xs);
  color: var(--text2);
  font-family: var(--font-data, var(--font-ui));
}
.plan-week-card-detail {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--border);
  font-family: var(--font-ui);
  font-size: var(--fs-sm);
}


/* ─────────────────────────────────────────────────────────────────
   PLAN — macroplan view (Phase 3)
   Day preview, weekly numbers, edit modal, warning actions.
   Token-only styling; type scale from tokens.css.
   ───────────────────────────────────────────────────────────────── */

/* ── Day preview (weeks 1-2 expanded) ─────────────────────────── */

.plan-active-day-rows {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.plan-active-day-row {
  display: grid;
  grid-template-columns: 90px 60px 1fr;
  gap: var(--space-tight);
  align-items: center;
  padding: 4px 0;
  color: var(--text);
  font-size: var(--fs-sm);
}
.plan-active-day-dow {
  color: var(--text2);
  font-weight: 500;
}
.plan-active-day-date {
  color: var(--muted2);
  font-size: var(--fs-xs);
}
.plan-active-day-info {
  font-family: var(--font-data, var(--font-ui));
  color: var(--text);
}

/* ── Weekly-numbers block (weeks 3+ expanded) ─────────────────── */

.plan-active-week-numbers {
  display: flex;
  flex-direction: column;
  gap: var(--space-tight);
}
.plan-active-week-numbers-row {
  display: grid;
  grid-template-columns: 140px 1fr;
  gap: var(--space-tight);
  font-size: var(--fs-sm);
}
.plan-active-week-numbers-label {
  color: var(--text2);
}
.plan-active-week-numbers-value {
  color: var(--text);
  font-family: var(--font-data, var(--font-ui));
}
.plan-active-week-note {
  margin-top: var(--space-tight);
  padding-top: var(--space-tight);
  border-top: 1px solid var(--border);
  color: var(--text2);
  font-style: italic;
  font-size: var(--fs-sm);
  line-height: 1.4;
}

/* ── Warning action button ────────────────────────────────────── */

.plan-active-warning {
  display: flex;
  flex-direction: column;
  gap: var(--space-tight);
}
.plan-active-warning-text {
  color: var(--text);
}
.plan-active-warning-action {
  align-self: flex-start;
  background: var(--surface2);
  border: 1px solid var(--border2);
  border-radius: var(--r);
  color: var(--text);
  font-family: var(--font-ui);
  font-size: var(--fs-sm);
  padding: 6px 12px;
  cursor: pointer;
  transition: background .15s, border-color .15s;
}
.plan-active-warning-action:hover {
  background: var(--bg3);
  border-color: var(--border);
}

/* ── Edit modal — inherits create-modal styling ──────────────── */

.plan-edit-modal { max-width: 460px; }

/* ── Post-connect modal (Phase 4, growth plan) ─────────────────────
   Reuses .pr-card-modal-overlay + .pr-card-modal shell from the share
   modal family. The additions here are: primary CTA (cyan, distinct
   from .pr-card-download because that one is a download icon button),
   inline error line, the URL code-block in the body, and the toast
   that fires after a successful flip-to-public.
*/

.post-connect-modal .pcm-url {
  background: var(--surface2);
  padding: 2px 6px;
  border-radius: var(--r);
  font-family: var(--font-data);
  font-size: var(--fs-xs);
  color: var(--text);
  word-break: break-all;
}

.post-connect-modal .pcm-error {
  font-size: var(--fs-sm);
  color: var(--rose);
  min-height: 1.4em;
  margin: 0;
}
.post-connect-modal .pcm-error:empty { min-height: 0; }

.pcm-primary {
  font-size: var(--fs-sm);
  padding: 8px 14px;
  border-radius: var(--r);
  cursor: pointer;
  font-family: var(--font-ui);
  border: 1px solid var(--accent);
  background: var(--accent);
  color: var(--bg);
  font-weight: 500;
}
.pcm-primary:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.pcm-primary:not(:disabled):hover {
  background: var(--accent-hi);
  border-color: var(--accent-hi);
}

/* Self-contained toast for the "Public. runperegrine.com/u/{name}."
   confirmation. Mirrors .app-toast in main.js but kept distinct so
   the post-connect flow doesn't depend on a non-exported helper. */
.pcm-toast {
  position: fixed;
  bottom: 20px;
  right: 20px;
  z-index: 1000;
  background: var(--surface);
  border: 1px solid var(--accent);
  padding: 14px 18px;
  border-radius: var(--r);
  box-shadow: 0 8px 24px rgba(0, 0, 0, .3);
  font-size: var(--fs-sm);
  color: var(--text);
  max-width: 340px;
  animation: toast-slide .25s ease-out;
}
