:root {
  --bg: #f5f6f8;
  --card: #ffffff;
  --ink: #1c2430;
  --muted: #6a7686;
  --line: #e4e8ee;
  --accent: #2f6df6;
  --accent-ink: #ffffff;
  --good: #1c9d64;
  --warn: #c9720b;
  --danger: #c53b3b;
  --anchor: #7a51e0;
  /* run-type categorical palette (validated for CVD; warm = hard, cool = easy) */
  --rt-race: #e34948;
  --rt-intervals: #5c37a3;
  --rt-tempo: #ef9f52;
  --rt-long: #1f4fa8;
  --rt-easy: #2e9e56;
  --rt-recovery: #269fd8;
  --rt-other: #e87ba4;
  --radius: 12px;
  --shadow: 0 1px 2px rgba(20, 30, 45, .06), 0 4px 16px rgba(20, 30, 45, .05);
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #12151b;
    --card: #1a1e26;
    --ink: #e7ebf1;
    --muted: #97a1b0;
    --line: #2a303b;
    --accent: #5b8cff;
    --good: #35c184;
    --warn: #e8a34b;
    --danger: #f07878;
    --anchor: #a488f0;
    /* same hues stepped for the dark surface (validated) */
    --rt-race: #e66767;
    --rt-intervals: #9a7ae6;
    --rt-tempo: #cb7f36;
    --rt-long: #2f5cc8;
    --rt-easy: #35a55f;
    --rt-recovery: #2f9acb;
    --rt-other: #d55181;
    --shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 6px 18px rgba(0, 0, 0, .25);
  }
}

/* Tells native controls (date/time pickers, disabled-field text, scrollbars) that both
   themes are supported so they follow the OS preference instead of defaulting to light,
   without this, a disabled date input renders its text in the browser's light-mode gray/black
   regardless of --ink, which reads as illegible near-black on this app's dark background. */
:root { color-scheme: light dark; }

* { box-sizing: border-box; }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
/* The `hidden` attribute must always win over a class's `display` (flex/grid/etc.).
   Author rules otherwise beat the UA default `[hidden]{display:none}` regardless of
   specificity, so e.g. `.inline[hidden]` would still render without this. */
[hidden] { display: none !important; }
body {
  margin: 0;
  font: 15px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  background: var(--bg);
  color: var(--ink);
}
.wrap { max-width: 1180px; margin: 0 auto; padding: 28px 18px 60px; }

header, .flow-steps, .revision-banner, .flow-screen:not(.flow-screen-wide), .footer {
  max-width: 820px;
  margin-left: auto;
  margin-right: auto;
}

header { position: relative; }
header h1 { margin: 0; font-size: 1.7rem; letter-spacing: -.02em; }
.dashboard-mode header { max-width: none; }
.sub { margin: 4px 0 0; color: var(--muted); }
header #logoutBtn { position: absolute; top: 0; right: 0; }

.flow-steps {
  /* Auto columns rather than a fixed 4, so a revision's 2 visible steps fill the row
     evenly instead of leaving two empty cells. */
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 8px;
  margin-top: 24px;
}
.flow-steps button {
  position: relative;
  height: auto;
  padding: 9px 10px;
  border-color: var(--line);
  background: color-mix(in srgb, var(--card) 75%, transparent);
  color: var(--muted);
  text-align: left;
}
.flow-steps button .step-num {
  display: inline-grid;
  place-items: center;
  width: 22px;
  height: 22px;
  margin-right: 5px;
  border: 1px solid var(--line);
  border-radius: 50%;
  font-size: .76rem;
}
.flow-steps button.active {
  border-color: var(--accent);
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, var(--card));
}
.flow-steps button.active .step-num { border-color: var(--accent); background: var(--accent); color: var(--accent-ink); }
.flow-steps button:disabled { cursor: not-allowed; opacity: .45; }
.flow-screen { animation: flow-in .18s ease-out; }
.flow-actions {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  margin-top: 18px;
}
.flow-actions-end { justify-content: flex-end; }
@keyframes flow-in {
  from { opacity: .4; transform: translateY(5px); }
  to { opacity: 1; transform: translateY(0); }
}

.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 20px;
  margin-top: 20px;
}
.card h2 { margin: 0 0 6px; font-size: 1.08rem; }
.card-head { display: flex; align-items: center; justify-content: space-between; }
.hint { color: var(--muted); font-size: .88rem; }
.hint code { background: rgba(127, 127, 127, .14); padding: 1px 5px; border-radius: 5px; }

.drop {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 4px;
  border: 1.5px dashed var(--line);
  border-radius: var(--radius);
  padding: 26px; margin: 14px 0;
  text-align: center; cursor: pointer;
  transition: border-color .15s, background .15s;
}
.drop:hover, .drop.over { border-color: var(--accent); background: rgba(95, 140, 255, .06); }
.drop.processing {
  pointer-events: none;
  cursor: wait;
  border-style: solid;
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, var(--card));
}
.drop.processing .drop-main::before {
  content: "";
  display: inline-block;
  width: 12px;
  height: 12px;
  margin-right: 8px;
  border: 2px solid color-mix(in srgb, var(--accent) 30%, transparent);
  border-top-color: var(--accent);
  border-radius: 50%;
  vertical-align: -2px;
  animation: spin .8s linear infinite;
}
.drop-main { font-weight: 600; }
.drop-main u { text-decoration-color: var(--accent); }
.drop-sub { color: var(--muted); font-size: .84rem; }

.row { display: flex; align-items: flex-end; gap: 14px; flex-wrap: wrap; }
.field { display: flex; flex-direction: column; gap: 5px; }
.field > label { font-size: .82rem; color: var(--muted); }
/* Only carries a data-tip while its input is disabled (see goal.js's revision entry/exit),
   a plain wrapper the rest of the time, so it never intercepts hover on an editable field. */
.field-tip { display: block; }
.field-tip[data-tip] input:disabled { cursor: help; }
.inline { display: flex; align-items: center; gap: 6px; }
.form-label { color: var(--muted); font-size: .9rem; }
.unit { color: var(--muted); font-size: .9rem; }

input[type=number], select {
  font: inherit;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--card);
  color: var(--ink);
  height: 38px;
}
textarea {
  width: 100%; min-height: 78px; resize: vertical;
  font: inherit; line-height: 1.4; padding: 9px 10px;
  border: 1px solid var(--line); border-radius: 8px;
  background: var(--card); color: var(--ink);
}
textarea:focus { outline: 2px solid color-mix(in srgb, var(--accent) 45%, transparent); border-color: var(--accent); }
input[type=number] { width: 84px; }

/* min-height, not height: a label that wraps on a narrow screen has to make its button taller
   rather than spill out of a fixed box. Centring keeps a one-line and a two-line button looking
   the same in a row. */
button {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  font: inherit; font-weight: 600; line-height: 1.25;
  border: 1px solid transparent; border-radius: 8px;
  padding: 8px 16px; min-height: 38px; cursor: pointer;
  text-align: center;
}
button.primary { background: var(--accent); color: var(--accent-ink); }
button.primary:disabled { opacity: .45; cursor: not-allowed; }
button.ghost { background: transparent; border-color: var(--line); color: var(--ink); }
button.small { min-height: 30px; padding: 4px 10px; font-size: .84rem; }

.stats {
  margin-top: 16px; padding: 12px 14px;
  background: rgba(127, 127, 127, .07);
  border-radius: 10px; font-size: .9rem;
}
.stats .pill { display: inline-block; margin: 3px 6px 3px 0; padding: 2px 9px; border-radius: 20px; background: var(--card); border: 1px solid var(--line); }
.stats .pill b { color: var(--accent); }

.head-actions { display: flex; gap: 8px; align-items: center; }


.summary { display: flex; align-items: flex-end; gap: 18px; flex-wrap: wrap; margin: 6px 0 14px; }
.summary .win-metrics { display: flex; gap: 18px; }
.summary .win-metrics .metric .v { color: var(--accent); }
.summary .win-arrow { color: var(--muted); align-self: center; margin-right: 5px; }

.best-run-intro { margin: 2px 0 8px; font-size: .84rem; color: var(--muted); font-weight: 600; }

/* collapsed-history summary of the runs not shown */
.rest-summary { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin: 12px 0 2px; font-size: .84rem; color: var(--muted); }
.rest-summary .lead { margin-right: 2px; }
.rest-summary .rchip { display: inline-flex; align-items: center; gap: 5px; padding: 2px 9px; border-radius: 20px; background: rgba(127, 127, 127, .09); border: 1px solid var(--line); }
.rest-summary .rchip .dot { width: 9px; height: 9px; border-radius: 3px; flex: none; }
.rest-summary .rchip b { color: var(--ink); }

/* Shared hover popover. The one tooltip mechanism used everywhere (never native `title`) */
.pop {
  position: fixed; z-index: 50; pointer-events: none;
  background: var(--card); color: var(--ink);
  border: 1px solid var(--line);
  border-radius: 8px; padding: 8px 10px; font-size: .78rem; line-height: 1.5;
  box-shadow: var(--shadow); max-width: 320px;
}
.pop .tt-head { font-weight: 700; text-transform: capitalize; margin-bottom: 3px; }
.pop .tt-date { color: var(--muted); font-size: .78rem; margin-bottom: 6px; }
.pop .tt-row { display: flex; justify-content: space-between; gap: 16px; }
.pop .tt-row .k { color: var(--muted); }
.pop .tt-foot { margin-top: 5px; padding-top: 5px; border-top: 1px solid var(--line); color: var(--muted); }
.weekbar .seg { cursor: default; }
[data-tip] { cursor: help; }
.summary .metric { display: flex; flex-direction: column; }
.summary .metric .v { font-size: 1.3rem; font-weight: 700; letter-spacing: -.01em; }
.summary .metric .k { font-size: .78rem; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }

.table-wrap { overflow-x: auto; }
table.history { width: 100%; border-collapse: collapse; font-size: .9rem; }
table.history th { text-align: left; font-size: .74rem; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); padding: 6px 8px; border-bottom: 1px solid var(--line); white-space: nowrap; }
table.history td { padding: 6px 8px; border-bottom: 1px solid var(--line); white-space: nowrap; }
table.history select { height: 30px; padding: 2px 6px; font-size: .85rem; }
table.history tr.fastest td { background: rgba(95, 140, 255, .10); }
table.history tr.fastest td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
.del { background: none; border: none; color: var(--muted); cursor: pointer; font-size: 1rem; padding: 2px 6px; height: auto; }
.del:hover { color: var(--danger); }
.type-cell { display: flex; align-items: center; gap: 6px; }
.info-ic { color: var(--muted); font-size: .8rem; opacity: .7; user-select: none; }
.info-ic:hover { opacity: 1; }
.env-ic { margin-right: 5px; }
.env-svg { vertical-align: -2px; color: var(--muted); }
.fastest-star { color: var(--accent); margin-left: 5px; }

/* baseline summary */
.warn {
  background: rgba(201, 114, 11, .12);
  border: 1px solid rgba(201, 114, 11, .35);
  color: var(--warn);
  padding: 9px 12px; border-radius: 8px; font-size: .86rem; margin: 4px 0 12px;
}
.metrics-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 10px; margin: 10px 0 14px;
}
.tile {
  background: rgba(127, 127, 127, .06);
  border: 1px solid var(--line); border-radius: 10px; padding: 11px 13px;
}
.tile .v { font-size: 1.35rem; font-weight: 700; letter-spacing: -.01em; }
.tile .k { font-size: .74rem; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; margin-bottom: 2px; }
.tile .s { font-size: .76rem; color: var(--muted); margin-top: 2px; }

.fitness-line {
  font-size: .9rem; padding: 10px 12px; margin-bottom: 14px;
  border-left: 3px solid var(--accent); background: rgba(95, 140, 255, .06); border-radius: 0 8px 8px 0;
}
.fitness-line .vdot { font-weight: 700; }
.fitness-line .pred-line { color: var(--muted); margin-top: 4px; }

.cond-note { font-size: .84rem; color: var(--muted); margin-top: 12px; }
.cond-note b { color: var(--ink); }
td .adj { font-size: .74rem; color: var(--good); cursor: help; margin-top: 1px; }

/* Per-week composition: an equal-width, 100%-stacked bar. Segments are runs,
   colored by type and sized by duration, with a 2px surface gap between them
   (the secondary encoding the palette validation requires). */
.type-legend { display: flex; flex-wrap: wrap; gap: 4px 12px; margin: 2px 0 10px; font-size: .78rem; color: var(--muted); }
.type-legend .lg { display: inline-flex; align-items: center; gap: 5px; }
.type-legend .sw { width: 10px; height: 10px; border-radius: 3px; flex: none; }

.weekbars { display: flex; flex-direction: column; gap: 6px; margin-bottom: 12px; }
/* fixed label + amount columns so the bar track is identical width every row */
.weekbar { display: grid; grid-template-columns: 46px 1fr 96px; align-items: center; gap: 10px; font-size: .82rem; }
.weekbar .lbl { color: var(--muted); }
/* track is the full-width rail; fill length encodes the week's share of the peak week */
.weekbar .track { background: rgba(127, 127, 127, .10); border-radius: 5px; height: 18px; overflow: hidden; }
.weekbar .fill { display: flex; gap: 2px; height: 100%; }
.weekbar .seg { height: 100%; border-radius: 3px; min-width: 3px; background: var(--seg-color); transition: background .1s; }
.vdot-rough { margin-top: 8px; font-size: .82rem; line-height: 1.4; }

/* runs outside the volume window stay muted until hovered, so the vis still
   shows full history while making clear what's actually counted below. Blended rather
   than flat gray so the run's type is still faintly legible without hovering. */
.weekbar .seg.excluded { background: color-mix(in srgb, var(--seg-color) 50%, var(--muted) 50%); opacity: .7; }
.weekbar .seg.excluded:hover { background: var(--seg-color); opacity: 1; }
.weekbar.current .fill { opacity: .6; }
.weekbar .amt { color: var(--muted); white-space: nowrap; text-align: right; font-variant-numeric: tabular-nums; }


/* Goal card */
input[type=text], input[type=password], input[type=date], input[type=time] {
  font: inherit;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--card);
  color: var(--ink);
  height: 38px;
}
input[type=text], input[type=password] { width: 100%; }

/* Dim the whole control rather than fight per-property colors: some engines render a
   disabled date/time input's internal text via UA styling that ignores author `color`,
   so opacity is what actually keeps it legible against the dark card. Same convention
   as .flow-steps button:disabled and button.primary:disabled elsewhere in this file. */
input:disabled, select:disabled {
  opacity: .45;
  cursor: not-allowed;
}

/* extra breathing room between the goal card's input fields. They read as cramped
   at the default .row/.field gaps used by the denser Import/History cards */
.goal-inputs { display: flex; flex-direction: column; gap: 22px; margin-bottom: 22px; }
.goal-inputs .row { gap: 20px; }
.goal-location-field { flex: 4 1 220px; }
.goal-acclimatization-field { flex: 1 1 180px; }

.chip-group { display: flex; flex-wrap: wrap; gap: 8px; }
.chip-btn {
  background: transparent; border: 1px solid var(--line); color: var(--ink);
  border-radius: 20px; height: 34px; padding: 4px 14px; font-weight: 600; font-size: .88rem;
}
.chip-btn.active { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }

.location-field { position: relative; }
.location-results {
  position: absolute; top: 100%; left: 0; right: 0; z-index: 10;
  margin: 4px 0 0; padding: 4px; list-style: none;
  background: var(--card); border: 1px solid var(--line); border-radius: 8px;
  box-shadow: var(--shadow); max-height: 220px; overflow-y: auto;
}
.location-results li { padding: 7px 9px; border-radius: 6px; cursor: pointer; font-size: .9rem; }
.location-results li:hover { background: rgba(95, 140, 255, .1); }

/* first-ever load: nothing to show yet, so a full, unambiguous "processing" state */
.goal-loading {
  display: flex; align-items: center; gap: 8px;
  color: var(--muted); font-size: .9rem; margin: 4px 0 14px;
}
.spinner {
  width: 14px; height: 14px; border-radius: 50%;
  border: 2px solid rgba(127, 127, 127, .3); border-top-color: var(--accent);
  animation: spin .7s linear infinite; flex: none;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* subsequent updates: keep the last-known result visible (never blank it out), dim it,
   and show a small badge, so it reads as "this is stale, hang on" rather than "broken" */
#goalResult.is-updating { opacity: .5; transition: opacity .15s; }
.goal-updating-badge {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: .78rem; font-weight: 600; color: var(--accent);
  margin-bottom: 6px;
}

.goal-section-label {
  font-size: .78rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  color: var(--muted); margin: 18px 0 8px; padding-top: 12px; border-top: 1px solid var(--line);
}
.goal-headline { font-size: 2rem; font-weight: 700; letter-spacing: -.02em; }
.goal-sentence { color: var(--muted); margin-top: 2px; }
.goal-ideal-note { font-size: .84rem; color: var(--muted); margin-top: 8px; }

.ambition-row { display: flex; align-items: center; gap: 10px; margin: 14px 0 4px; }
.ambition-row input[type=range] { flex: 1; }
.amb-current { text-align: center; font-size: .78rem; font-weight: 600; color: var(--accent); margin-bottom: 8px; }
.amb-end { font-size: .74rem; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; white-space: nowrap; }
.goal-stop-note { margin-bottom: 10px; font-size: .84rem; }
.goal-compare { font-size: .84rem; color: var(--muted); }

.goal-conditions { display: flex; flex-direction: column; gap: 4px; font-size: .9rem; }
.goal-outlook { font-size: .88rem; }
.goal-outlook .outlook-summary { color: var(--muted); margin-bottom: 10px; }
.goal-outlook .outlook-summary b { color: var(--ink); }
.outlook-phases { display: flex; flex-wrap: wrap; gap: 8px; }
.outlook-phase {
  flex: 1 1 130px;
  background: rgba(127, 127, 127, .06);
  border: 1px solid var(--line); border-radius: 10px; padding: 9px 11px;
}
.outlook-phase .phase-name { font-size: .74rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
.outlook-phase .phase-weeks { font-size: .78rem; color: var(--muted); margin-bottom: 4px; }
.outlook-phase .phase-mix { font-size: .82rem; }

/* Read-only weekly plan calendar and FIT export. Kept as a real 8-column grid inside an
   overflow wrapper so weekday alignment survives on phones instead of collapsing into
   an ambiguous list. Workout color is the same run-type palette as history. */
.plan-loading {
  display: flex; align-items: center; gap: 8px;
  color: var(--muted); font-size: .9rem; margin: 12px 0 4px;
}
.plan-calendar-wrap { overflow-x: auto; margin-top: 12px; padding-bottom: 4px; }
.plan-calendar { width: 100%; min-width: 860px; font-size: .78rem; }
.plan-calendar-head,
.plan-week {
  display: grid;
  grid-template-columns: 100px repeat(7, minmax(0, 1fr));
  gap: 5px;
}
.plan-calendar-head {
  color: var(--muted); font-size: .7rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em; text-align: center;
  padding: 0 0 5px;
}
.plan-calendar-head > :first-child { text-align: left; }
.plan-week { border-top: 1px solid var(--line); padding: 7px 0; min-height: 72px; align-items: stretch; }
.plan-week-label { display: flex; flex-direction: column; gap: 2px; padding: 4px 5px 4px 0; }
.plan-week-label b { font-size: .8rem; }
.plan-week-label span { color: var(--muted); font-size: .7rem; text-transform: capitalize; }
.plan-day { min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.plan-day.rest {
  display: flex; align-items: center; justify-content: center;
  color: color-mix(in srgb, var(--muted) 60%, transparent); font-size: .68rem;
  border: 1px dashed color-mix(in srgb, var(--line) 70%, transparent); border-radius: 7px;
}
.plan-workout {
  flex: 1; min-height: 56px; padding: 6px 7px;
  display: flex; flex-direction: column; justify-content: space-between; gap: 4px;
  border-radius: 7px; border-left: 4px solid var(--workout-color);
  background: color-mix(in srgb, var(--workout-color) 13%, var(--card));
  cursor: help;
}
.plan-workout.type-race { --workout-color: var(--rt-race); }
.plan-workout.type-intervals { --workout-color: var(--rt-intervals); }
.plan-workout.type-tempo { --workout-color: var(--rt-tempo); }
.plan-workout.type-long { --workout-color: var(--rt-long); }
.plan-workout.type-easy { --workout-color: var(--rt-easy); }
.plan-workout.type-recovery { --workout-color: var(--rt-recovery); }
.plan-workout.type-other { --workout-color: var(--rt-other); }
.plan-workout.race { box-shadow: inset 0 0 0 1px var(--rt-race); }
/* Two lines is the budget. A third pushes the distance below its neighbors, and the row grows to
   fit the longest name in it, so the name is clamped and every card keeps its shape. */
.plan-workout-name {
  font-weight: 700; line-height: 1.25; overflow-wrap: anywhere;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}
.plan-workout-meta { display: inline-flex; align-items: center; gap: .3rem; }
.plan-workout-distance { color: var(--muted); font-size: .68rem; font-variant-numeric: tabular-nums; }
/* Same completion icon as the dashboard's day cards (see plan.js's exported checkIcon), so
   a carried-over completed workout in the preview reads the same way it will once the
   update is accepted. No missed marker here: a revision drops any past workout that was
   never completed, so one never reaches this render (see planRevision.js). */
.plan-workout .workout-check { color: var(--good); }

/* Revision preview: weeks already behind the athlete are muted so the seam (everything
   that is NOT muted is the new part) is legible without reading dates. */
.plan-week.carried .plan-week-label { opacity: .55; }
.plan-workout.carried { opacity: .5; filter: saturate(.6); }
.plan-week.update-boundary { border-top: 2px solid var(--accent); }
.plan-week-badge {
  align-self: flex-start; margin-top: 2px; white-space: nowrap;
  padding: 1px 7px; border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 16%, var(--card));
  color: var(--accent); font-size: .62rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em; cursor: help;
}
.plan-change-summary {
  margin: 4px 0 0; font-size: .8rem; color: var(--accent);
  font-variant-numeric: tabular-nums;
}

/* The detail behind View details. A native dialog: Escape, focus trapping and the backdrop are
   the browser's job, and ::backdrop is the only hook the styling needs. */
.plan-detail-dialog {
  width: min(1100px, 94vw); max-height: 88vh; padding: 0;
  /* The card and the dimmed page behind it are close enough in tone, in dark mode especially,
     that the shadow alone leaves the edge hard to find. */
  border: 1px solid color-mix(in srgb, var(--ink) 22%, var(--line));
  border-radius: var(--radius); background: var(--card); color: var(--ink);
  box-shadow: 0 12px 48px rgba(20, 30, 45, .28);
}
.plan-detail-dialog::backdrop { background: rgba(15, 22, 34, .45); }

/* A short question with two answers, borrowing the detail dialog's edge and backdrop. */
.confirm-dialog {
  width: min(440px, 92vw); padding: 20px;
  border: 1px solid color-mix(in srgb, var(--ink) 22%, var(--line));
  border-radius: var(--radius); background: var(--card); color: var(--ink);
  box-shadow: 0 12px 48px rgba(20, 30, 45, .28);
}
.confirm-dialog::backdrop { background: rgba(15, 22, 34, .45); }
.confirm-dialog h3 { margin: 0 0 10px; }
.confirm-dialog p { margin: 0 0 12px; }
.plan-detail-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 12px 16px; border-bottom: 1px solid var(--line);
  position: sticky; top: 0; background: var(--card); z-index: 1;
}
.plan-detail-tabs { display: flex; gap: 4px; }
.plan-detail-tabs button {
  border: none; background: none; cursor: pointer; padding: 7px 12px;
  border-radius: 999px; font: inherit; font-size: .85rem; font-weight: 600; color: var(--muted);
}
.plan-detail-tabs button[aria-selected="true"] {
  background: color-mix(in srgb, var(--accent) 14%, var(--card)); color: var(--accent);
}
.plan-detail-body { padding: 4px 16px 18px; overflow: auto; max-height: calc(88vh - 56px); }
.plan-detail-body .plan-changes { border-top: none; padding-top: 0; }
.plan-detail-body .plan-changes > summary { display: none; }
.plan-detail-body .plan-calendar { min-width: 880px; }
.plan-detail-body > .plan-calendar { overflow-x: auto; }

/* The adaptation review: a recommendation card, not an editing screen. Bordered in --accent so
   it reads as the app proposing something rather than as another panel of the dashboard. */
.dashboard-adapt-card {
  border: 1px solid color-mix(in srgb, var(--accent) 55%, var(--line));
  box-shadow: var(--shadow), 0 0 0 3px color-mix(in srgb, var(--accent) 8%, transparent);
}
.dashboard-adapt-card .plan-changes { margin-top: 14px; }
.dashboard-adapt-card .ai-note { margin-top: 14px; }
.dashboard-adapt-actions { display: flex; gap: 10px; margin-top: 16px; flex-wrap: wrap; }

/* The per-session change list under the preview calendar. Closed by default, so it costs a line
   of chrome until someone wants the detail. */
.plan-changes { margin-top: 16px; border-top: 1px solid var(--line); padding-top: 12px; }
.plan-changes > summary {
  cursor: pointer; font-size: .82rem; font-weight: 600; color: var(--accent);
  list-style-position: inside;
}
/* The default marker sits in its own box away from the label. One drawn here keeps it against
   the text at any indent. */
.plan-changes > summary::marker, .plan-changes > summary::-webkit-details-marker { content: ""; }
.plan-changes > summary::before {
  content: "▸"; display: inline-block; width: 1em; color: var(--muted);
  transition: transform .12s ease;
}
.plan-changes[open] > summary::before { transform: rotate(90deg); }
.plan-changes-past { margin: 10px 0 0; font-size: .78rem; color: var(--muted); }
.plan-changes-week { margin-top: 12px; font-size: .8rem; }
.plan-changes-week > b { font-variant-numeric: tabular-nums; }
.plan-changes-week.unchanged { color: var(--muted); display: flex; gap: 8px; }
.plan-changes-week ul { margin: 6px 0 0; padding: 0; list-style: none; }
.plan-change {
  display: grid; grid-template-columns: 5.5rem minmax(0, 1fr); gap: 2px 10px;
  padding: 5px 0; border-top: 1px solid var(--line);
}
.plan-change-kind {
  grid-row: span 2; font-size: .68rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .04em; align-self: center;
}
.plan-change.added .plan-change-kind { color: var(--good); }
.plan-change.removed .plan-change-kind { color: var(--danger); }
.plan-change.changed .plan-change-kind { color: var(--warn); }
.plan-change-name { font-weight: 600; }
.plan-change-detail { color: var(--muted); font-variant-numeric: tabular-nums; }

/* The banner that rides above both revision steps. */
.revision-banner {
  display: flex; flex-wrap: wrap; gap: 12px; justify-content: space-between; align-items: flex-start;
  margin-top: 16px; padding: 14px 16px;
  border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--line));
  border-radius: 10px;
  background: color-mix(in srgb, var(--accent) 7%, var(--card));
}
.revision-banner-main { flex: 1 1 320px; min-width: 0; }
.revision-eyebrow {
  font-size: .68rem; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
  color: var(--accent);
}
.revision-lead { margin: 4px 0 0; font-size: .84rem; color: var(--ink); }
.revision-notes { margin-top: 8px; display: flex; flex-direction: column; gap: 5px; }
.revision-note {
  font-size: .78rem; color: var(--muted);
  padding-left: 12px; border-left: 2px solid color-mix(in srgb, var(--accent) 45%, transparent);
}
.revision-banner-actions { display: flex; flex-direction: column; align-items: stretch; gap: 8px; }
.revision-banner-actions button { white-space: nowrap; }
.pop .tt-description {
  max-width: 310px; color: var(--muted); margin: 3px 0 7px;
  white-space: pre-line;
}
.pop .tt-steps { display: flex; flex-direction: column; gap: 5px; margin: 5px 0 8px; }
.pop .tt-step { display: flex; justify-content: space-between; gap: 16px; }
.pop .tt-step-name { font-weight: 600; }
.pop .tt-step-target { color: var(--muted); white-space: nowrap; font-variant-numeric: tabular-nums; }
.pop .tt-repeat {
  border: 1px solid var(--line); border-radius: 7px;
  background: rgba(127, 127, 127, .045); overflow: hidden;
}
.pop .tt-repeat-title {
  padding: 4px 7px; border-bottom: 1px solid var(--line);
  font-size: .7rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .04em; color: var(--accent);
}
.pop .tt-repeat-content { display: flex; flex-direction: column; gap: 3px; padding: 5px 7px; }

/* Plan preview: how well the block prepares the athlete, above the calendar it describes. The
   verdict colors the left edge; an axis that falls short is marked on its own. */
.readiness { margin-top: 12px; border: 1px solid var(--line); border-radius: 9px; overflow: hidden; }
.readiness-head {
  display: flex; flex-direction: column; gap: 2px; padding: 9px 11px;
  border-left: 4px solid var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, var(--card));
}
.readiness-head b { font-size: .92rem; }
.readiness-head span { color: var(--muted); font-size: .8rem; }
.readiness-ready { border-left-color: var(--good); background: color-mix(in srgb, var(--good) 8%, var(--card)); }
.readiness-stretch { border-left-color: var(--warn); background: color-mix(in srgb, var(--warn) 8%, var(--card)); }
.readiness-under-prepared { border-left-color: var(--danger); background: color-mix(in srgb, var(--danger) 8%, var(--card)); }
.readiness-axes { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.readiness-axis {
  display: flex; flex-direction: column; gap: 1px; padding: 8px 11px;
  border-top: 1px solid var(--line);
}
.readiness-axis + .readiness-axis { border-left: 1px solid var(--line); }
.readiness-axis-name { color: var(--muted); font-size: .72rem; text-transform: uppercase; letter-spacing: .04em; }
.readiness-axis-value { font-size: .82rem; font-variant-numeric: tabular-nums; }
.readiness-axis-value b { font-size: .95rem; }
.readiness-axis-target { color: var(--muted); font-size: .72rem; font-variant-numeric: tabular-nums; }
.readiness-axis.short .readiness-axis-value b,
.readiness-axis.short .readiness-axis-target { color: var(--warn); }

/* The way into the whole-plan dialog, under the week it is an alternative to. */
.dashboard-week-actions { display: flex; justify-content: center; margin-top: 14px; }
.dashboard-plan-grid { margin-top: 10px; }
.dashboard-plan-grid .plan-calendar-head { position: sticky; top: 0; z-index: 1; background: var(--card); }
#planGridDialog .plan-detail-head h2 { font-size: 1rem; }
#planGridHint { margin: 4px 0 0; }

/* A draggable card in the whole-plan grid. It reads as a control rather than as the preview's
   popover-only card, so the cursor and the selected outline follow the week strip's. */
.plan-calendar.interactive .plan-workout[draggable="true"] { cursor: grab; }
.plan-calendar.interactive .plan-day.drop-target {
  outline: 2px dashed var(--accent); outline-offset: -2px; border-radius: 7px;
}
.plan-calendar.interactive .plan-day.drop-target.occupied { outline-style: solid; }

/* Days a move cannot reach, marked while a card is in the air. The window is a week either side of
   the day the plan generated the workout onto; see server/services/workoutMover.js. */
.dragging-workout [data-drop-date].drop-blocked { opacity: .3; }

/* A change that went through and is worth a word about, unlike .dashboard-error beside it. */
.dashboard-notice {
  margin-top: 12px; padding: 9px 12px; border-radius: var(--radius);
  border: 1px solid color-mix(in srgb, var(--warn) 32%, var(--line));
  background: color-mix(in srgb, var(--warn) 7%, var(--card));
  font-size: .88rem; line-height: 1.45;
}

/* Accepted-plan current-week dashboard. */
.dashboard-shell { display: grid; grid-template-columns: minmax(0, 3fr) minmax(280px, 1.1fr); gap: 18px; }
.dashboard-shell.assistant-unavailable { grid-template-columns: minmax(0, 1fr); }
.dashboard-main { min-width: 0; }
.dashboard-eyebrow { color: var(--accent); font-size: .74rem; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; }
.dashboard-header-card h2 { font-size: 1.4rem; margin-top: 3px; }
.dashboard-metrics { display: flex; gap: 26px; flex-wrap: wrap; margin-top: 16px; }
.dashboard-metrics > div { display: flex; flex-direction: column; }
.dashboard-metrics b { font-size: 1.05rem; }
.dashboard-metrics span { color: var(--muted); font-size: .72rem; text-transform: uppercase; letter-spacing: .04em; }
/* The drift offer. Sits under the header metrics in --warn, which reads as "look at this"
   without the alarm of --danger: nothing is broken, the plan is just out of date. */
.dashboard-drift { margin-top: 16px; padding: 12px 14px; border-radius: var(--radius);
  border: 1px solid color-mix(in srgb, var(--warn) 35%, var(--line));
  background: color-mix(in srgb, var(--warn) 7%, var(--card)); }
.dashboard-drift-text { margin: 0; font-size: .9rem; line-height: 1.45; }
.dashboard-drift-actions { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }

.dashboard-week-nav { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 12px; }
.dashboard-week-nav h2 { text-align: center; margin: 0; }
.dashboard-week-nav button:first-child { justify-self: start; }
.dashboard-week-nav button:last-child { justify-self: end; }
.dashboard-days { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 7px; margin-top: 16px; }
/* The column stretches so a day holding two runs does not leave its neighbors short. */
.dashboard-day-column { min-width: 0; display: flex; flex-direction: column; }
.dashboard-day-column .dashboard-day { flex: 1; }
.dashboard-day { min-height: 116px; border: 1px solid var(--line); border-radius: 9px; padding: 7px; background: rgba(127,127,127,.025); }
.dashboard-day.today { outline: 2px solid var(--accent); outline-offset: 1px; background: color-mix(in srgb, var(--accent) 7%, var(--card)); }
/* Selection marks the run, not the day it sits on: a day may hold two, and only one is being read.
   The whole-plan grid marks its cards the same way. */
.dashboard-workout.selected,
.plan-calendar.interactive .plan-workout.selected {
  box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--ink) 45%, var(--workout-color));
}
.dashboard-day.drop-target {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 2px var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, var(--card));
  transform: scale(1.03);
}
/* A day already holding a workout swaps rather than rejects, so it gets its own color: the
   athlete should be able to tell a swap from a plain move before letting go. */
.dashboard-day.drop-target.occupied {
  border-color: var(--anchor);
  box-shadow: inset 0 0 0 2px var(--anchor);
  background: color-mix(in srgb, var(--anchor) 12%, var(--card));
}
.dashboard-day { transition: transform .12s ease, background .12s ease, box-shadow .12s ease; }
.dashboard-day-head { display: flex; justify-content: space-between; color: var(--muted); font-size: .7rem; font-weight: 700; text-transform: uppercase; }
.dashboard-day-marker { display: block; min-height: .95rem; margin-bottom: 4px; color: var(--accent); font-size: .62rem; font-weight: 700; line-height: .95rem; text-align: center; text-transform: uppercase; letter-spacing: .04em; }
.dashboard-rest { display: grid; place-items: center; height: 75px; color: color-mix(in srgb, var(--muted) 65%, transparent); font-size: .76rem; }
.dashboard-workout { width: 100%; height: auto; min-height: 75px; margin-top: 7px; flex: none; padding: 7px; display: flex; flex-direction: column; align-items: flex-start; justify-content: space-between; overflow: hidden; text-align: left; color: var(--ink); border-left: 4px solid var(--workout-color); background: color-mix(in srgb, var(--workout-color) 13%, var(--card)); }
.dashboard-workout[draggable="true"] { cursor: grab; -webkit-touch-callout: none; }
/* The card left behind reads as a hole the workout came out of, not as the workout itself. */
.dashboard-workout.dragging {
  opacity: .3;
  background: transparent;
  border-left-style: dashed;
  box-shadow: inset 0 0 0 1px var(--line);
}
.dashboard-workout.dragging * { visibility: hidden; }

/* Touch has no drag image of its own, so this clone is the thing the finger actually carries.
   Lifted off the page with a shadow and a slight tilt, and never a hit-test target, or
   elementFromPoint under the finger would only ever find the ghost. */
.drag-ghost {
  position: fixed;
  z-index: 60;
  margin: 0;
  pointer-events: none;
  opacity: .95;
  transform: rotate(-2deg) scale(1.06);
  box-shadow: 0 10px 24px rgba(0, 0, 0, .28), 0 2px 6px rgba(0, 0, 0, .2);
  cursor: grabbing;
}
body.dragging-workout { cursor: grabbing; -webkit-user-select: none; user-select: none; }
.dashboard-workout span { color: var(--muted); font-size: .72rem; }
.dashboard-workout.type-race { --workout-color: var(--rt-race); }
.dashboard-workout.type-intervals { --workout-color: var(--rt-intervals); }
.dashboard-workout.type-tempo { --workout-color: var(--rt-tempo); }
.dashboard-workout.type-long { --workout-color: var(--rt-long); }
.dashboard-workout.type-easy { --workout-color: var(--rt-easy); }
.dashboard-workout.type-recovery { --workout-color: var(--rt-recovery); }
.dashboard-workout.type-other { --workout-color: var(--rt-other); }
.dashboard-detail-card { min-height: 110px; }

.dashboard-context-card { display: flex; flex-direction: column; gap: 10px; }
.brief-head { display: flex; align-items: center; justify-content: space-between; }
.brief-head h3 { margin: 0; font-size: .95rem; }
/* The same two columns as .dashboard-detail-head below, so the adjusted paces sit directly
   above the planned ones they replace and the apply control lands beside them, in line with
   the Garmin buttons. The controls row above stays full width: the time and place apply to
   the whole brief, not to the paces alone. */
.brief-body {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: start;
  gap: 20px;
}
.brief-detail { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.brief-action { display: flex; flex-direction: column; align-items: stretch; min-width: 0; }
.brief-action button { width: 100%; }
/* Time and location are one row of inputs where there is width for it, and stack below that.
   The time is a fixed narrow control, so location takes everything to its right: place names
   are long, and there is no reason to leave that space empty. */
.brief-controls { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.brief-time { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }
.brief-time label { color: var(--muted); font-size: .82rem; }
.brief-time input[type=time] {
  font: inherit; padding: 6px 8px; border: 1px solid var(--line); border-radius: 7px;
  background: var(--card); color: var(--ink); height: 32px;
}
.brief-location { display: flex; align-items: center; gap: 8px; flex: 1 1 220px; min-width: 0; }
.brief-location label { color: var(--muted); font-size: .82rem; flex: 0 0 auto; }
.brief-location input[type=text] {
  flex: 1 1 auto; min-width: 0;
  font: inherit; padding: 6px 8px; border: 1px solid var(--line); border-radius: 7px;
  background: var(--card); color: var(--ink); height: 32px;
}
.brief-location .location-results { top: calc(100% + 3px); }
.brief-conditions { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.brief-temp { font-size: 1.3rem; font-weight: 700; }
.brief-paces { gap: 4px; }

/* Every generated note wears the same box and badge, so it never reads as one more measured
   figure among the numbers it describes (see coachNote.js). */
.ai-note {
  display: flex; flex-direction: column; gap: 8px;
  padding: 10px 12px; border: 1px solid var(--line); border-radius: 9px;
  background: color-mix(in srgb, var(--accent) 4%, var(--card));
}
.ai-note-head { display: flex; align-items: center; gap: 8px; }
.ai-note-kicker {
  color: var(--muted); font-size: .66rem; font-weight: 700;
  letter-spacing: .04em; text-transform: uppercase;
}
.ai-note-body { margin: 0; font-size: .9rem; line-height: 1.45; }
.ai-note-body.warn { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 0; }
/* Placeholder lines while the text generates; the sweep is what says it is still working. */
.ai-note-skeleton { display: flex; flex-direction: column; gap: 7px; }
.ai-note-skeleton span {
  height: .6rem; border-radius: 4px;
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--muted) 13%, var(--card)) 25%,
    color-mix(in srgb, var(--muted) 32%, var(--card)) 50%,
    color-mix(in srgb, var(--muted) 13%, var(--card)) 75%);
  background-size: 300% 100%;
  animation: ai-shimmer 1.6s ease-in-out infinite;
}
.ai-note-skeleton span:nth-child(2) { width: 93%; }
.ai-note-skeleton span:nth-child(3) { width: 58%; }
@keyframes ai-shimmer { from { background-position: 150% 0; } to { background-position: -150% 0; } }
@media (prefers-reduced-motion: reduce) {
  .ai-note-skeleton span { animation: none; }
  .spinner { animation-duration: 2s; }
  .drag-ghost, .dashboard-workout, .dashboard-day { transition: none; }
}
/* The brief and debrief panes render .tt-row/.tt-steps rows the same way the prescription
   card and the shared popover already do, but they live in neither of those scopes. */
.dashboard-context-card .tt-steps { display: flex; flex-direction: column; gap: 6px; }
.dashboard-context-card .tt-row { display: flex; justify-content: space-between; gap: 20px; }
.dashboard-context-card .tt-row .k { color: var(--muted); }

.debrief-comparison { margin: 2px 0; }
.debrief-chart-wrap { margin-top: 4px; }
.debrief-chart { position: relative; height: 92px; overflow: hidden; }
.debrief-bars { display: flex; align-items: flex-end; height: 100%; }
/* Widths are exact percentages of lap distance, so the 2px separation is a surface-colored
   shadow outside the box instead of a flex gap: a gap consumes pixels that the heart-rate
   overlay's percentage geometry cannot see, and the line drifts off the bars. */
.debrief-bars .seg {
  flex: 0 1 auto; min-width: 2px; border-radius: 3px 3px 0 0;
  background: var(--seg-color); box-shadow: 2px 0 0 var(--card);
  cursor: default; transition: opacity .1s;
}
.debrief-bars .seg.resting { background: color-mix(in srgb, var(--seg-color) 50%, var(--muted) 50%); opacity: .7; }
.debrief-bars .seg.resting:hover, .debrief-bars .seg:hover { opacity: 1; }
.debrief-hr { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
/* Neutral against the categorical bars: a second hue would compete with the run-type color,
   and chroma against achromatic separates for every kind of color vision. */
.debrief-hr polyline {
  fill: none; stroke: var(--ink); stroke-width: 1.75; opacity: .85;
  stroke-linejoin: round; stroke-linecap: round;
}
.debrief-hr polyline.halo { stroke: var(--card); stroke-width: 3.75; opacity: .7; }
.debrief-legend {
  display: flex; flex-wrap: wrap; gap: 3px 14px;
  margin-top: 7px; color: var(--muted); font-size: .72rem;
}
.debrief-legend span { display: inline-flex; align-items: center; gap: 6px; }
.debrief-legend .legend-bar { width: 9px; height: 9px; border-radius: 2px; background: var(--seg-color); }
.debrief-legend .legend-line { width: 14px; height: 2px; border-radius: 2px; background: var(--ink); opacity: .6; }
/* Even halves. The action column needs the width to put its two Garmin buttons side by side,
   and the prescription reads fine at half. */
.dashboard-detail-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: start;
  gap: 20px;
}
.garmin-action { display: flex; flex-direction: column; align-items: stretch; gap: .4rem; min-width: 0; }
.dashboard-workout .dashboard-workout-name { display: inline-flex; align-items: flex-start; min-width: 0; max-width: 100%; width: 100%; color: var(--ink); font-size: inherit; }
/* Two lines here as in the plan grid, and sized to fit them: a name cut to "5 x 6 M..." says less
   than one that wraps, and a day column is only about ninety pixels wide. */
.dashboard-workout-name b {
  flex: 1 1 auto; min-width: 0; overflow: hidden; overflow-wrap: anywhere;
  font-size: .82rem; line-height: 1.22;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
}
.dashboard-workout .dashboard-workout-meta { display: inline-flex; align-items: center; align-self: flex-end; gap: .3rem; }
.garmin-watch, .workout-check, .workout-missed { display: inline-flex; flex: 0 0 auto; color: currentColor; }
.garmin-watch svg, .workout-check svg, .workout-missed svg { width: .9rem; height: .9rem; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.dashboard-workout .workout-check { color: var(--good); }
.dashboard-workout .workout-missed { color: var(--danger); }
.garmin-action span { color: var(--muted); font-size: .78rem; }
/* The two Garmin actions pair up on a row; Mark as complete spans beneath them. `auto-fit` with
   a minimum lets the pair fall back to stacking on a narrow screen without a media query. */
.garmin-buttons {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  align-items: stretch;
  gap: .5rem;
  width: 100%;
}
.garmin-buttons > button, .garmin-buttons > .disabled-action-tip { width: 100%; }
.garmin-buttons > .complete-workout { grid-column: 1 / -1; }
.garmin-action .disabled-action-tip { display: inline-flex; width: 100%; }
.garmin-action .disabled-action-tip > button { width: 100%; }
.disabled-action-tip > button { pointer-events: none; font-size: 15px; }
.dashboard-detail-card .tt-head { font-size: 1.05rem; font-weight: 700; }
/* Marks a prescription the pane is showing in terms other than the stored plan's. */
.tt-badge {
  display: inline-block; margin-left: 8px; padding: 2px 7px; border-radius: 20px;
  vertical-align: middle;
  background: color-mix(in srgb, var(--warn) 13%, var(--card));
  color: var(--warn); font-size: .6rem; font-weight: 700;
  letter-spacing: .04em; text-transform: uppercase;
}
.dashboard-detail-card .tt-date { color: var(--muted); margin-bottom: 8px; }
.dashboard-detail-card .tt-steps { display: flex; flex-direction: column; gap: 6px; }
.dashboard-detail-card .tt-step { display: flex; justify-content: space-between; gap: 20px; }
.dashboard-detail-card .tt-step-target, .dashboard-detail-card .tt-row .k { color: var(--muted); }
.dashboard-detail-card .tt-row { display: flex; justify-content: space-between; gap: 20px; }
.dashboard-detail-card .tt-repeat { border: 1px solid var(--line); border-radius: 7px; padding: 7px; margin: 5px 0; }
.dashboard-detail-card .tt-repeat-title { color: var(--accent); font-size: .72rem; font-weight: 700; text-transform: uppercase; }
.dashboard-adjust { align-self: start; padding: 16px; }
.adjust-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.adjust-head .hint { margin: 2px 0 0; line-height: 1.35; }
/* One badge for "a model wrote this", worn by the Adjust plan pane and every coaching note. */
.ai-label {
  flex: none; padding: 2px 7px; border-radius: 20px;
  background: color-mix(in srgb, var(--accent) 11%, var(--card));
  color: var(--accent); font-size: .68rem; font-weight: 700; letter-spacing: .03em;
}
.adjust-messages {
  display: flex; flex-direction: column; gap: 10px;
  min-height: 190px; max-height: 480px; overflow-y: auto;
  margin: 14px -4px 12px; padding: 4px;
  scrollbar-width: thin;
}
.adjust-empty {
  margin: auto 8px; color: var(--muted); font-size: .82rem; line-height: 1.5; text-align: center;
}
.chat-row { display: flex; }
.chat-row.user { justify-content: flex-end; }
.chat-row.assistant { justify-content: flex-start; }
.chat-bubble {
  max-width: 94%; padding: 8px 10px; border-radius: 10px;
  background: color-mix(in srgb, var(--muted) 8%, var(--card));
  font-size: .84rem; line-height: 1.45;
}
.chat-row.user .chat-bubble {
  background: color-mix(in srgb, var(--accent) 13%, var(--card));
  border-bottom-right-radius: 3px;
}
.chat-row.assistant .chat-bubble { border-bottom-left-radius: 3px; }
.chat-text { overflow-wrap: anywhere; }
.chat-row.failed .chat-bubble { border: 1px solid color-mix(in srgb, var(--warn) 45%, var(--line)); }
.chat-failure {
  display: flex; align-items: center; justify-content: flex-end; gap: 8px;
  margin-top: 6px; color: var(--warn); font-size: .68rem; font-weight: 700;
}
.chat-failure button { padding: 3px 7px; font-size: .68rem; }
.chat-thinking { display: inline-flex; align-items: center; gap: 7px; color: var(--muted); }
.adjust-form { padding-top: 12px; border-top: 1px solid var(--line); }
.adjust-form-actions { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 7px; }
.adjust-form-hint { color: var(--muted); font-size: .66rem; line-height: 1.3; }
.adjust-form button { flex: none; }
.adjust-form-actions button { white-space: nowrap; }
#adjustPlanSend { margin-left: auto; }
.assistant-proposal {
  margin-top: 9px; padding: 9px; border: 1px solid var(--line); border-radius: 8px;
  background: var(--card);
}
.proposal-label {
  margin-bottom: 5px; color: var(--accent); font-size: .65rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .05em;
}
.proposal-replace { display: flex; align-items: baseline; flex-wrap: wrap; gap: 5px; }
.proposal-replace > span:first-child { color: var(--muted); text-decoration: line-through; }
.proposal-stats { display: flex; flex-wrap: wrap; gap: 4px 10px; margin-top: 6px; color: var(--muted); font-size: .74rem; }
.proposal-stat-change { display: inline-flex; gap: 4px; }
.proposal-old { color: var(--muted); text-decoration: line-through; text-decoration-thickness: 1.5px; }
.proposal-steps { margin-top: 7px; padding-top: 7px; border-top: 1px solid var(--line); line-height: 1.55; }
.proposal-step-diff { display: grid; gap: 5px; }
.proposal-diff-heading { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; color: var(--muted); font-size: .65rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; }
.proposal-segment { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); gap: 7px; align-items: baseline; }
.proposal-segment.unchanged { display: block; color: var(--muted); }
.proposal-new { color: var(--text); font-weight: 650; }
.proposal-repeat-diff { padding: 6px 7px; border: 1px solid var(--line); border-radius: 6px; }
.proposal-repeat-title { color: var(--accent); font-weight: 700; }
.proposal-repeat-steps { display: grid; gap: 3px; margin-top: 4px; padding-left: 7px; }
.proposal-context, .proposal-meta { margin-top: 7px; color: var(--muted); font-size: .72rem; }
.proposal-warnings { margin: 7px 0 0; padding-left: 17px; color: var(--warn); font-size: .72rem; }
.proposal-note { margin-top: 7px; color: var(--muted); font-size: .68rem; font-style: italic; }
.proposal-note.accepted { color: var(--good); font-style: normal; font-weight: 700; }
.proposal-note.superseded { font-style: normal; }
.proposal-actions { display: flex; justify-content: flex-end; margin-top: 8px; }
.proposal-actions button { padding: 6px 10px; font-size: .72rem; }
.detail-move { width: 100%; margin-top: .35rem; padding-top: .65rem; border-top: 1px solid var(--line); }
.detail-move label { display: block; margin-bottom: .35rem; color: var(--muted); font-size: .78rem; font-weight: 700; }
/* Stacked, not side by side: these live in the narrow action rail, where a select and a
   button on one row overflow the column. */
.detail-move-row { display: flex; flex-direction: column; align-items: stretch; gap: .5rem; }
.detail-move-row select, .detail-move-row button { width: 100%; min-width: 0; }
.detail-move-hint { margin: .35rem 0 0; color: var(--muted); font-size: .72rem; }
.completed-activity { margin-top: .85rem; padding-top: .85rem; border-top: 1px solid var(--line); }
.completed-activity-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: .65rem; }
.completed-activity-head > div { display: flex; flex-direction: column; gap: .12rem; }
.completed-activity-head span { color: var(--muted); font-size: .72rem; }
.completed-activity-head b { font-size: .9rem; }
.completed-activity-nudge { margin: .6rem 0 0; color: var(--muted); font-size: .78rem; }
.completed-activity-actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .5rem; }
.completed-activity-actions > button, .completed-activity-actions .disabled-action-tip,
.completed-activity-actions .disabled-action-tip > button { width: 100%; }
.completed-activity-stats { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .55rem; }
.completed-activity-stats > div { display: flex; flex-direction: column; padding: .55rem .65rem; border-radius: 7px; background: color-mix(in srgb, var(--ink) 4%, var(--card)); }
.completed-activity-stats b { font-size: .88rem; }
.completed-activity-stats span { color: var(--muted); font-size: .7rem; }
.completed-activity-remove { width: 100%; margin-top: .5rem; }
.completed-activity .hint { margin: .55rem 0 0; font-size: .72rem; line-height: 1.35; }

@media (max-width: 850px) {
  .dashboard-shell { grid-template-columns: 1fr; }
  .dashboard-adjust { min-height: auto; }
  .dashboard-week-card { overflow-x: auto; }
  .dashboard-days { min-width: 680px; }
}

@media (max-width: 620px) {
  .flow-steps { gap: 5px; }
  .flow-steps button { padding: 8px 5px; text-align: center; font-size: .72rem; }
  .flow-steps button .step-num { display: grid; margin: 0 auto 3px; }
  .card-head { align-items: flex-start; gap: 10px; }
  .card-head .head-actions { flex-wrap: wrap; justify-content: flex-end; }
  .dashboard-detail-head, .brief-body { grid-template-columns: minmax(0, 1fr); }
  .garmin-action { width: 100%; }
  .plan-calendar-wrap {
    margin-left: -20px; margin-right: -20px; padding-left: 20px; padding-right: 20px;
  }
}

.footer { text-align: center; color: var(--muted); font-size: .82rem; margin-top: 26px; }

.toast {
  position: fixed; left: 50%; bottom: 26px; transform: translateX(-50%) translateY(20px);
  background: var(--ink); color: var(--bg);
  padding: 10px 16px; border-radius: 8px; font-size: .88rem;
  opacity: 0; pointer-events: none; transition: opacity .2s, transform .2s;
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

/* Boot sequence: #appLoading is the only thing visible on first paint (see index.html); a
   plain centered spinner line, deliberately with no card chrome since it should read as
   "still figuring out what to show," not as a screen of its own. */
#appLoading {
  max-width: 820px; margin: 80px auto; text-align: center; color: var(--muted);
}
.auth-loading { display: inline-flex; align-items: center; gap: 8px; }

#authScreen { max-width: 380px; margin: 60px auto; }
.auth-card { display: flex; flex-direction: column; gap: 4px; }
.auth-brand { margin: 0 0 2px; font-size: 1.3rem; letter-spacing: -.02em; text-align: center; }
.auth-card #authTitle { text-align: center; }
.auth-card .hint { text-align: center; margin-bottom: 14px; }
#authForm { display: flex; flex-direction: column; gap: 14px; margin-top: 4px; }
.auth-field-hint { font-size: .78rem; color: var(--muted); }
.auth-actions { margin-top: 4px; }
.auth-actions .primary { width: 100%; }
.auth-switch {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  margin-top: 16px; font-size: .88rem; color: var(--muted);
}
.auth-divider {
  display: flex; align-items: center; gap: 10px;
  margin: 18px 0 4px; color: var(--muted); font-size: .8rem;
}
.auth-divider::before, .auth-divider::after { content: ""; flex: 1; height: 1px; background: var(--line); }
.auth-google {
  display: flex; align-items: center; justify-content: center;
  text-decoration: none; width: 100%;
}
.auth-about { margin: 18px 0 0; text-align: center; font-size: .85rem; }

/* Declared baseline (step 1's second path). Two columns where there is room, so the four
   numbers read as one short set of questions rather than a long form. */
.import-alt {
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
  margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--line);
  color: var(--muted); font-size: .9rem;
}
.declare-grid { display: grid; gap: 14px; }
@media (min-width: 620px) { .declare-grid { grid-template-columns: 1fr 1fr; } }
/* A number input sizes to its `size` attribute, not to its column, so the four questions would
   otherwise come out at four different widths. */
.declare-grid input { width: 100%; }
.declare-effort {
  margin: 20px 0 0; padding: 16px 16px 18px;
  border: 1px solid var(--line); border-radius: var(--radius);
}
.declare-effort legend { padding: 0 6px; font-weight: 600; font-size: .95rem; }
.declare-effort .hint { margin-top: 0; }
.declare-effort .chip-group { margin-bottom: 14px; }
.declare-optional { margin-left: 6px; color: var(--muted); font-weight: 400; font-size: .82rem; }
.declare-actions { margin-top: 18px; }
.declare-remove { margin-left: auto; }
/* The server names the field its rejection is about; this is how the athlete sees which one. */
.field-invalid input { border-color: var(--danger); }
.field-invalid label { color: var(--danger); }

