/* §12.2 — the single source of truth for colour and size.
   No hard-coded colours or sizes anywhere else in the codebase. Every value in
   app.css references a token defined here. */

:root {
  /* Declares the palette below as already-dark. Without this, Chromium's
     auto-dark heuristic (Samsung Internet's "force dark mode on websites")
     reads the page as light-themed and inverts everything. Paired with
     <meta name="color-scheme" content="dark"> in index.html so the opt-out
     applies before the stylesheet lands. */
  color-scheme: dark;

  /* ---- Colour: warm dark greys, a gym at night rather than a terminal ---- */
  --bg: #14110F;
  --surface: #1E1A17;
  --surface-alt: #292420;
  --line: #3A3330;
  --text: #F0EBE5;
  --text-dim: #9A918A;
  --accent: #E8833A;
  --good: #6FA86A;
  --warn: #C9A227;
  --bad: #B5503C;
  /* §12.5a elevation needs a tone BELOW --bg — on a dark UI a shadow cannot be
     derived from the surfaces it sits under. Warm-shifted like the rest. */
  --shadow: #0A0806;

  /* Derived, so the palette above stays the only place real colours live.
     --bad at full strength is 3.4:1 on --surface, under the §12.4 floor, so
     body text in the "bad" role uses the lifted variant (5.5:1). The raw token
     stays for fills, borders and bars where the floor does not apply. */
  --bad-text: color-mix(in srgb, var(--bad) 70%, var(--text));
  --accent-soft: color-mix(in srgb, var(--accent) 14%, transparent);
  --accent-line: color-mix(in srgb, var(--accent) 45%, var(--line));
  --warn-line: color-mix(in srgb, var(--warn) 40%, var(--line));
  --good-line: color-mix(in srgb, var(--good) 45%, var(--line));
  --scrim: color-mix(in srgb, var(--bg) 88%, transparent); /* sticky header */
  --veil: color-mix(in srgb, var(--bg) 78%, transparent); /* modal backdrop */

  /* ---- §12.5a Elevation ----
     Three surface levels read as layers, not swatches. Light is implied from
     above: a lifted hairline on the top edge, a soft shadow below. Kept
     deliberately low-contrast — cumulatively this gives the screen structure,
     but no single card should announce itself. */
  --shadow-soft: color-mix(in srgb, var(--shadow) 50%, transparent);
  --shadow-softer: color-mix(in srgb, var(--shadow) 28%, transparent);

  /* Edges: lit catches implied light, shade turns away from it. */
  --edge-lit: color-mix(in srgb, var(--line) 62%, var(--text));
  --edge-shade: color-mix(in srgb, var(--line) 55%, var(--shadow));

  /* Rows inside a card separate on a hairline, not on spacing alone. */
  --line-soft: color-mix(in srgb, var(--line) 55%, transparent);

  /* Raised: cards and rows. Higher: the modal sheet. */
  --elev-1: 0 var(--hair) var(--s-1) calc(-1 * var(--hair)) var(--shadow-softer),
            0 var(--s-1) var(--s-3) calc(-1 * var(--s-2)) var(--shadow-soft);
  --elev-2: 0 var(--s-1) var(--s-3) calc(-1 * var(--s-1)) var(--shadow-softer),
            0 var(--s-2) var(--s-8) calc(-1 * var(--s-3)) var(--shadow-soft);

  /* Recessed: inputs sit IN the surface, so the edge inverts — shade on top,
     lit along the bottom. */
  --inset-1: inset 0 var(--hair) var(--s-1) calc(-1 * var(--hair)) var(--shadow-soft);

  /* Active state is an accent EDGE, never a filled block — a fill reads as a
     button, an edge reads as "you are here". Carried as a transparent border
     on the base rule so turning it on shifts no layout. */
  --edge-active: var(--s-1);

  /* Tier scale as palette (§12.2): S+/S at the accent end, A/B neutral,
     C and below desaturated toward --text-dim. Confidence is a separate axis
     and never changes hue — only the dotted underline.
     The ramp bottoms out AT --text-dim rather than below it: badge letters are
     text, so every band has to clear the §12.4 4.5:1 floor. Desaturating past
     --text-dim measured 2.96:1 and was unreadable at low brightness. */
  --tier-s: var(--accent);
  --tier-a: color-mix(in srgb, var(--accent) 42%, var(--text));
  --tier-b: var(--text);
  --tier-c: color-mix(in srgb, var(--text-dim) 60%, var(--text));
  --tier-d: var(--text-dim);

  /* ---- Space: 4px base unit ---- */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 20px;
  --s-6: 24px;
  --s-8: 32px;

  /* Card padding: 16px mobile, 12px desktop (§12.2) */
  --card-pad: var(--s-4);

  /* ---- Radius ---- */
  --r-sm: 6px;
  --r-md: 10px;
  --r-lg: 14px;
  --r-pill: 999px;
  --hair: 1px;

  /* ---- Type ---- */
  --font-ui: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --fs-label: 0.8125rem; /* labels and units */
  --fs-body: 0.9375rem;
  --fs-name: 1.125rem; /* exercise names */
  --fs-input: 1.125rem; /* planning numerics */
  --fs-num: 2rem; /* the set row's weight and reps — §12.3, step 4 */
  --lh-tight: 1.2;
  --lh-body: 1.45;

  --ls-caps: 0.04em;
  --ls-tight: -0.01em;
  --underline-offset: 2px;

  /* ---- Controls ---- */
  --tap: 48px; /* §12.4 floor, one-handed with poor grip */
  --tap-lg: 64px; /* §12.3 set-row steppers — thumb-sized mid-set */
  --header-h: 73px; /* sticky app header — what the rest timer pins below */
  --focus: 2px;

  /* ---- Layout: column minimums and container widths.
     Tokenised for the same reason as everything else — these are the values
     that otherwise drift step to step. Each grid uses min(track, 100%) so a
     360px screen collapses to one column instead of overflowing (§12.4). ---- */
  --w-main: 900px;
  --w-sheet: 640px;
  /* `dvh`, not `vh`, and on a phone they are different numbers. `vh` resolves
     against the LARGE viewport — the one with Chrome's URL bar retracted — while
     `.overlay` is `position: fixed`, which gets the viewport as it is right now.
     With the bar showing, 88vh can exceed the box the sheet sits in, the overlay
     starts overflowing, and `align-items: flex-end` then clips the top of the
     sheet where nothing can scroll it back. Desktop never sees it; the S23 is
     the case this app is logged on. The `vh` line stays as the fallback. */
  --h-sheet: 88vh;
  --h-sheet: 88dvh;
  --col-card: 240px; /* template cards */
  --col-day: 104px; /* weekday grid */
  --col-eq: 128px; /* equipment toggles */
  --col-inv: 210px; /* specific-equipment rows */
  --h-inv: 320px; /* inventory scroll area */
  --col-num: 72px; /* numeric field, fluid on mobile */
  /* A field holding a PAIR of numerics plus the separator between them — the
     rep range. `auto-fit` tracks are all the same width, so the widest field in
     a row sets the minimum for every track: size the track off the pair or the
     pair is the one that gets squeezed. At 72px the rep inputs came out 36px
     wide on a 360px screen, an 18px content box against 23px of two-digit
     numeral, and every range from 10 up was clipped. Held under 139px so two
     tracks plus the --s-3 gap still fit a 360px row (§12.4). */
  --col-num-pair: 136px;
  --w-field-max: 116px; /* its desktop cap — denser, §12.1 */
  --w-field-pair-max: calc(var(--w-field-max) * 2); /* the pair's, likewise */
  --w-search: 220px;
  --w-gym-name: 180px;
  --w-mg-label: 82px;
  --w-prog-field: 200px; /* §11 chart pickers — wide enough for a lift name */

  /* ---- §12.4b body map ----
     The ramp itself is --accent mixed toward --bm-unworked, so no new hue
     enters the palette: a single hue carries all the information. */
  /* Corrected after device testing. These carry their own literals rather than
     aliasing --surface-alt / --bg: the figure is read at arm's length in a dim
     room, and pinning it to the surface ramp made an unworked muscle sit within
     a few percent of the page behind it. The figure needs its own contrast
     relationship, so it gets its own values — declared here, where literals
     belong, and referenced by token everywhere else (§12.2). */
  --bm-unworked: #3E3833; /* ratio 0 — flat, but unmistakably a muscle */
  --bm-sep: #12100E; /* the divider every region carries */
  /* Under the minimum: the SAME stroke, recoloured to a thin near-white
     outline. It was --warn at double width, which drew the eye to the outline
     instead of the muscle and buried the fill underneath it. The fill is the
     measurement; the outline only says the week is short of its floor. */
  --bm-low: var(--text);
  /* Head, hands, feet, joints: reads "not applicable", so it must sit apart
     from --bm-unworked rather than a shade of it. */
  --bm-inert: color-mix(in srgb, var(--line) 50%, var(--bg));
  --stroke-map: 1.2px; /* with vector-effect: non-scaling-stroke — see body-map.css */
  --stroke-map-low: var(--stroke-map); /* same width — the warning is colour, never weight */
  --w-map-sm: 128px; /* static, on an exercise card */
  --w-map-lg: 200px; /* plan editor and weekly volume */
  --col-map: 220px; /* the two-scope grid's track */

  /* ---- §11 charts ----
     Geometry as well as colour, because SVG takes r/stroke-width from CSS: the
     chart's literals belong here with everything else. */
  --chart-band: color-mix(in srgb, var(--good) 16%, transparent);
  --chart-grid: color-mix(in srgb, var(--line) 70%, transparent);
  --stroke-series: 2px;
  --dot-r: 3px;
  --dot-r-pr: 6px;

  /* ---- Opacity ---- */
  --op-muted: 0.7;
  --op-dim: 0.6;
  --op-hidden: 0.62;
}

/* Desktop is the denser context — same tokens, tighter cards and names. */
@media (min-width: 700px) {
  :root {
    --card-pad: var(--s-3);
    --fs-name: 1rem;
    --fs-input: 1rem;
  }
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  /* §12.4: no horizontal scroll at 360px.
     `clip`, NOT `hidden` — `overflow-x: hidden` forces overflow-y to `auto`,
     which makes html/body a scroll container and silently kills every
     `position: sticky` descendant (the header and the rest timer both scrolled
     away off-screen). `clip` leaves overflow-y `visible`, so sticky survives. */
  overflow-x: clip;
}

/* Base font goes on body, NOT html: setting it on the root element rescales
   every rem, which would shrink §12.2's 2rem set-row numerals to 30px. */
body {
  background: var(--bg);
  color: var(--text);
  font: var(--fs-body) / var(--lh-body) var(--font-ui);
}

/* Numerals are the content of this app (§12.2) — tabular everywhere they
   appear, so columns align and values don't jitter as they change. */
.num,
input[type='number'],
.score,
.rot-idx,
.tier-badge,
.sess-count,
.tpl-days,
.gym-meta,
.lib-summary,
.slot-num,
.bucket {
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
}

:focus-visible {
  outline: var(--focus) solid var(--accent);
  outline-offset: 2px;
}

/* §12.4: motion is functional only. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
