/* ═══════════════════════════════════════════════════════════════════════════════
   DASHBOARD — fixed overlay panel
   ═══════════════════════════════════════════════════════════════════════════════ */
#dashboard-panel { display: none; overflow-y: auto; background: var(--bg0); }
#dashboard-panel.visible { display: block; }

.dash-wrap { padding: 20px 24px 40px; }

/* ── Greeting ── */
.dash-hi { font-size: var(--fs-xl); font-weight: 600; color: var(--txt); margin-bottom: 2px; }
.dash-date { font-size: var(--fs-sm); color: var(--dim2); letter-spacing: 0.5px; margin-bottom: 18px; }

/* ── Layout ── */
.dash-row { display: flex; gap: 12px; margin-bottom: 12px; }
.dash-row.full { flex-direction: column; }

/* ── PMC Chart ── */
.dash-pmc-wrap {
  /* min-width:0 defeats the flex item's automatic minimum size. Without it the
     wrap can't shrink below the canvas's intrinsic width, so zooming in pushes
     the chart out past the right edge of the page instead of resizing it — the
     canvas has a real intrinsic width, unlike the text-filled flex items here
     that get away without it. .dash-comp-wrap below already carries the same
     fix, which is why the compliance charts never had the bug. */
  flex: 1; min-width: 0;
  background: var(--bg1); border: 1px solid var(--bdr);
  border-radius: 8px; padding: 14px 16px;
}
.dash-pmc-box { height: 200px; position: relative; }
.dash-pmc-head-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 6px; flex-wrap: wrap;
}
.dash-pmc-head-row .dash-label { margin-bottom: 0; }
.dash-pmc-range {
  display: flex; gap: 4px;
}
/* Custom legend row sits between the section label and the chart. Toggle on
   the left of the CTL/ATL/TSB swatches, all right-aligned so they hug the
   end like the built-in Chart.js legend used to. */
.dash-pmc-legend {
  display: flex; align-items: center; justify-content: flex-end;
  gap: 14px;
  margin-bottom: 8px;
  font: 10px 'JetBrains Mono', ui-monospace, monospace;
  color: var(--dim2);
}
.dash-pmc-legend-item {
  display: inline-flex; align-items: center; gap: 5px;
  background: transparent; border: none; padding: 2px 0;
  font: inherit; color: inherit;
  cursor: pointer;
  transition: opacity .12s;
}
.dash-pmc-legend-item:hover { color: var(--amber-ink); }
.dash-pmc-legend-item.hidden { opacity: 0.4; text-decoration: line-through; }
/* Colour comes inline, from the series it keys. All four call sites set
   `background` AND `border-color` to it, so the chip is a solid 10px block
   rather than the hollow outline it was until TKB-271 — border-box means the
   1px edge sits inside the 10px either way, so nothing moved. The border is
   kept because the compliance keys use the two independently: `planned` is a
   faded fill inside a full-strength edge. No default background — nothing
   renders this without naming a colour, so `transparent` here could only ever
   mean "the caller forgot". */
.dash-pmc-legend-sw {
  display: inline-block;
  width: 10px; height: 10px;
  border-radius: 2px;
  border-width: 1px; border-style: solid;
}
.dash-pmc-toggle {
  display: inline-flex; align-items: center; gap: 5px;
  cursor: pointer;
  user-select: none;
}
.dash-pmc-toggle input { cursor: pointer; accent-color: var(--amber); margin: 0; }

/* ── Plan compliance row ──
   Two half-width boxes sitting directly above the PMC chart, only while a plan
   is active. Boxes mirror .dash-pmc-wrap so the three charts read as one stack.
   Stacks to full width on narrow screens — 7 columns of dots don't survive a
   half-width phone viewport. */
.dash-comp-wrap {
  flex: 1 1 0; min-width: 0;
  background: var(--bg1); border: 1px solid var(--bdr);
  border-radius: 8px; padding: 14px 16px;
}
.dash-comp-box { height: 150px; position: relative; }
.dash-comp-legend {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end;
  gap: 6px 14px; margin-bottom: 8px;
  font: 10px 'JetBrains Mono', ui-monospace, monospace;
  color: var(--dim2);
}
.dash-comp-key { display: inline-flex; align-items: center; gap: 5px; }
.dash-comp-empty {
  margin: 6px 0 2px; max-width: 60ch;
  font-size: var(--fs-sm); line-height: 1.5; color: var(--dim2);
}

/* Session grid — a week-number gutter plus Mon–Sun. */
.dash-sess-grid {
  display: grid;
  grid-template-columns: auto repeat(7, 1fr);
  gap: 4px; align-items: center; justify-items: center;
  height: 150px; align-content: center;
}
.dash-sess-hd, .dash-sess-wk {
  font: 10px 'JetBrains Mono', ui-monospace, monospace;
  color: var(--dim2);
}
.dash-sess-wk { justify-self: end; padding-right: 4px; }
/* Stretched across its grid column, overriding the grid's justify-items:center,
   because the cell carries the tooltip. Left to shrink-wrap, the hover target
   was the 9–20px dot alone and the tooltip was effectively unreachable; the dot
   still sits centred, the target is now the whole column width. */
.dash-sess-cell {
  display: flex; align-items: center; justify-content: center;
  justify-self: stretch; align-self: stretch; width: 100%;
}

/* Drawn tooltip rather than the browser's title one, which never surfaced here
   and can't be styled to match the charts beside it. Nothing on the path to
   .dash-comp-wrap clips, so the bubble is free to sit outside the 150px grid. */
.dash-sess-cell[data-tip] { position: relative; cursor: help; }
.dash-sess-cell[data-tip]::after {
  content: attr(data-tip);
  position: absolute; bottom: calc(100% + 6px); left: 50%;
  transform: translateX(-50%);
  z-index: 30;
  padding: 4px 7px; border-radius: 4px;
  background: var(--bg3); color: var(--txt);
  border: 1px solid var(--bdr);
  box-shadow: 0 2px 8px rgba(0,0,0,.45);
  font: 11px 'JetBrains Mono', ui-monospace, monospace;
  white-space: nowrap;
  opacity: 0; visibility: hidden;
  transition: opacity .08s ease;
  pointer-events: none;   /* never let the bubble steal its own hover */
}
.dash-sess-cell[data-tip]:hover::after { opacity: 1; visibility: visible; }
/* Top row has no headroom inside the grid box — drop its bubble below instead. */
.dash-sess-cell[data-w="0"][data-tip]::after {
  bottom: auto; top: calc(100% + 6px);
}
/* Mon/Tue and Sat/Sun bubbles would hang off the card, so anchor them inward
   instead of centring on a dot that sits near the edge. */
.dash-sess-cell[data-dow="0"][data-tip]::after,
.dash-sess-cell[data-dow="1"][data-tip]::after { left: 0; transform: none; }
.dash-sess-cell[data-dow="5"][data-tip]::after,
.dash-sess-cell[data-dow="6"][data-tip]::after { left: auto; right: 0; transform: none; }

/* Shape is the primary encoding — filled / ring / small — so the states stay
   distinguishable without colour. Colour reinforces. In the session grid, dots
   also carry planned Load in their size: sessionDotHtml() overrides the width
   and height below inline, so this 9px is the legend size and the size a
   session with no Load estimate falls back to. */
.dash-dot {
  display: inline-block; width: 9px; height: 9px;
  border-radius: 50%; box-sizing: border-box;
}
.dash-dot.done     { background: var(--green); }
.dash-dot.missed   { border: 1.5px solid var(--red); background: transparent; }
.dash-dot.upcoming { border: 1.5px solid var(--dim2); background: transparent; }
.dash-dot.rest     { width: 3px; height: 3px; background: var(--dim2); }

@media (max-width: 800px) {
  .dash-comp-row { flex-direction: column; }
}
.dash-pmc-toggle:hover { color: var(--amber-ink); }

/* ── Section titles ── */
.dash-label {
  font-size: var(--fs-xs); letter-spacing: 2px; text-transform: uppercase;
  color: var(--dim2); margin-bottom: 8px;
}

/* ═══ THREE COLUMN ROW ═══════════════════════════════════════════════════════ */
.dash-col { flex: 1; min-width: 0; }
.dash-box {
  background: var(--bg1); border: 1px solid var(--bdr);
  border-radius: 8px; padding: 14px 16px; height: 100%;
}

/* ── Today ── */
.dash-sched-item {
  display: flex; align-items: flex-start; gap: 8px;
  padding: 10px 12px; background: var(--bg2); border-radius: 6px;
  margin-bottom: 6px; font-size: var(--fs-base);
}
.dash-sched-item:last-child { margin-bottom: 0; }
.dash-sched-row { display: flex; align-items: center; gap: 8px; }
.dash-sched-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; }
.dash-sched-sub { font-size: var(--fs-xs); color: var(--dim2); margin-top: 3px; white-space: nowrap; }

/* ── Shared workout profile thumb ── */
.dash-thumb-wrap {
  margin-top: 6px;
  background: var(--bg0);
  border: 1px solid var(--bdr);
  border-radius: 4px;
  overflow: hidden;
}
.dash-thumb { display: block; width: 100%; height: 120px; }
.dash-thumb.empty {
  background: repeating-linear-gradient(45deg, var(--bg0), var(--bg0) 6px, var(--bg1) 6px, var(--bg1) 12px);
}
.dash-thumb-inner {
  position: relative;
  cursor: crosshair;
}
.dash-ftp-lbl {
  position: absolute;
  right: 4px;
  transform: translateY(-50%);
  font: 700 10px 'JetBrains Mono', monospace;
  color: var(--amber-ink);
  opacity: 0.8;
  pointer-events: none;
  text-shadow: 0 0 3px rgba(0,0,0,.6);
}
.dash-cursor-line {
  display: none;
  position: absolute;
  top: 0; bottom: 0; width: 1px;
  background: color-mix(in srgb, var(--txt) 55%, transparent);
  pointer-events: none;
}
.dash-cursor-tip {
  display: none;
  position: absolute;
  top: 4px;
  white-space: nowrap;
  padding: 3px 6px;
  border-radius: 3px;
  font-family: var(--font-data);
  line-height: 1.35;
  color: var(--bg0);
  pointer-events: none;
  box-shadow: 0 2px 6px rgba(0,0,0,.4);
}
.dash-tip-pwr  { font-size: var(--fs-xs); font-weight: 700; }
.dash-tip-pwr .dash-tip-pct { font-size: var(--fs-xs); font-weight: 600; opacity: .72; }
.dash-tip-zone { font-size: var(--fs-xs); font-weight: 600; opacity: .85; }
.dash-tip-meta { font-size: var(--fs-xs); opacity: .68; }
.dash-badge {
  font-size: var(--fs-micro); letter-spacing: 1px; text-transform: uppercase;
  padding: 3px 7px; border-radius: 3px; background: var(--bg3); color: var(--dim);
  flex-shrink: 0;
}
.dash-badge.done { background: color-mix(in srgb, var(--green) 20%, transparent); color: var(--green); }
.dash-go {
  background: var(--amber); color: var(--on-amber); border: none;
  padding: 5px 12px; border-radius: 3px; font-size: var(--fs-xs); font-weight: 700;
  font-family: inherit; cursor: pointer; letter-spacing: 1px;
  text-transform: uppercase; flex-shrink: 0;
}
.dash-go:hover { filter: brightness(1.1); }
.dash-nil { color: var(--dim2); font-size: var(--fs-sm); font-style: italic; }

/* ── Weekly TSS ── */
.dash-big { font-size: var(--fs-hero); font-weight: 800; color: var(--amber-ink); line-height: 1; }
.dash-big-sub { font-size: var(--fs-xs); color: var(--dim2); margin-top: 4px; letter-spacing: 0.5px; }

/* ── Quick stats ── */
.dash-stats { display: flex; gap: 8px; }
.dash-stat {
  flex: 1; text-align: center; padding: 10px 6px;
  background: var(--bg2); border-radius: 6px;
}
.dash-stat-v { font-size: var(--fs-lg); font-weight: 700; color: var(--txt); }
.dash-stat-k {
  font-size: var(--fs-micro); letter-spacing: 1px; text-transform: uppercase;
  color: var(--dim2); margin-top: 3px;
}

/* ═══ COACH CHAD ═════════════════════════════════════════════════════════════ */
.dash-chad { flex-direction: column; align-items: stretch; }
.dash-chad-cols {
  display: flex; gap: 12px; align-items: stretch; flex: 1;
}
.dash-chad-cols .dash-col { flex: 1; min-width: 0; display: flex; }
.dash-chad-cols .dash-box { flex: 1; }
.dash-chad-insights-col { flex: 0 0 38% !important; }
.dash-chad-today-col    { flex: 1 !important; }
.dash-chad-subhead {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin-bottom: 10px; flex-wrap: wrap;
}
.dash-chad-subhead .dash-label { margin-bottom: 0; }
.dash-chad-subhead-l { display: flex; flex-direction: column; gap: 2px; }
.dash-chad-subhead-r { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

.dash-chad-plan-meta {
  display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap;
  font-size: var(--fs-xs);
}
.dash-chad-plan-name { color: var(--txt); font-weight: 600; }
.dash-chad-plan-week {
  color: var(--dim2); text-transform: uppercase; letter-spacing: 1px;
}
/* This week's session compliance, as one glanceable number. The 8-week detail
   lives in .dash-comp-row above the PMC chart. */
.dash-plan-extras {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  margin: 2px 0 8px;
}
.dash-plan-hero {
  font-size: var(--fs-xl); font-weight: 600; color: var(--txt);
  line-height: 1.1; font-family: var(--font-data); font-variant-numeric: tabular-nums;
}
.dash-plan-hero-sub {
  font-size: var(--fs-micro); color: var(--dim2);
  text-transform: uppercase; letter-spacing: 0.5px; line-height: 1.4;
}

.dash-budget-chips { flex-shrink: 0; }
.dash-budget-chip { background: var(--bg2); }
.dash-budget-chip.active { background: var(--amber); border-color: var(--amber); color: var(--on-amber); }
.dash-chad-card {
  position: relative;
  background: var(--bg2);
  border: 1px solid var(--bdr);
  border-radius: 6px;
  padding: 10px 28px 10px 12px;
  margin-bottom: 8px;
}
/* No severity colour on the container. The card's kind is the .tk-eyebrow
   inside it (core/insight-kind.js), coloured by --kind, which core/kind.css
   sets from data-severity — the one map the coach panel reads too. The 6px
   coloured edge that used to be here was the only carrier of severity, and
   the tell that made the column look machine-made (TKF-310). */
.dash-chad-card:last-child { margin-bottom: 0; }
.dash-chad-title { font-size: var(--fs-base); font-weight: 600; color: var(--txt); margin-bottom: 4px; }
/* pre-line, not normal: insight bodies are built server-side as one line per
   item (implode("\n", $lines) in lib/insight_render.php). A multi-record PB
   card — "1K: 8:34 — best this season", "1 mile: 13:54 — …" — collapsed into a
   single run-on paragraph without this. pre-line keeps the newlines and still
   collapses runs of spaces, so the text stays escaped and no markup is needed. */
.dash-chad-body  { font-size: var(--fs-sm); color: var(--dim); line-height: 1.45; white-space: pre-line; }

/* ── Readiness meter ──────────────────────────────────────────────────────
   Segmented band scale: four zones sized to their score range, with a marker
   at today's value. Zone colours come from the server (readiness_band_scale)
   via --seg, so the dashboard and the daily-brief email share one definition.
   Colour is never the sole carrier — each zone is labelled, and the active
   band is named in the card title. */
.dash-rdy { margin: 8px 0 4px; }
.dash-rdy-mark { position: relative; height: 9px; }
/* The caret is nudged back by half its own width so it points AT the value,
   not just after it. */
.dash-rdy-mark span {
  position: absolute; top: 2px; margin-left: -4px;
  width: 0; height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid var(--txt);
}
.dash-rdy-bar { display: flex; height: 8px; border-radius: 4px; overflow: hidden; }
.dash-rdy-seg {
  background: var(--seg);
  /* Inactive zones stay the right hue but recede, so the live band reads
     first. A 2px surface-coloured gap separates adjacent fills. */
  opacity: 0.32;
  border-right: 2px solid var(--bg1, var(--bg0));
}
.dash-rdy-seg:last-child { border-right: 0; }
.dash-rdy-seg.is-on { opacity: 1; }
.dash-rdy-labels { display: flex; margin-top: 3px; }
.dash-rdy-lbl {
  font-size: var(--fs-xs); line-height: 1.2; color: var(--dim2);
  white-space: nowrap; overflow: hidden;
}
.dash-rdy-lbl.is-on { color: var(--txt); font-weight: 700; }
.dash-rdy-lbl i { font-style: normal; opacity: 0.75; }

/* ── Readiness contribution breakdown ─────────────────────────────────────
   Diverging bars: each signal's signed points either side of a centre rule.
   Blue↔red (never green↔red — that pair is indistinguishable to deuteranopes),
   and polarity is also carried by which side of the line the bar sits on and
   by the signed number, so it reads with no colour at all. */
.dash-rdyb { margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--bdr, var(--bg3)); }
.dash-rdyb-row { display: flex; align-items: center; gap: 0; margin: 3px 0; }
.dash-rdyb-lbl { flex: 0 0 34%; font-size: var(--fs-xs); color: var(--dim); white-space: nowrap;
                 overflow: hidden; text-overflow: ellipsis; padding-right: 6px; }
/* The raw reading vs its baseline — recessive, but present, so a rider can see
   why a good-looking number scored negative. */
.dash-rdyb-lbl i { font-style: normal; color: var(--dim2); }
.dash-rdyb-arm { flex: 0 0 26%; height: 8px; display: flex; }
/* The negative arm fills from the centre outwards, so its bar is pinned right. */
.dash-rdyb-neg { justify-content: flex-end; border-right: 1px solid var(--bdr, var(--bg3)); }
.dash-rdyb-arm i { display: block; height: 100%; border-radius: 2px; }
.dash-rdyb-neg i { background: #d03b3b; }
.dash-rdyb-pos i { background: #2a78d6; }
.dash-rdyb-val { flex: 1 1 auto; font-size: var(--fs-xs); text-align: right; padding-left: 6px;
                 color: #2a78d6; white-space: nowrap; }
.dash-rdyb-val.is-neg { color: #d03b3b; }
/* Breathing room before the card's prose, which follows the meter/breakdown
   directly. Scoped to the adjacent-sibling case so ordinary insight cards —
   where the body sits right under the title — are untouched. */
.dash-rdy + .dash-chad-body,
.dash-rdyb + .dash-chad-body { margin-top: 10px; }

/* ── Form scale ───────────────────────────────────────────────────────────
   Sits under the PMC chart and gives its Form line the reference points the
   chart itself can't: where the thresholds are and which side of them today
   sits on. Same construction as the readiness meter above — zones sized to
   their range, marker at the value, every zone named in text — because it
   answers the same shape of question and shouldn't look like a new idea.
   Ticks are absolutely positioned on the boundaries rather than flowed with
   the segments: they label the EDGES, not the zones. */
.dash-fs { margin: 12px 2px 2px; }
.dash-fs-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 7px; }
.dash-fs-title {
  font-size: var(--fs-micro); letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--dim2);
}
.dash-fs-val { font-size: var(--fs-md); font-weight: 700; font-family: var(--font-data); }
.dash-fs-band { font-size: var(--fs-xs); color: var(--txt); font-weight: 600; }
.dash-fs-meter { max-width: 420px; }
.dash-fs-mark { position: relative; height: 9px; }
/* Nudged back by half its own width so the caret points AT the value. */
.dash-fs-mark span {
  position: absolute; top: 2px; margin-left: -4px;
  width: 0; height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid var(--txt);
}
.dash-fs-bar { display: flex; height: 8px; border-radius: 4px; overflow: hidden; }
.dash-fs-seg {
  background: var(--seg);
  opacity: 0.32;
  border-right: 2px solid var(--bg1, var(--bg0));
}
.dash-fs-seg:last-child { border-right: 0; }
.dash-fs-seg.is-on { opacity: 1; }
.dash-fs-ticks { position: relative; height: 12px; margin-top: 3px; }
.dash-fs-tick {
  position: absolute; transform: translateX(-50%);
  font-size: var(--fs-xs); line-height: 1.2; color: var(--dim2);
  font-family: var(--font-data); white-space: nowrap;
}
.dash-fs-note {
  margin: 8px 0 0; max-width: 640px;
  font-size: var(--fs-xs); line-height: 1.5; color: var(--dim);
}
.dash-fs-note.dash-fs-dim { color: var(--dim2); margin-top: 5px; }

.dash-chad-stats { margin-top: 2px; gap: 14px; }
/* Pinned to the card's top-right corner (the card is position:relative, and its
   28px right padding reserves the space). Without this the button falls back
   into normal flow and lands above the title on the left. */
.dash-chad-dismiss {
  position: absolute; top: 4px; right: 6px;
}

/* ═══ SOURCE EYEBROW ═════════════════════════════════════════════════════════
   Small uppercase label above the card title telling the rider where the
   suggestion came from: their plan, a smart pick from the prescription, or
   freshly generated. Different colour per source so the visual cue is
   instant — riders learn "amber = generated/picked-by-the-system" vs
   "blue = my plan". */
.dash-source-eyebrow {
  font-size: var(--fs-micro);
  letter-spacing: 2px;
  text-transform: uppercase;
  font-weight: 700;
  margin-bottom: 4px;
}
.dash-source-eyebrow.source-plan      { color: var(--blue, var(--blue)); }
.dash-source-eyebrow.source-smart     { color: var(--amber-ink); }
.dash-source-eyebrow.source-route     { color: var(--blue, var(--blue)); }
.dash-source-eyebrow.source-generated { color: var(--amber-ink); }

/* ── Alternatives on a Today card ──
   Three same-zone library picks shown side-by-side as full mini-cards with
   preview thumb + Start ride / Push to Garmin buttons. Each alt is a real
   saved workout (numeric id) so the buttons reuse the same delegated
   handlers as the primary card. */
.dash-chad-alts {
  margin-top: 14px;
}
.dash-chad-alts-head {
  cursor: pointer;
  list-style: none;
  font-size: var(--fs-micro); font-weight: 700; letter-spacing: 1.5px;
  text-transform: uppercase; color: var(--amber-ink);
  padding: 2px 0;
}
.dash-chad-alts-head::-webkit-details-marker { display: none; }
.dash-chad-alts-head::before {
  content: '▸';
  display: inline-block;
  margin-right: 6px;
  transition: transform .12s;
}
.dash-chad-alts[open] .dash-chad-alts-head::before { transform: rotate(90deg); }
.dash-chad-alts-head:hover { filter: brightness(1.2); }
/* Time budget, inside the disclosure with the alternatives it filters. It is
   a CEILING — coach.php sets dur_hi = budget — so the label says "Time I
   have", not "Duration": read as a target, a correct 25-minute answer to a
   30-minute budget looks like a bug. */
.dash-chad-budget-row {
  display: flex; align-items: center; gap: 8px;
  flex-wrap: wrap;
  margin-top: 8px;
}
.dash-chad-budget-lbl {
  font-size: var(--fs-micro); letter-spacing: 1px; text-transform: uppercase;
  color: var(--dim3, var(--bdr2));
}
/* A card with no alternatives renders the row on its own — no disclosure to
   sit inside, but the control still has to be reachable. */
.dash-chad-alts > .dash-chad-budget-row:first-child { margin-top: 0; }

.dash-chad-alts-grid {
  display: grid; gap: 10px;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin-top: 8px;
}
.dash-chad-alt-card {
  background: var(--bg1);
  border: 1px solid var(--bdr);
  border-radius: 6px;
  padding: 10px 12px;
  display: flex; flex-direction: column; gap: 6px;
  min-width: 0;
}
.dash-chad-alt-card .dash-thumb { height: 90px; }
.dash-chad-alt-card .dash-yp-today-actions { margin-top: auto; }
.dash-chad-alt-title {
  font-size: var(--fs-sm); font-weight: 600; color: var(--txt);
  line-height: 1.3;
  overflow: hidden; text-overflow: ellipsis;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
@media (max-width: 900px) {
  .dash-chad-alts-grid { grid-template-columns: 1fr; }
}

/* ═══ PRESCRIPTION DISCLOSURE ════════════════════════════════════════════════
   "because: <reason>" subtitle + "?" inputs popover. Used on both Today's
   Optimal and Coach Chad Today so the rider can see what triggered the
   recommendation and the signals that drove it. */
.dash-rx-because {
  position: relative;
  display: flex; align-items: center; gap: 6px;
  margin: 2px 0 8px;
  font-size: var(--fs-sm); color: var(--dim);
}
.dash-rx-because-text { font-style: italic; }
.dash-rx-info {
  background: transparent;
  border: 1px solid var(--bdr);
  color: var(--dim);
  width: 16px; height: 16px;
  border-radius: 50%;
  font-size: var(--fs-xs); font-weight: 700;
  font-family: inherit;
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer;
  padding: 0;
  transition: color .12s, border-color .12s;
}
.dash-rx-info:hover { color: var(--amber-ink); border-color: var(--amber-ink); }
.dash-rx-popover[hidden] { display: none; }
.dash-rx-popover {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  z-index: 50;
  min-width: 200px;
  background: var(--bg2);
  border: 1px solid var(--bdr2);
  border-radius: 6px;
  padding: 10px 12px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.5);
  font-style: normal;
}
.dash-rx-pop-title {
  font-size: var(--fs-micro); letter-spacing: 1.5px; text-transform: uppercase;
  color: var(--amber-ink); font-weight: 700; margin-bottom: 6px;
}
.dash-rx-pop-row {
  display: flex; justify-content: space-between; gap: 16px;
  font-size: var(--fs-sm); padding: 2px 0;
}
.dash-rx-pop-k { color: var(--dim); }
.dash-rx-pop-v { color: var(--txt); font-weight: 600; }

/* ═══ COACH CHAD — supporting bits ═════════════════════════════════════════ */
/* Inline link inside a Chad insight card — used by the synthetic
   "Browse plans" nudge so the rider has a clear next step. */
.dash-chad-link {
  background: none; border: none;
  color: var(--amber-ink); font-family: inherit; font-size: var(--fs-sm);
  font-weight: 600; cursor: pointer; padding: 6px 0 0;
  text-align: left;
}
.dash-chad-link:hover { text-decoration: underline; }

.dash-cc-notes {
  flex: 1;
  background: var(--bg1);
  border: 1px solid var(--bdr);
  border-radius: 8px;
  padding: 12px 16px;
}
.dash-cc-notes-head {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 6px;
}
.dash-cc-notes-title {
  font-size: var(--fs-micro); font-weight: 700; letter-spacing: 1.5px;
  text-transform: uppercase; color: var(--amber-ink);
}
.dash-cc-notes-list {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: 4px;
}
.dash-cc-note {
  display: flex; align-items: center; gap: 8px;
  font-size: var(--fs-sm); color: var(--txt);
  padding: 4px 0;
  line-height: 1.4;
}
.dash-cc-note-icon { flex-shrink: 0; font-size: var(--fs-base); line-height: 1; }
.dash-cc-note-text { flex: 1; }
.dash-cc-note-link {
  background: none; border: none;
  color: var(--amber-ink); font-family: inherit; font-size: var(--fs-sm);
  font-weight: 600; cursor: pointer; padding: 0;
}
.dash-cc-note-link:hover { text-decoration: underline; }
.dash-cc-note-action .dash-cc-note-text {
  color: var(--dim);
}
.dash-yp-start {
  background: var(--amber); color: var(--on-amber); border: 1px solid var(--amber);
  border-radius: 5px;
  padding: 8px 14px;
  font-family: inherit;
  font-size: var(--fs-sm);
  font-weight: 700;
  letter-spacing: 0.5px;
  cursor: pointer;
  transition: filter .12s;
}
.dash-yp-start:hover { filter: brightness(1.1); }

.dash-yp-edit {
  background: transparent;
  color: var(--dim);
  border: 1px solid var(--bdr2);
  border-radius: 5px;
  padding: 6px 12px;
  font-family: inherit;
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.5px;
  cursor: pointer;
  transition: all .12s;
}
.dash-yp-edit:hover { border-color: var(--amber-ink); color: var(--amber-ink); }

.dash-yp-today-actions {
  display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
  margin-top: 4px;
}
.dash-yp-start { flex-shrink: 0; }
/* Load in Climbs (TKF-378): the secondary action beside Load in Player —
   same size, outlined, so the Player stays the default. Only rendered where
   the Climbs module is (dev-only today). */
.dash-yp-climbs {
  flex-shrink: 0;
  background: transparent;
  color: var(--amber-ink);
  border: 1px solid var(--amber-ink);
  border-radius: 5px;
  padding: 8px 14px;
  font-family: inherit;
  font-size: var(--fs-sm);
  font-weight: 700;
  letter-spacing: 0.5px;
  cursor: pointer;
  transition: all .12s;
}
.dash-yp-climbs:hover { background: color-mix(in srgb, var(--amber) 12%, transparent); }
/* .dash-yp-push-garmin styles removed with the button — Garmin delivery moves
   to the Edge Connect IQ app (same call as route-manager.js). */

/* ═══ RESPONSIVE ═════════════════════════════════════════════════════════════ */
@media (max-width: 900px) {
  .dash-row.trio { flex-wrap: wrap; }
  .dash-row.trio .dash-col { flex: 1 1 45%; }
  .dash-row.trio .dash-col:last-child { flex: 1 1 100%; }
}
@media (max-width: 600px) {
  .dash-wrap { padding: 14px; }
  .dash-row { flex-direction: column; }
  .dash-fitness-strip { flex-wrap: wrap; }
  .dash-fs-cell { min-width: 30%; }
  .dash-week { flex-wrap: nowrap; gap: 0; }
}

/* ═══ TODAY'S ROUTE ═══════════════════════════════════════════════════════════ */
/* Map beside the metadata, stacking under ~640px. The map keeps route-thumb's
   own 2:1 aspect so the served 400×200 JPEG is never letterboxed or stretched. */
.dash-route-wrap {
  flex: 1; min-width: 0;
  background: var(--bg1); border: 1px solid var(--bdr);
  border-radius: 8px; padding: 14px 16px;
}
.dash-route-card {
  display: flex; gap: 14px; align-items: flex-start;
}
.dash-route-card + .dash-route-card {
  margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--bg2);
}
.dash-route-map {
  flex: 0 0 auto; width: 260px; aspect-ratio: 2 / 1;
  border-radius: 6px; overflow: hidden; display: block;
  background: var(--bg2); border: 1px solid var(--bdr);
}
.dash-route-map img { width: 100%; height: 100%; object-fit: cover; display: block; }
.dash-route-meta { flex: 1; min-width: 0; }
.dash-route-title { font-size: var(--fs-md); font-weight: 600; color: var(--txt); margin-bottom: 4px; }
.dash-route-bits {
  font: 12px 'JetBrains Mono', ui-monospace, monospace;
  color: var(--dim); margin-bottom: 8px;
}
.dash-route-notes {
  font-size: var(--fs-sm); color: var(--dim2); line-height: 1.5;
  margin-bottom: 8px; max-width: 60ch;
}
.dash-route-open { font-size: var(--fs-sm); padding: 7px 14px; }

/* Scheduled route inside the Coach · Today column. Compact list-row layout —
   small map beside the metadata — because the column is far narrower than the
   old full-width row it used to live in. Takes the same chrome as its
   .dash-chad-card neighbours; its "Scheduled route" eyebrow says what it is. */
.dash-chad-routes .dash-route-card {
  gap: 12px;
  align-items: center;
  background: var(--bg2);
  border: 1px solid var(--bdr);
  border-radius: 6px;
  padding: 10px 12px;
  margin-bottom: 8px;
}
.dash-chad-routes .dash-route-card + .dash-route-card { margin-top: 0; padding-top: 10px; border-top: 0; }
/* Fixed, modest map — NOT width:100%. In this column that would stretch the
   2:1 map to the full width and let one route dominate the whole panel; this
   keeps it to roughly the workout thumbnail's visual weight beside it. */
.dash-chad-routes .dash-route-map { flex: 0 0 150px; width: 150px; }
.dash-chad-routes .dash-route-meta { flex: 1 1 auto; min-width: 0; }
.dash-chad-routes .dash-route-title { font-size: var(--fs-base); }
.dash-chad-routes .dash-route-bits,
.dash-chad-routes .dash-route-notes { margin-bottom: 6px; }
.dash-chad-routes .dash-route-notes {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; margin-bottom: 6px;
}
/* Narrow panels: stack rather than crush the metadata. */
@media (max-width: 900px) {
  .dash-chad-routes .dash-route-card { flex-direction: column; align-items: stretch; }
  .dash-chad-routes .dash-route-map { flex: 0 0 auto; width: 100%; max-width: 240px; }
}

@media (max-width: 640px) {
  .dash-route-card { flex-direction: column; }
  .dash-route-map { width: 100%; }
}

/* ═══ YOUR ROUTES (TKF-440) ═══════════════════════════════════════════════════ */
/* The Plan pillar's row: the last-edited route large on the left, recent routes
   as thumbnails beside it ending in + New route, and what the calendar holds
   this week. Maps keep route-thumb's 2:1 like the Today card, so the 400×200
   JPEG is never letterboxed.
   The Continue card is only as wide as its content and the thumbnails start
   right after it. The thumbnail grid fills the rest in tracks of at least 180px,
   and fitRouteTiles() (dashboard.js) shows as many routes as fit on one line:
   a wide monitor shows more routes, a narrow window fewer, never smaller ones.
   Sizes switch on the ROW's width (a container query), not the viewport's,
   because the left sidebar takes a varying share of the window. */
.dash-routes {
  flex: 1; min-width: 0; container-type: inline-size;
  background: var(--bg1); border: 1px solid var(--bdr);
  border-radius: 8px; padding: 14px 16px;
}
.dash-routes-head { display: flex; align-items: center; gap: 12px; margin-bottom: 8px; }
.dash-routes-head .dash-label { margin-bottom: 0; }
.dash-routes-all {
  margin-left: auto; background: none; border: 0; padding: 2px 0;
  font: inherit; font-size: var(--fs-sm); color: var(--dim); cursor: pointer;
}
.dash-routes-all:hover { color: var(--amber-ink); }

.dash-routes-body { display: flex; gap: 24px; align-items: flex-start; }
.dash-routes-main { flex: 0 0 auto; display: flex; gap: 14px; align-items: flex-start; }
.dash-routes-map {
  flex: 0 0 auto; width: 300px; aspect-ratio: 2 / 1;
  border-radius: 6px; overflow: hidden; display: block;
  background: var(--bg2); border: 1px solid var(--bdr);
}
.dash-routes-map:hover { border-color: var(--amber-ink); }
.dash-routes-map img { width: 100%; height: 100%; object-fit: cover; display: block; }
.dash-routes-meta { flex: 0 0 240px; min-width: 0; }
.dash-routes-title-row {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; margin-bottom: 4px;
}
.dash-routes-title-row .dash-route-title { margin-bottom: 0; min-width: 0; overflow-wrap: anywhere; }
/* Same pill as the planner's 🔗 Shared tag, read-only here. */
.dash-routes-shared {
  padding: 1px 8px; border-radius: 999px; white-space: nowrap;
  border: 1px solid color-mix(in srgb, var(--amber-ink) 55%, transparent);
  color: var(--amber-ink); font-size: var(--fs-xs); font-weight: 600;
}
.dash-routes-meta .dash-route-bits { margin-bottom: 2px; }
.dash-routes-when { font-size: var(--fs-xs); color: var(--dim2); margin-bottom: 10px; }

.dash-routes-side { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 12px; }
/* auto-fill, not auto-fit: the empty tracks stay, so a rider with two routes gets
   two normal-sized thumbnails, not two stretched across the row. */
.dash-routes-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 10px; }
.dash-routes-tile {
  display: flex; flex-direction: column; gap: 3px; min-width: 0;
  background: none; border: 0; padding: 0; margin: 0; text-align: left;
  font: inherit; color: inherit; text-decoration: none; cursor: pointer;
}
.dash-routes-tile-map {
  display: block; aspect-ratio: 2 / 1; border-radius: 6px; overflow: hidden;
  background: var(--bg2); border: 1px solid var(--bdr);
}
.dash-routes-tile-map img { width: 100%; height: 100%; object-fit: cover; display: block; }
.dash-routes-tile:hover .dash-routes-tile-map { border-color: var(--amber-ink); }
.dash-routes-tile[hidden] { display: none; }
.dash-routes-tile:focus-visible { outline: 2px solid var(--amber-ink); outline-offset: 2px; border-radius: 6px; }
.dash-routes-tile-title {
  font-size: var(--fs-sm); color: var(--txt);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dash-routes-tile-bits {
  font: 11px 'JetBrains Mono', ui-monospace, monospace; color: var(--dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dash-routes-plus {
  display: flex; align-items: center; justify-content: center;
  border-style: dashed; border-color: var(--bdr2);
  font-size: 22px; line-height: 1; color: var(--dim);
}
.dash-routes-new:hover .dash-routes-plus { color: var(--amber-ink); border-color: var(--amber-ink); }

.dash-routes-sub {
  font-size: var(--fs-micro); letter-spacing: 2px; text-transform: uppercase;
  font-weight: 700; color: var(--dim2); margin-bottom: 4px;
}
.dash-routes-upcoming ul { list-style: none; margin: 0; padding: 0; }
.dash-routes-upcoming a {
  display: flex; align-items: baseline; gap: 10px;
  padding: 5px 0; border-top: 1px solid var(--bdr2);
  color: inherit; text-decoration: none; font-size: var(--fs-sm);
}
.dash-routes-upcoming li:first-child a { border-top: 0; }
.dash-routes-upcoming a:hover .dash-routes-up-title { color: var(--amber-ink); }
.dash-routes-day {
  flex: 0 0 auto; min-width: 9ch; color: var(--dim);
  font-family: var(--font-data); font-size: var(--fs-xs);
}
.dash-routes-up-title { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dash-routes-up-bits {
  flex: 0 0 auto; color: var(--dim);
  font: 11px 'JetBrains Mono', ui-monospace, monospace;
}

/* No routes yet, and the onboarding card is not there to say it. */
.dash-routes-empty-body {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  font-size: var(--fs-sm); color: var(--dim);
}
.dash-routes-empty-body .dash-yp-start { margin-left: auto; }

/* The Fitness chart with no rides behind it: one line instead (TKF-440). */
.dash-pmc-empty-body {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  font-size: var(--fs-sm); color: var(--dim);
}
.dash-pmc-empty-body .dash-yp-edit { margin-left: auto; }

/* Stack unless three thumbnails fit beside the card: 554 (map + details) + 24 gap
   + 3 × 180 + 2 × 10 = 1138. With only two beside it, they wrap to a 2 × 2 block
   taller than the card and leave a hole under it; stacked, they fill a line. */
@container (max-width: 1137px) {
  .dash-routes-body { flex-direction: column; }
  .dash-routes-side { flex-basis: auto; width: 100%; }
}
@container (max-width: 560px) {
  .dash-routes-main { flex-direction: column; width: 100%; }
  .dash-routes-meta { flex-basis: auto; }
  .dash-routes-map { width: 100%; }
  .dash-routes-tiles { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
}

/* Next event (TKF-301) — the soonest dated pace plan and how ready the rider is
   for it. Above the PMC because a date is the most time-sensitive thing on the
   page; absent entirely when no saved plan carries one. */
.dash-ev {
  padding: 12px 14px; border: 1px solid var(--bdr); border-radius: 10px;
  background: var(--bg1); cursor: pointer; width: 100%; box-sizing: border-box;
}
.dash-ev:hover { background: var(--bg2); }
.dash-ev:focus-visible { outline: 2px solid var(--amber-ink); outline-offset: -2px; }
.dash-ev-main {
  display: flex; flex-wrap: wrap; gap: 12px;
  align-items: baseline; justify-content: space-between; margin-top: 4px;
}
.dash-ev-name { font-size: var(--fs-md); }
.dash-ev-when { font-size: var(--fs-sm); color: var(--dim); font-family: var(--font-data); margin-top: 2px; }
.dash-ev-right { text-align: right; }
.dash-ev-sub { display: block; font-size: var(--fs-xs); color: var(--dim); margin-top: 3px; }
.dash-ev-chip {
  display: inline-block; padding: 2px 9px; border-radius: 9px;
  font-size: var(--fs-micro); text-transform: uppercase; letter-spacing: .04em;
}
.dash-ev-chip.dash-ev-clear { background: rgba(var(--amber-wash), .10); color: var(--green); }
.dash-ev-chip.dash-ev-limit { background: rgba(var(--amber-wash), .16); color: var(--on-amber-wash); }
.dash-ev-chip.dash-ev-short { background: rgba(var(--amber-wash), .10); color: var(--red); }

/* Onboarding checklist (TKF-304) — the four steps that make TourKit work, in
   the order they build on each other. Removes itself when all four are done, so
   an established rider never sees it and never has to dismiss it. */
.dash-ob {
  padding: 12px 14px 6px; border: 1px solid var(--bdr); border-radius: 10px;
  background: var(--bg1); width: 100%; box-sizing: border-box;
}
.dash-ob-head { display: flex; align-items: center; gap: 10px; }
.dash-ob-count { font-size: var(--fs-xs); color: var(--dim); font-family: var(--font-data); }
.dash-ob-hide {
  margin-left: auto; background: none; border: 0; color: var(--dim);
  font-size: var(--fs-md); line-height: 1; cursor: pointer; padding: 0 2px;
}
.dash-ob-hide:hover { color: var(--txt); }

.dash-ob-list { list-style: none; margin: 8px 0 0; padding: 0; counter-reset: none; }
.dash-ob-step {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 7px 0; border-top: 1px solid var(--bdr2);
}
.dash-ob-step:first-child { border-top: 0; }

/* The step number becomes a tick. Same box either way so the list does not
   shift sideways as steps complete. */
.dash-ob-mark {
  flex: 0 0 auto; width: 20px; height: 20px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: var(--fs-micro); font-family: var(--font-data);
  background: var(--bg3); color: var(--dim); margin-top: 1px;
}
.dash-ob-step.done .dash-ob-mark { background: var(--amber); color: var(--on-amber); }
.dash-ob-step.next .dash-ob-mark { background: var(--bg3); color: var(--txt-hi); box-shadow: inset 0 0 0 1px var(--amber-ink); }

.dash-ob-text { flex: 1 1 auto; min-width: 0; }
.dash-ob-title { display: block; font-size: var(--fs-sm); }
.dash-ob-step.done .dash-ob-title { color: var(--dim); }
/* Only the next step explains itself — four descriptions at once is a wall, and
   the completed ones no longer need one. */
.dash-ob-body { display: block; font-size: var(--fs-xs); color: var(--dim); margin-top: 3px; line-height: 1.5; }

.dash-ob-cta {
  flex: 0 0 auto; align-self: center;
  background: var(--amber); border: 1px solid var(--amber); color: var(--on-amber);
  border-radius: 8px; padding: 6px 13px; font-size: var(--fs-xs);
  font-family: inherit; cursor: pointer; white-space: nowrap;
}
.dash-ob-cta:hover { filter: brightness(1.08); }
@media (max-width: 640px) {
  .dash-ob-step { flex-wrap: wrap; }
  .dash-ob-cta { margin-left: 30px; }
}
