/* strava-viz -- palette and layout.
   Series colours come from a validated categorical palette; both light and
   dark steps are selected, not flipped. */

:root {
  color-scheme: light dark;

  --surface-0: #f6f5f2;
  --surface-1: #fcfcfb;
  --surface-2: #efeeea;
  --border: #dedcd5;
  --border-strong: #c6c3b9;

  --text-primary: #0b0b0b;
  --text-secondary: #52514e;
  --text-muted: #7c7a72;

  --series-1: #2a78d6;  /* blue    -- fitness / power */
  --series-2: #eb6834;  /* orange  -- fatigue */
  --series-3: #1baf7a;  /* aqua    -- heart rate */
  --series-4: #eda100;  /* yellow */
  --series-5: #e87ba4;  /* magenta */
  --series-6: #008300;  /* green */
  --series-7: #4a3aa7;  /* violet */
  --series-8: #e34948;  /* red */

  --good: #0ca30c;
  --warning: #fab219;
  --serious: #ec835a;
  --critical: #d03b3b;

  /* Blue ordinal ramp, light mode: never lighter than step 250. */
  --ramp-1: #86b6ef;
  --ramp-2: #5598e7;
  --ramp-3: #3987e5;
  --ramp-4: #256abf;
  --ramp-5: #1c5cab;
  --ramp-6: #184f95;
  --ramp-7: #0d366b;

  --diverge-pos: #2a78d6;
  --diverge-neg: #d03b3b;
  --diverge-mid: #f0efec;

  --grid: #e5e3dd;
  --radius: 10px;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --surface-0: #131312;
    --surface-1: #1a1a19;
    --surface-2: #242422;
    --border: #35342f;
    --border-strong: #4a4943;

    --text-primary: #ffffff;
    --text-secondary: #c3c2b7;
    --text-muted: #918f84;

    --series-1: #3987e5;
    --series-2: #d95926;
    --series-3: #199e70;
    --series-4: #c98500;
    --series-5: #d55181;
    --series-6: #008300;
    --series-7: #9085e9;
    --series-8: #e66767;

    --ramp-1: #cde2fb;
    --ramp-2: #9ec5f4;
    --ramp-3: #6da7ec;
    --ramp-4: #3987e5;
    --ramp-5: #256abf;
    --ramp-6: #1c5cab;
    --ramp-7: #184f95;

    --diverge-mid: #383835;
    --grid: #2c2c29;
  }
}

:root[data-theme="dark"] {
  --surface-0: #131312;
  --surface-1: #1a1a19;
  --surface-2: #242422;
  --border: #35342f;
  --border-strong: #4a4943;
  --text-primary: #ffffff;
  --text-secondary: #c3c2b7;
  --text-muted: #918f84;
  --series-1: #3987e5;
  --series-2: #d95926;
  --series-3: #199e70;
  --series-4: #c98500;
  --series-5: #d55181;
  --series-6: #008300;
  --series-7: #9085e9;
  --series-8: #e66767;
  --ramp-1: #cde2fb;
  --ramp-2: #9ec5f4;
  --ramp-3: #6da7ec;
  --ramp-4: #3987e5;
  --ramp-5: #256abf;
  --ramp-6: #1c5cab;
  --ramp-7: #184f95;
  --diverge-mid: #383835;
  --grid: #2c2c29;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font: 15px/1.5 var(--sans);
  background: var(--surface-0);
  color: var(--text-primary);
  -webkit-text-size-adjust: 100%;
}

h1, h2, h3 { line-height: 1.25; margin: 0; font-weight: 600; }
h1 { font-size: 1.15rem; }
h2 { font-size: 1rem; }
h3 { font-size: 0.9rem; }
a { color: var(--series-1); }
.muted { color: var(--text-muted); }
.num { font-family: var(--mono); font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------------ chrome ----- */

header.top {
  position: sticky; top: 0; z-index: 30;
  display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
  padding: 0.6rem 1rem;
  background: var(--surface-1);
  border-bottom: 1px solid var(--border);
}
header.top .brand { font-weight: 650; margin-right: auto; display: flex; gap: .6rem; align-items: baseline; }
header.top .brand small { color: var(--text-muted); font-weight: 400; }

nav.tabs { display: flex; gap: 2px; flex-wrap: wrap; }
nav.tabs a {
  padding: 0.35rem 0.7rem; border-radius: 999px; text-decoration: none;
  color: var(--text-secondary); font-size: 0.875rem; white-space: nowrap;
}
nav.tabs a:hover { background: var(--surface-2); color: var(--text-primary); }
nav.tabs a[aria-current="page"] {
  background: var(--text-primary); color: var(--surface-1); font-weight: 550;
}

.iconbtn {
  border: 1px solid var(--border); background: var(--surface-1);
  color: var(--text-secondary); border-radius: 8px; cursor: pointer;
  padding: 0.3rem 0.55rem; font-size: 0.85rem;
}
.iconbtn:hover { border-color: var(--border-strong); color: var(--text-primary); }
.iconbtn:disabled { cursor: default; opacity: 0.75; }
/* Width is pinned so "Sync" -> "Syncing…" -> "Updated" does not shuffle the
   header buttons beside it on every state change. */
#sync-btn { min-width: 5.5rem; }
.iconbtn.busy { border-color: var(--border-strong); color: var(--text-primary); }
.iconbtn.busy::before {
  content: ''; display: inline-block; vertical-align: -1px;
  width: 0.62em; height: 0.62em; margin-right: 0.4em;
  border: 1.5px solid currentColor; border-right-color: transparent;
  border-radius: 50%; animation: sv-spin 0.7s linear infinite;
}
@keyframes sv-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .iconbtn.busy::before { animation-duration: 2.4s; }
}

main { padding: 1rem; max-width: 1500px; margin: 0 auto; }

/* ------------------------------------------------------------- cards ----- */

.grid { display: grid; gap: 0.85rem; }
.grid.cols-2 { grid-template-columns: repeat(auto-fit, minmax(420px, 1fr)); }
.grid.cols-3 { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }

.card {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.85rem 0.9rem 0.6rem;
  min-width: 0;
}
.card > header {
  display: flex; align-items: baseline; gap: 0.6rem; flex-wrap: wrap;
  margin-bottom: 0.15rem;
}
.card > header .sub { color: var(--text-muted); font-size: 0.8rem; }
.card > header .spacer { margin-left: auto; }
.card .note {
  color: var(--text-muted); font-size: 0.78rem; margin: 0.45rem 0 0.2rem;
}

/* --------------------------------------------------------- stat tiles ---- */

.tiles { display: grid; gap: 0.6rem; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.tile {
  background: var(--surface-1); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 0.7rem 0.8rem;
}
.tile .label {
  font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--text-muted); display: flex; align-items: center; gap: 0.35rem;
}
.tile .value {
  font-family: var(--mono); font-size: 1.65rem; font-weight: 600;
  line-height: 1.15; margin-top: 0.2rem; font-variant-numeric: tabular-nums;
}
.tile .value small { font-size: 0.8rem; font-weight: 400; color: var(--text-muted); }
.tile .foot { font-size: 0.76rem; color: var(--text-secondary); margin-top: 0.15rem; }

.swatch {
  width: 9px; height: 9px; border-radius: 2px; display: inline-block; flex: none;
}
/* Dashes only read as dashes on something wider than it is tall. */
.swatch.dashed { width: 14px; height: 3px; border-radius: 0; }
.dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }

.pill {
  display: inline-flex; align-items: center; gap: 0.3rem;
  font-size: 0.72rem; padding: 0.1rem 0.45rem; border-radius: 999px;
  border: 1px solid var(--border-strong); color: var(--text-secondary);
  white-space: nowrap;
}
.pill.good    { border-color: var(--good);     color: var(--good); }
.pill.warning { border-color: var(--warning);  color: var(--text-primary); background: color-mix(in oklab, var(--warning) 18%, transparent); }
.pill.serious { border-color: var(--serious);  color: var(--text-primary); background: color-mix(in oklab, var(--serious) 18%, transparent); }
.pill.critical{ border-color: var(--critical); color: var(--critical); }

/* ------------------------------------------------------------ legend ----- */

.legend {
  display: flex; flex-wrap: wrap; gap: 0.35rem 0.9rem;
  font-size: 0.78rem; color: var(--text-secondary); margin: 0.3rem 0 0.1rem;
}
.legend .item { display: inline-flex; align-items: center; gap: 0.35rem; cursor: default; }
.legend .item.toggle { cursor: pointer; }
.legend .item.off { opacity: 0.35; }
.legend .item b { font-weight: 550; color: var(--text-primary); font-family: var(--mono); }

/* All-time context strip under the PMC. Same visual weight as the legend, but
   these are fixed reference numbers rather than toggles, so no swatches and no
   pointer cursor. */
.reflist {
  display: flex; flex-wrap: wrap; gap: 0.25rem 1.1rem;
  font-size: 0.76rem; color: var(--text-secondary);
  margin: 0.45rem 0 0; padding-top: 0.45rem;
  border-top: 1px solid var(--border);
}
.reflist:empty { display: none; }
.refitem { display: inline-flex; align-items: baseline; gap: 0.3rem; }
.reflabel { color: var(--text-muted); }
.refitem b { font-weight: 550; color: var(--text-primary); font-family: var(--mono); }
.refnote { color: var(--text-muted); font-size: 0.72rem; }

/* ------------------------------------------------------------ charts ----- */

.chart { width: 100%; }
/* Sits over the plot area, so it lands inside the axes rather than over them. */
.chart-empty {
  position: absolute; inset: 0; display: grid; place-items: center;
  font-size: .85rem; color: var(--text-muted); pointer-events: none;
}
.chart-scroll { overflow-x: auto; }

.uplot { font-family: var(--sans) !important; }
.u-title { color: var(--text-primary); }
.u-legend { display: none; }           /* replaced by our own legend + tooltip */
.u-axis   { color: var(--text-muted); }
.u-select { background: color-mix(in oklab, var(--series-1) 14%, transparent); }
.u-cursor-x, .u-cursor-y { border-color: var(--border-strong) !important; }
.u-cursor-pt { box-shadow: 0 0 0 2px var(--surface-1); }

.tooltip {
  position: absolute; z-index: 50; pointer-events: none;
  background: var(--surface-1); color: var(--text-primary);
  border: 1px solid var(--border-strong); border-radius: 8px;
  padding: 0.4rem 0.55rem; font-size: 0.78rem; line-height: 1.4;
  box-shadow: 0 4px 14px rgb(0 0 0 / 0.14); min-width: 130px;
}
.tooltip .t-date { color: var(--text-muted); margin-bottom: 0.2rem; }
.tooltip .t-row { display: flex; gap: 0.5rem; align-items: center; }
.tooltip .t-row .name { color: var(--text-secondary); }
.tooltip .t-row .val { margin-left: auto; font-family: var(--mono); font-variant-numeric: tabular-nums; }
.tooltip .t-total { margin-top: 0.2rem; padding-top: 0.2rem; border-top: 1px solid var(--border); }
.tooltip .t-total .name { color: var(--text-primary); }

/* --------------------------------------------------------- controls ------ */

.controls {
  display: flex; flex-wrap: wrap; gap: 0.4rem 0.55rem; align-items: center;
  margin-bottom: 0.85rem;
}
/* A long segmented group (eight years of the calendar) scrolls inside itself
   rather than pushing the page sideways. */
/* The segmented control also rides in a card header (card()'s `right` slot).
 * The track stays --surface-2 and the pressed segment --surface-1, which still
 * separates them there because the card itself is the darker of the two. */
.controls .group, .card > header .group {
  display: flex; gap: 2px; background: var(--surface-2); padding: 2px;
  border-radius: 8px; max-width: 100%; overflow-x: auto;
}
.controls .group button, .card > header .group button { flex: 0 0 auto; }
.controls button, .controls select, .controls input[type="search"], .controls input[type="date"],
.card > header .group button {
  font: inherit; font-size: 0.83rem;
  border: 1px solid transparent; background: transparent;
  color: var(--text-secondary); border-radius: 6px; padding: 0.25rem 0.6rem;
  cursor: pointer;
}
.controls select, .controls input[type="search"], .controls input[type="date"] {
  border-color: var(--border); background: var(--surface-1); cursor: auto;
}
.controls .group button[aria-pressed="true"],
.card > header .group button[aria-pressed="true"] {
  background: var(--surface-1); color: var(--text-primary); font-weight: 550;
  border-color: var(--border);
}
.controls button:hover, .card > header .group button:hover { color: var(--text-primary); }
.controls label { font-size: 0.8rem; color: var(--text-muted); }

/* ----------------------------------------------------------- tables ------ */

.table-wrap { overflow-x: auto; }
table.data {
  width: 100%; border-collapse: collapse; font-size: 0.83rem;
}
table.data th, table.data td {
  padding: 0.35rem 0.5rem; text-align: right; white-space: nowrap;
  border-bottom: 1px solid var(--border);
}
table.data th:first-child, table.data td:first-child,
table.data th.left, table.data td.left { text-align: left; }
table.data thead th {
  position: sticky; top: 0; background: var(--surface-1); z-index: 1;
  color: var(--text-muted); font-weight: 550; font-size: 0.75rem;
  text-transform: uppercase; letter-spacing: 0.03em; cursor: pointer;
  border-bottom: 1px solid var(--border-strong);
}
table.data thead th.nosort { cursor: default; }
table.data tbody tr:hover { background: var(--surface-2); }
table.data td.num { font-family: var(--mono); font-variant-numeric: tabular-nums; }
table.data tbody tr { cursor: pointer; }
table.data .name-cell { max-width: 26ch; overflow: hidden; text-overflow: ellipsis; }

/* Progression strip. Rows are not links, so they lose the pointer that
   table.data hands out by default. */
table.data.progress tbody tr { cursor: default; }
table.data.progress td { padding-top: 0.45rem; padding-bottom: 0.45rem; }
table.data.progress td.left { font-weight: 550; }
table.data.progress td.strong { font-size: 1.05rem; font-weight: 600; }
table.data.progress small { color: var(--text-muted); font-weight: 400; }

/* Direction is carried by colour and by the sign in the text, never by colour
   alone -- the sign is already there in fmtSigned, which is what a red/green
   pair has to fall back on.

   Card-scoped rather than --good/--critical: those two are tuned for the pills,
   which set them as a background behind dark text, and as 0.83rem foreground
   text they land at 3.1:1 (good on light) and 2.9:1 (critical on dark). Both are
   under 4.5:1. Retuning the globals would repaint every pill on the site, so the
   contrast is fixed here where the text lives. */
table.data.progress { --delta-up: #0a7d0a; --delta-down: #b32d2d; }
:root[data-theme="dark"] table.data.progress { --delta-up: #4ec94e; --delta-down: #f08a8a; }

table.data.progress td.delta.up { color: var(--delta-up); }
table.data.progress td.delta.down { color: var(--delta-down); }
table.data.progress td.delta.flat { color: var(--text-muted); }
table.data.progress td.delta.up small,
table.data.progress td.delta.down small { color: inherit; opacity: 0.85; }

table.data.progress td.sparkcell { width: 108px; padding-right: 0.5rem; }
/* currentColor on the polyline, so one declaration themes the whole thing. */
table.data.progress .spark { color: var(--series-1); display: block; }

table.data.progress .lbl-short { display: none; }

@media (max-width: 700px) {
  /* Trend and best-ever go first: both are context, while the two change
     columns are the entire point of the card and must survive to 390 px. */
  table.data.progress th:nth-child(6), table.data.progress td:nth-child(6) { display: none; }
}
@media (max-width: 560px) {
  table.data.progress th:nth-child(5), table.data.progress td:nth-child(5) { display: none; }
  table.data.progress .lbl-full { display: none; }
  table.data.progress .lbl-short { display: inline; }
  table.data.progress th, table.data.progress td { padding-left: 0.3rem; padding-right: 0.3rem; }
  table.data.progress td.strong { font-size: 1rem; }
}

/* Climb list: no column sorts, and a row selects a stretch of the map rather
   than navigating, so only the linkable variant gets a pointer. */
table.data.climbs thead th { cursor: default; }
table.data.climbs tbody tr { cursor: default; }
table.data.climbs.pick tbody tr { cursor: pointer; }
table.data.climbs tbody tr.sel {
  background: var(--surface-2); box-shadow: inset 3px 0 0 var(--series-8);
}

/* --------------------------------------------------------- calendar ------ */

.cal { display: flex; flex-direction: column; gap: 0.4rem; }
.cal-grid {
  display: grid; grid-auto-flow: column; grid-template-rows: repeat(7, 12px);
  gap: 2px; overflow-x: auto; padding-bottom: 0.2rem;
}
.cal-cell { width: 12px; height: 12px; border-radius: 2px; background: var(--surface-2); }
.cal-cell[data-l="0"] { background: var(--surface-2); }
.cal-cell[data-l="1"] { background: var(--ramp-1); }
.cal-cell[data-l="2"] { background: var(--ramp-2); }
.cal-cell[data-l="3"] { background: var(--ramp-3); }
.cal-cell[data-l="4"] { background: var(--ramp-4); }
.cal-cell[data-l="5"] { background: var(--ramp-5); }
.cal-cell[data-l="6"] { background: var(--ramp-6); }
.cal-months { display: flex; gap: 2px; font-size: 0.68rem; color: var(--text-muted); }

/* ---------------------------------------------------------- details ------ */

dl.kv {
  display: grid; grid-template-columns: auto 1fr; gap: 0.2rem 0.8rem;
  margin: 0; font-size: 0.85rem;
}
dl.kv dt { color: var(--text-muted); }
dl.kv dd { margin: 0; font-family: var(--mono); font-variant-numeric: tabular-nums; }

.bars { display: flex; flex-direction: column; gap: 3px; }
.bar-row { display: grid; grid-template-columns: 8.5rem 1fr 5.5rem; gap: 0.5rem; align-items: center; font-size: 0.8rem; }
.bar-track { background: var(--surface-2); border-radius: 3px; height: 14px; overflow: hidden; }
/* display:block matters — the fill is a span, and width does nothing inline. */
.bar-fill { display: block; height: 100%; border-radius: 3px; }
.bar-row .v { text-align: right; font-family: var(--mono); color: var(--text-secondary); }

/* ------------------------------------------------------------ login ------ */

body.login-page { display: grid; place-items: center; min-height: 100vh; }
.login-card {
  background: var(--surface-1); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 1.6rem; width: min(340px, 90vw);
}
.login-card h1 { margin-bottom: 0.2rem; }
.login-card form { display: grid; gap: 0.4rem; margin-top: 1rem; }
.login-card label { font-size: 0.78rem; color: var(--text-muted); }
.login-card input {
  font: inherit; padding: 0.45rem 0.6rem; border-radius: 8px;
  border: 1px solid var(--border-strong); background: var(--surface-0);
  color: var(--text-primary);
}
.login-card button {
  font: inherit; font-weight: 550; padding: 0.45rem; border-radius: 8px;
  border: none; background: var(--text-primary); color: var(--surface-1);
  cursor: pointer; margin-top: 0.3rem;
}
.error { color: var(--critical); font-size: 0.85rem; }

/* -------------------------------------------------------- utilities ----- */

.spinner { color: var(--text-muted); font-size: 0.85rem; padding: 1.5rem 0; text-align: center; }
.row { display: flex; gap: 0.6rem; align-items: center; flex-wrap: wrap; }
.hide { display: none !important; }
.prose { max-width: 68ch; margin: 0 auto; font-size: 0.9rem; }
.prose h2 { margin: 1.4rem 0 0.4rem; }
.prose h3 { margin: 1rem 0 0.25rem; }
.prose p, .prose li { color: var(--text-secondary); }
.prose code {
  font-family: var(--mono); font-size: 0.85em; background: var(--surface-2);
  padding: 0.05rem 0.3rem; border-radius: 4px; color: var(--text-primary);
}
.prose .ref { font-size: 0.82rem; }

/* --------------------------------------------------------- glossary ------ */

/* The marker has to survive being set in a tile label, a chart legend, a table
 * header and body prose, so it borrows the surrounding size and colour and
 * changes one thing only: the underline. A dotted rule reads as "there is more
 * here" without competing with the number the tile exists to show. */
/* Mixed from currentColor, not from a border token: the same rule has to sit
   under muted tile captions and full-strength prose in both themes, and a fixed
   grey that reads correctly on the light background all but vanishes on the
   dark one. */
abbr.gloss {
  text-decoration: none;                       /* Safari draws its own */
  border-bottom: 1px dotted color-mix(in oklab, currentColor 60%, transparent);
  cursor: help;
}
abbr.gloss:hover, abbr.gloss:focus-visible {
  border-bottom-color: currentColor;
  color: var(--text-primary);
}
abbr.gloss:focus-visible { outline: 2px solid var(--series-1); outline-offset: 2px; }
/* A dotted underline on a heading is heavier than on body text; on a th it
   would sit on the sort arrow. Both drop it and rely on hover instead. */
.card > header h2 abbr.gloss, th abbr.gloss { border-bottom-style: none; }
.card > header h2 abbr.gloss:hover, th abbr.gloss:hover,
.card > header h2 abbr.gloss:focus-visible, th abbr.gloss:focus-visible {
  border-bottom: 1px dotted currentColor;
}

/* Fixed, not absolute: triggers live inside cards that scroll and inside chart
   overlays that clip. z-index clears the chart tooltip at 50. */
.glosspop {
  position: fixed; z-index: 120; max-width: min(19rem, calc(100vw - 1rem));
  background: var(--surface-1); color: var(--text-secondary);
  border: 1px solid var(--border-strong); border-radius: 8px;
  padding: 0.5rem 0.6rem; font-size: 0.78rem; line-height: 1.45;
  box-shadow: 0 6px 20px rgb(0 0 0 / 0.18);
  text-transform: none; letter-spacing: normal; font-weight: 400;
}
.glosspop .g-head { display: flex; gap: 0.4rem; align-items: baseline; flex-wrap: wrap; }
.glosspop .g-head b { color: var(--text-primary); font-family: var(--mono); }
.glosspop .g-name { color: var(--text-muted); font-size: 0.74rem; }
.glosspop p { margin: 0.3rem 0 0; }
.glosspop .g-more {
  display: inline-block; margin-top: 0.4rem; font-size: 0.74rem;
  color: var(--series-1); text-decoration: none;
}
.glosspop .g-more:hover { text-decoration: underline; }

/* Methods page listing. Two columns on anything wide enough for the term to sit
   beside its definition rather than above it. */
.prose .glossary { margin: 0.6rem 0 0; }
.prose .glossary dt {
  margin-top: 0.7rem; color: var(--text-primary);
  display: flex; gap: 0.5rem; align-items: baseline; flex-wrap: wrap;
}
.prose .glossary dt abbr {
  font-family: var(--mono); font-weight: 600; text-decoration: none;
}
.prose .glossary dt .expand { color: var(--text-muted); font-size: 0.85em; }
.prose .glossary dd {
  margin: 0.15rem 0 0; padding-left: 0.75rem; color: var(--text-secondary);
  border-left: 2px solid var(--border);
}
/* Arriving from a popover's "How it is calculated" link: keep the entry clear of
   the sticky header, and colour it so the right one of twenty-odd is obvious. */
.prose .glossary dt, .prose h2[id] { scroll-margin-top: 4rem; }
.prose .glossary dt:target abbr,
.prose .glossary dt:target + dd { color: var(--series-1); }
.prose .glossary dt:target + dd { border-left-color: var(--series-1); }

@media (max-width: 620px) {
  .grid.cols-2, .grid.cols-3 { grid-template-columns: 1fr; }
  main { padding: 0.6rem; }
  .tile .value { font-size: 1.35rem; }
}

/* ----------------------------------------------------------- weight ------ */

/* Table wide, form narrow, and the form stays put while the list scrolls. */
.grid.weightgrid { grid-template-columns: minmax(0, 1.6fr) minmax(280px, 1fr); }
.weightgrid > .card:last-child { align-self: start; position: sticky; top: 0.6rem; }

.weightform {
  display: grid; gap: 0.55rem; grid-template-columns: 1fr 1fr;
  margin-top: 0.5rem;
}
.weightform label { display: flex; flex-direction: column; gap: 0.2rem; min-width: 0; }
.weightform label.wide, .weightform .formactions { grid-column: 1 / -1; }
.weightform label > span {
  font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--text-muted);
}
.weightform input, .weightform select {
  font: inherit; font-size: 0.85rem; min-width: 0;
  border: 1px solid var(--border); background: var(--surface-0);
  color: var(--text-primary); border-radius: 6px; padding: 0.3rem 0.45rem;
}
.weightform input:focus-visible, .weightform select:focus-visible {
  outline: 2px solid var(--border-strong); outline-offset: 1px;
}
.weightform .row { gap: 0.35rem; }
.weightform .row input { flex: 1 1 5rem; }
.weightform .row select { flex: 0 0 auto; }
/* The unit is picked in the Body mass card header now, so the box beside the
 * number only reports which one the typed value is being read as. */
/* text-transform is inherited from the .row, which is a label > span and so
 * styled as a caption. This is a unit, so undo it — "KG" reads as a heading. */
.weightform .row .unit {
  flex: 0 0 auto; font-size: 0.85rem; color: var(--text-muted);
  text-transform: none; letter-spacing: normal;
}
.weightform .formactions { display: flex; gap: 0.5rem; margin-top: 0.15rem; }

.btn {
  font: inherit; font-size: 0.83rem; cursor: pointer;
  border: 1px solid var(--border); background: var(--surface-1);
  color: var(--text-secondary); border-radius: 6px; padding: 0.3rem 0.7rem;
}
.btn:hover:not(:disabled) { border-color: var(--border-strong); color: var(--text-primary); }
.btn:disabled { opacity: 0.5; cursor: default; }
.btn.primary {
  border-color: var(--border-strong); color: var(--text-primary); font-weight: 550;
}
.btn.small { font-size: 0.75rem; padding: 0.12rem 0.4rem; }

/* 143 monthly weigh-ins is a very long page, and the notes under the table are
   worth reading, so scroll the rows instead of the document. thead is already
   sticky, so the header stays put inside it. */
.weightgrid .table-wrap { max-height: 62vh; overflow-y: auto; }

/* Two buttons per row, so the row itself must not also be a link target. */
.weightgrid table.data tbody tr { cursor: default; }
td.rowbtns { display: flex; gap: 0.3rem; justify-content: flex-end; }

/* Green for down, amber for up. This does assume losing is the direction you
   want, which is the whole reason a cyclist tracks mass against FTP; it is a
   hint on a column that already carries a sign, not a verdict. */
table.data td.up { color: var(--warning); }
table.data td.down { color: var(--good); }

.formnote {
  font-size: 0.78rem; margin: 0.5rem 0 0.1rem; color: var(--text-muted);
  min-height: 1.2em;
}
.formnote.ok { color: var(--good); }
.formnote.warn { color: var(--warning); }
.formnote.bad { color: var(--critical); }

@media (max-width: 820px) {
  .grid.weightgrid { grid-template-columns: 1fr; }
  /* Sticky inside a single column would pin the form over the table. */
  .weightgrid > .card:last-child { position: static; }
}

/* -------------------------------------------------------------- maps ----- */

.map {
  width: 100%; height: 380px;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface-2);
  /* Leaflet puts panes and controls at z-index 400-1000; a stacking context
     here keeps them under the site header instead of over it. */
  isolation: isolate;
}

/* Leaflet ships its own font stack and a white chrome. Its stylesheet is
   injected at first use, so it lands after this file and wins on source order;
   the extra `.map` in each selector is what puts these back on top. */
.map.leaflet-container { font-family: var(--sans); font-size: 0.75rem; }
.map .leaflet-bar a,
.map .leaflet-control-attribution {
  background: var(--surface-1); color: var(--text-secondary);
  border-color: var(--border);
}
.map .leaflet-bar a:hover { background: var(--surface-2); color: var(--text-primary); }
.map .leaflet-control-attribution a { color: var(--series-1); }

@media (max-width: 620px) {
  .map { height: 260px; }
}

/* OSM only publishes a light raster style. Inverting the tile pane is the
   standard trick: invert, then rotate the hue back so water is blue again
   rather than orange. Applied to the tiles alone, so the route and its
   markers keep their own colours. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .leaflet-tile-pane {
    filter: invert(1) hue-rotate(180deg) brightness(0.92) contrast(0.9);
  }
}
:root[data-theme="dark"] .leaflet-tile-pane {
  filter: invert(1) hue-rotate(180deg) brightness(0.92) contrast(0.9);
}
