/* =============================================================================
   RoxPulse — visual system
   One theme, and it is HYROX's: black, white, and one yellow. MacroDaddy's
   geometry, spacing scale and liquid glass carried over; the palette does not.
   The surface is true black and the only saturated thing on any screen is the
   number you came to read.
   ============================================================================= */

:root {
  /* === Surfaces ===
     True black, because hyrox.com ships `theme-color: #000000` and the whole
     identity is built on it. The greys above it are NEUTRAL — the previous
     palette tinted every surface slightly blue, which read as "fitness tech"
     rather than HYROX's industrial monochrome. On OLED the black is also free
     battery, which matters for a screen held awake through a 90-minute race. */
  --bg:        #000000;   /* the page — matches hyrox.com exactly */
  --bg-card:   oklch(16.5% 0 0);   /* was #0D0D0D */
  --bg-soft:   oklch(22.6% 0 0);   /* was #171717 — inputs, chips, inset tiles */
  --bg-lift:   oklch(28.4% 0 0);   /* was #232323 — pressed / hovered */

  /* === Text === */
  --fg:        #FFFFFF;
  --fg-soft:   #C9C9C9;
  --fg-muted:  #9A9A9A;
  --fg-faint:  #6B6B6B;

  /* === Lines === */
  --line:        #212121;
  --line-strong: #333333;

  /* === Signal ===
     One accent, and it is HYROX yellow. Text ON yellow is always black: white
     on this yellow is about 1.2:1 and completely unreadable, which is an easy
     mistake to make and an ugly one to ship. */
  --accent:      oklch(91.2% 0.1885 105.6);   /* HYROX yellow, ≈ #FFE600 */
  --accent-ink:  #000000;
  --accent-soft: #332E00;
  --accent-dim:  #B8A600;

  /* === Data series ===
     Three fills for the race-time split, chosen inside the brand rather than
     invented alongside it: yellow stations, white running, grey transitions.
     Worst adjacent separation is ΔE 20.1 for deuteranopia and 20.9 for normal
     vision — well past the ΔE 8 target — and all three clear 3:1 on black.

     A two-tone brand cannot satisfy a categorical palette's lightness-band and
     chroma-floor checks; nothing monochrome can. What those checks protect is
     "can these be told apart", and the discriminability numbers above answer
     that directly. Identity is never carried by colour alone here either: the
     split bar ships a key with a swatch, a name, a time and a percentage. */
  --run:     #FFFFFF;
  --station: #FFE600;
  --rox:     #8C8C8C;
  --strength:#FFFFFF;   /* prescribed loads — neutral, so yellow stays reserved */

  /* Status colours are reserved and never reused as a series. They also never
     carry meaning alone: every delta in the app renders with a ▲/▼ glyph. */
  --up:      #3FD97F;
  --down:    #FF4D4D;
  --danger:  #FF4D4D;
  --warn:    #FFB020;

  /* === Elevation === */
  --shadow-sm: 0 1px 2px rgba(0,0,0,.6);
  --shadow:    0 2px 6px rgba(0,0,0,.5), 0 14px 34px rgba(0,0,0,.55);
  --shadow-lg: 0 6px 18px rgba(0,0,0,.6), 0 30px 64px rgba(0,0,0,.68);
  --glow:      0 0 0 1px rgba(255,230,0,.3), 0 6px 26px rgba(255,230,0,.16);

  /* === Geometry === */
  --radius-sm: 10px;
  --radius:    14px;
  --radius-lg: 18px;
  --radius-xl: 24px;
  --radius-pill: 999px;

  /* === Spacing (4-pt) === */
  --space-1: 4px;  --space-2: 8px;  --space-3: 12px; --space-4: 16px;
  --space-5: 20px; --space-6: 24px; --space-7: 32px; --space-8: 40px;

  /* === Type ===
     Seven steps, and the floor is 12px. The dashboard's single most common
     text style used to be 11px/400 — seventeen instances of it — which is not
     a scale, it is everything that wasn't a headline being dumped into the
     smallest available size. 11px is also below what anyone reads on a phone
     propped against a rower at arm's length. */
  --text-xs: 12px;  --text-sm: 13px;  --text-md: 14px;
  --text-base: 15px; --text-lg: 18px; --text-xl: 22px;
  --text-2xl: 28px; --text-3xl: 34px; --text-4xl: 44px;

  /* === Layout === */
  --pad: var(--space-4);
  --tabbar-h: 66px;
  --topbar-h: 56px;

  /* === Motion ===
     Real spring curves, sampled with linear(). A cubic-bezier can only ever
     approximate a spring — it has two control points and a spring has
     overshoot AND settle, so the classic (.34,1.56,.64,1) fakes the bounce and
     then lands wrong. linear() takes as many stops as you give it, so these
     are actual damped-harmonic samples and the difference is legible: the
     overshoot decays instead of stopping dead.

     Fallbacks sit above each one for engines without linear() — the property
     is simply re-declared, so an engine that can't parse the second
     declaration keeps the first. */
  --ease:        cubic-bezier(.25,.46,.45,.94);
  --ease-out:    cubic-bezier(.16,1,.3,1);
  --spring:      cubic-bezier(.34,1.56,.64,1);
  --spring:      linear(0, 0.0088 1.4%, 0.0346 2.9%, 0.1379 6%, 0.5088 12.4%, 0.7245 16%, 0.9098 19.8%, 1.0432 23.7%, 1.1257 27.9%, 1.1607 32.6%, 1.155 36.2%, 1.1233 40.3%, 1.0311 50%, 0.9876 55.5%, 0.9738 61.6%, 0.9843 71.6%, 1.0034 85.4%, 1);
  --spring-soft: cubic-bezier(.22,1,.36,1);
  --spring-soft: linear(0, 0.0148 2.3%, 0.0570 4.9%, 0.2204 10.6%, 0.4933 17.6%, 0.7268 24%, 0.8646 29.2%, 0.9401 34%, 0.9803 39.4%, 0.9992 47.2%, 1.0055 55.6%, 1.0018 70%, 1);
  --spring-tight: linear(0, 0.0435 3.3%, 0.1704 6.9%, 0.5867 15%, 0.7742 19.2%, 0.9257 23.7%, 0.9871 26.5%, 1.0298 29.6%, 1.0554 33.3%, 1.0619 37.6%, 1.0396 45.7%, 1.0011 56.4%, 0.9925 62.6%, 0.9992 82.4%, 1);
  --dur-fast: .16s;

  /* === Glass material === */
  --glass-blur: 30px;
  --glass-sat: 160%;
  --glass-bright: 1.05;
  --glass-fill: 66%;
  --glass-rim: 0.10;

  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI",
               system-ui, "Helvetica Neue", Arial, sans-serif;
  --font-display: "Barlow Condensed", "Oswald", -apple-system, sans-serif;

  color-scheme: dark;
}

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

/* The chrome is hidden with the `hidden` attribute, and half of it carries
   `display: grid` — which outranks the user-agent's `[hidden] { display: none }`
   on specificity and leaves the dock, the + and the back arrow sitting on top
   of the onboarding screen. This is the one place !important earns its keep:
   hidden must mean hidden regardless of what a component declares. */
[hidden] { display: none !important; }

html, body {
  margin: 0; padding: 0;
  background: var(--bg);
  color: var(--fg);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* Two separate problems, one property. Pull-to-refresh will reload the app
     mid-entry if you scroll up at the top of a list — in a standalone PWA that
     looks like a crash. And the rubber-band overscroll drags the page off its
     own background, exposing bare white at the edges on iOS. `none` stops both. */
  overscroll-behavior-y: none;
}
html { height: 100%; }

/* The chrome is furniture, not content. Long-pressing a tab should not raise
   the iOS text magnifier and a Copy bubble, and dragging across the dock
   should not select its labels. Content keeps normal selection so a session
   note can still be copied out. */
.topbar, .tabbar, .fab, .tab, .chip, .seg, .btn, .linkbtn, .badge,
.station-num, .metric, .race-actions, .modal-grip {
  -webkit-user-select: none; user-select: none;
  -webkit-touch-callout: none;
}
* { -webkit-tap-highlight-color: transparent; }

body {
  display: flex; flex-direction: column;
  min-height: 100dvh;
  padding-top: calc(var(--topbar-h) + env(safe-area-inset-top));
  /* The dock floats 10px off the bottom, so clearing it needs its height plus
     that offset plus breathing room — not just its height, which left the last
     card tucked under the glass. */
  padding-bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom) + var(--space-7));
  font-size: var(--text-base);
  line-height: 1.45;
  font-variant-numeric: tabular-nums;
  /* One very slow yellow wash at the top. hyrox.com is flat black and this is
     nearly that — just enough that an OLED panel doesn't read as a dead
     screen, and no blue anywhere. */
  background-image:
    radial-gradient(130% 58% at 50% -16%, rgba(255,230,0,.045), transparent 64%);
  background-attachment: fixed;
}
body.no-chrome { padding-bottom: var(--space-6); }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
  }
}

button { font: inherit; color: inherit; background: transparent; border: none; cursor: pointer; }
a { color: var(--accent); }
h1, h2, h3, h4 { margin: 0; font-weight: 700; letter-spacing: -0.01em; }

/* Screen titles in the display face, uppercase and tight — the same voice
   HYROX's own branding uses, and the thing that was missing when the display
   family only ever appeared on numerals. */
.h-screen {
  font-family: var(--font-display);
  font-size: 34px; font-weight: 800; line-height: 1;
  text-transform: uppercase; letter-spacing: .012em;
}
.h-screen em { font-style: italic; font-weight: 900; color: var(--accent); }
.screen-head { margin-bottom: var(--space-4); }
.screen-head .screen-sub { margin-top: 5px; font-size: var(--text-sm); color: var(--fg-muted); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 6px; }

/* =========================================================================
   Liquid glass — one material, shared by the header, the dock and sheets
   ========================================================================= */
:where(.glass) { position: relative; }
.glass {
  isolation: isolate;
  background: color-mix(in srgb, var(--bg-card) var(--glass-fill), transparent);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat)) brightness(var(--glass-bright));
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat)) brightness(var(--glass-bright));
  border: 1px solid rgba(255,255,255,.07);
  box-shadow:
    0 12px 38px -8px rgba(0,0,0,.66),
    0 2px 10px rgba(0,0,0,.40),
    inset 0 1px 0 rgba(255,255,255,var(--glass-rim)),
    inset 0 -1px 0 rgba(0,0,0,.42),
    inset 0 0 26px -8px rgba(255,255,255,.08);
}
/* Specular sheen sits UNDER the content so labels stay crisp, and follows the
   contact point written by glass.js into --gx/--gy/--gp. */
.glass::after {
  content: ""; position: absolute; inset: 0;
  border-radius: inherit; pointer-events: none; z-index: -1;
  background:
    radial-gradient(120px 90px at var(--gx,50%) var(--gy,0%),
      rgba(255,255,255,calc(.14 * var(--gp,0))), transparent 70%),
    linear-gradient(to bottom, rgba(255,255,255,.13) 0%, rgba(255,255,255,.035) 26%, transparent 58%);
  transition: background .22s ease;
}
.glass-capsule { border-radius: var(--radius-pill); }
.glass-interactive { transition: transform .2s var(--spring-soft); --gp: 0; }
.glass-interactive.is-touched { transform: scale(.985); }

@media (prefers-reduced-transparency: reduce) {
  .glass, .topbar, .tabbar {
    -webkit-backdrop-filter: none; backdrop-filter: none;
    background: var(--bg-card);
  }
  .glass::after { display: none; }
}

/* =========================================================================
   Top bar
   ========================================================================= */
.topbar {
  position: fixed; top: 0; left: 0; right: 0;
  height: calc(var(--topbar-h) + env(safe-area-inset-top));
  display: flex; align-items: center; gap: var(--space-2);
  padding: 0 var(--space-3);
  padding-top: env(safe-area-inset-top);
  border-radius: 0;
  border-left: none; border-right: none; border-top: none;
  border-bottom: 1px solid rgba(255,255,255,.06);
  z-index: 60;
}
.topbar.is-scrolled { --glass-fill: 84%; }

/* 44 × 44 is the floor for anything tappable — Apple's HIG figure and the WCAG
   2.2 target-size minimum. Several controls here were drawn at 32–40px because
   that is what looked right; on glass, wet-handed, at arm's length, it is not
   what works. Where the visual box needs to stay small, the TARGET is grown
   with padding instead of the background. */
.iconbtn {
  width: 44px; height: 44px; flex: none;
  display: grid; place-items: center;
  border-radius: 12px;
  color: var(--fg-soft);
  transition: background var(--dur-fast) var(--ease), transform .1s var(--ease);
}
.iconbtn:hover { background: var(--bg-soft); color: var(--fg); }
.iconbtn:active { transform: scale(.92); }

.brand-home {
  display: flex; align-items: center; gap: var(--space-2);
  padding: 7px 10px; min-height: 44px; border-radius: 12px; flex: none;
  transition: background var(--dur-fast) var(--ease), transform .1s var(--ease);
}
.brand-home:active { transform: scale(.96); }
/* The wordmark IS the logo — there is no icon beside it any more. A mark plus
   a wordmark in a 56px header was two logos competing for 44px of height, and
   the mark simply repeated the app icon the user tapped to get here.

   Geometric sans, sentence case, upright. This is the brand's voice and it is
   deliberately NOT the condensed italic face the app uses for numerals — the
   logo should not look like a split time. */
.brand-word {
  font-family: "Poppins", -apple-system, BlinkMacSystemFont, "Segoe UI",
               system-ui, "Helvetica Neue", Arial, sans-serif;
  font-size: 21px; font-weight: 600;
  letter-spacing: -0.006em;
  line-height: 1; color: #FFFFFF; white-space: nowrap;
}
.brand-word em { font-style: normal; font-weight: 600; color: var(--accent); }

.topbar-right { display: flex; align-items: center; gap: var(--space-2); margin-left: auto; flex: none; }

/* The race countdown lives in the header, permanently. It is the number the
   whole app is organised around, and it should never require navigation. */
.countdown-pill {
  display: flex; align-items: baseline; gap: 5px;
  padding: 6px 12px; min-height: 44px;
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--accent) 13%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 26%, transparent);
  transition: background var(--dur-fast) var(--ease), transform .1s var(--ease);
}
.countdown-pill:active { transform: scale(.95); }
.countdown-num {
  font-family: var(--font-display); font-size: 19px; font-weight: 900;
  font-style: italic; line-height: 1; color: var(--accent);
}
.countdown-label { font-size: var(--text-xs); font-weight: 600; color: var(--accent-dim); letter-spacing: .02em; }
.countdown-pill.is-past { background: rgba(140,147,166,.12); border-color: rgba(140,147,166,.24); }
.countdown-pill.is-past .countdown-num { color: var(--fg-soft); }
.countdown-pill.is-past .countdown-label { color: var(--fg-muted); }
.countdown-pill.is-soon { animation: pulse-soft 2.6s var(--ease) infinite; }
@keyframes pulse-soft {
  0%,100% { box-shadow: 0 0 0 0 rgba(255,230,0,0); }
  50%     { box-shadow: 0 0 0 5px rgba(255,230,0,.09); }
}

/* =========================================================================
   Main view
   ========================================================================= */
.view {
  flex: 1; width: 100%; max-width: 720px;
  margin: 0 auto; padding: var(--pad);
  outline: none;
}
@media (min-width: 768px) { .view { padding: var(--space-6) var(--space-5); max-width: 760px; } }

/* =========================================================================
   Tab bar — a floating dock, not a strip glued to the bottom
   ========================================================================= */
.tabbar {
  position: fixed;
  bottom: calc(env(safe-area-inset-bottom) + 10px);
  left: 50%; transform: translateX(-50%);
  width: min(calc(100% - 24px), 440px);
  height: var(--tabbar-h);
  display: grid; grid-template-columns: 1fr 1fr 66px 1fr 1fr;
  border-radius: 26px;
  z-index: 60;
}
.tab {
  display: grid; place-items: center; grid-template-rows: auto auto; gap: 3px;
  padding: 8px 2px 6px;
  color: var(--fg-muted);
  font-size: 11px; font-weight: 600; letter-spacing: .01em;
  -webkit-tap-highlight-color: transparent;
  position: relative;
  transition: color .18s var(--ease), transform .3s var(--spring-soft);
  border-radius: 20px;
}
.tab .tab-icon {
  width: 34px; height: 30px; display: grid; place-items: center;
  border-radius: 999px;
  transition: background .18s var(--ease), transform .18s var(--spring-soft);
}
.tab.active { color: var(--accent); }
.tab.active .tab-icon {
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  transform: translateY(-1px);
}
.tab:not(.active):active .tab-icon { background: rgba(255,255,255,.06); }
.tab:active { transform: scale(.96); }
.tab-icon svg { display: block; }
.tab-label { line-height: 1; font-size: 11px; }
.tab-spacer { width: 66px; }

/* The + sits in the dock's notch and is the primary action of the whole app. */
.fab {
  position: fixed;
  bottom: calc(env(safe-area-inset-bottom) + 22px);
  left: 50%; transform: translateX(-50%);
  width: 56px; height: 56px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: linear-gradient(155deg, var(--accent), #E5B800);
  color: var(--accent-ink);
  box-shadow: 0 6px 22px rgba(255,230,0,.32), 0 2px 6px rgba(0,0,0,.5);
  z-index: 61;
  transition: transform .34s var(--spring), opacity .2s ease, filter .26s ease;
}
.fab:active { transform: translateX(-50%) scale(.92); }
.fab.fab-hidden { transform: translateX(-50%) scale(.2) translateY(14px); opacity: 0; filter: blur(6px); pointer-events: none; }
@media (prefers-reduced-motion: reduce) { .fab.fab-hidden { transform: translateX(-50%); filter: none; } }

/* =========================================================================
   Cards & primitives
   ========================================================================= */
.card {
  background: var(--bg-card);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--pad);
  box-shadow: var(--shadow);
  margin-bottom: var(--space-3);
}
.card.flush { padding: 0; overflow: hidden; }
/* Descendant, not child: several cards put their heading inside a .row-between
   next to an action link, and a child selector silently dropped those back to
   browser-default h2 — a 24px serif-weight "Recent" in the middle of a screen
   of 11px eyebrows. */
.card h2, .card-title {
  margin: 0 0 var(--space-3);
  font-size: var(--text-xs); font-weight: 700;
  text-transform: uppercase; letter-spacing: .09em;
  color: var(--fg-muted);
}
.card-hero {
  background:
    radial-gradient(120% 130% at 84% -20%, rgba(255,230,0,.14), transparent 58%),
    var(--bg-card);
  border-color: rgba(255,230,0,.18);
}

.row { display: flex; align-items: center; gap: var(--space-3); }
.row-between { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); }
.stack { display: grid; gap: var(--space-3); }
.grow { flex: 1; min-width: 0; }
.muted { color: var(--fg-muted); }
.small { font-size: var(--text-md); }
.tiny  { font-size: var(--text-xs); }
.eyebrow {
  font-size: var(--text-xs); font-weight: 700;
  text-transform: uppercase; letter-spacing: .09em; color: var(--fg-muted);
}
.center { text-align: center; }
.nowrap { white-space: nowrap; }
hr.sep { border: none; border-top: 1px solid var(--line); margin: var(--space-4) 0; }

/* Big metric numerals — the reason the display face is loaded at all. */
.metric {
  font-family: var(--font-display);
  font-weight: 900; font-style: italic;
  line-height: .92; letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}
.metric-xl { font-size: 54px; }
.metric-lg { font-size: 38px; }
.metric-md { font-size: 27px; }
.metric-sm { font-size: 21px; font-style: normal; font-weight: 800; }
.metric-unit { font-family: inherit; font-size: var(--text-md); font-weight: 600; font-style: normal; color: var(--fg-muted); margin-left: 3px; }

/* Buttons */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  min-height: 46px; padding: 11px 20px;
  border-radius: var(--radius-pill);
  background: var(--accent); color: var(--accent-ink);
  font-size: var(--text-base); font-weight: 700;
  box-shadow: 0 2px 10px rgba(255,230,0,.24);
  transition: transform .12s var(--ease), background .16s var(--ease), box-shadow .16s var(--ease);
  -webkit-tap-highlight-color: transparent;
}
.btn:hover { background: #FFEE4D; }
.btn:active { transform: scale(.96); }
.btn.block { display: flex; width: 100%; }
.btn.secondary {
  background: var(--bg-soft); color: var(--fg);
  border: 1px solid var(--line-strong); box-shadow: none;
}
.btn.secondary:hover { background: var(--bg-lift); }
.btn.ghost { background: transparent; color: var(--fg-soft); border: 1px solid var(--line-strong); box-shadow: none; }
.btn.ghost:hover { background: var(--bg-soft); color: var(--fg); }
.btn.danger { background: var(--danger); color: #140000; box-shadow: none; }
.btn.small { min-height: 44px; padding: 10px 16px; font-size: var(--text-md); }
.btn.big { min-height: 62px; font-size: var(--text-lg); font-weight: 800; letter-spacing: .01em; }
.btn[disabled] { opacity: .45; pointer-events: none; }

.linkbtn {
  display: inline-flex; align-items: center; gap: 5px;
  min-height: 44px; padding: 8px 14px;
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--accent) 11%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 22%, transparent);
  color: var(--accent);
  font-size: var(--text-md); font-weight: 600;
  transition: background var(--dur-fast) var(--ease), transform var(--dur-fast) var(--spring-soft);
}
.linkbtn:hover { background: color-mix(in srgb, var(--accent) 19%, transparent); }
.linkbtn:active { transform: scale(.96); }
.linkbtn.quiet { background: transparent; border-color: var(--line-strong); color: var(--fg-soft); }
.linkbtn.quiet:hover { background: var(--bg-soft); color: var(--fg); }

/* Chips / segmented controls */
.chips { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 44px; padding: 10px 15px;
  border-radius: var(--radius-pill);
  background: var(--bg-soft); border: 1px solid var(--line);
  color: var(--fg-soft); font-size: var(--text-md); font-weight: 600;
  transition: all var(--dur-fast) var(--ease);
  -webkit-tap-highlight-color: transparent;
}
.chip:hover { background: var(--bg-lift); color: var(--fg); }
.chip:active { transform: scale(.95); }
.chip.active {
  background: color-mix(in srgb, var(--accent) 17%, transparent);
  border-color: color-mix(in srgb, var(--accent) 42%, transparent);
  color: var(--accent);
}
.seg {
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  background: var(--bg-soft); border: 1px solid var(--line);
  border-radius: var(--radius-pill); padding: 3px; gap: 2px;
}
.seg button {
  padding: 10px 6px; border-radius: var(--radius-pill);
  font-size: var(--text-md); font-weight: 600; color: var(--fg-muted);
  transition: all var(--dur-fast) var(--ease);
  min-height: 44px;
}
.seg button.active { background: var(--accent); color: var(--accent-ink); }

/* Forms */
label.field { display: block; margin-bottom: var(--space-3); }
.field > span.label {
  display: block; margin-bottom: 6px;
  font-size: var(--text-xs); font-weight: 700;
  text-transform: uppercase; letter-spacing: .08em; color: var(--fg-muted);
}
input[type="text"], input[type="number"], input[type="date"], input[type="search"], select, textarea {
  width: 100%;
  min-height: 48px; padding: 12px 13px;
  background: var(--bg-soft);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  color: var(--fg);
  font-family: inherit;
  /* 16px is not a taste decision. iOS Safari zooms the entire page whenever it
     focuses a field rendered below 16px, and it does not zoom back out — one
     tap on Notes and the whole app is left magnified and horizontally
     scrollable. Every field in the app has to clear this bar. */
  font-size: 16px;
  line-height: 1.3;
  transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
  -webkit-appearance: none; appearance: none;
}
input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--accent); background: var(--bg-lift);
}
input::placeholder, textarea::placeholder { color: var(--fg-faint); }
textarea { min-height: 80px; resize: vertical; line-height: 1.45; }
select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%238C93A6' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 13px center;
  padding-right: 36px;
}
/* Time inputs are typed constantly and always in mm:ss. A monospaced, centred,
   display-face field makes a typo visible before you leave the box. */
input.time-input {
  font-family: var(--font-display); font-size: 26px; font-weight: 800;
  text-align: center; letter-spacing: .02em; min-height: 54px;
}
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); }
.field-row.three { grid-template-columns: 1fr 1fr 1fr; }

/* =========================================================================
   Dashboard
   ========================================================================= */
.hero-race { display: grid; gap: var(--space-2); }
.hero-top { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-3); }
.hero-days { display: flex; align-items: baseline; gap: 8px; }
.hero-days .metric { color: var(--accent); }
.hero-race-name { font-size: var(--text-lg); font-weight: 700; }
.hero-sub { color: var(--fg-muted); font-size: var(--text-md); }

.projbar { display: grid; gap: 7px; margin-top: var(--space-2); }
.projbar-track {
  height: 10px; border-radius: 999px; background: var(--bg-soft);
  overflow: hidden; position: relative; border: 1px solid var(--line);
}
.projbar-fill { height: 100%; border-radius: 999px; background: linear-gradient(90deg, var(--run), var(--accent)); transition: width .6s var(--spring-soft); }
.projbar-target {
  position: absolute; top: -3px; bottom: -3px; width: 2px;
  background: var(--fg); border-radius: 2px; box-shadow: 0 0 0 2px var(--bg-card);
}

.tiles { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-3); }
.tiles.three { grid-template-columns: repeat(3, 1fr); }
.tile {
  background: var(--bg-card); border: 1px solid var(--line);
  border-radius: var(--radius); padding: var(--space-3);
  display: grid; gap: 4px; align-content: start;
  transition: border-color var(--dur-fast) var(--ease), transform .12s var(--ease);
}
.tile:active { transform: scale(.98); }
.tile-label { font-size: var(--text-xs); font-weight: 700; text-transform: uppercase; letter-spacing: .07em; color: var(--fg-muted); }
.tile-value { font-family: var(--font-display); font-size: 27px; font-weight: 900; font-style: italic; line-height: 1; }
.tile-sub { font-size: var(--text-xs); color: var(--fg-muted); }

/* Split bar — where the race time actually goes */
.splitbar { display: flex; height: 14px; border-radius: 999px; overflow: hidden; border: 1px solid var(--line); }
.splitbar span { display: block; height: 100%; }
.splitbar-key { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-2); }
.key-item { display: flex; align-items: center; gap: 6px; font-size: var(--text-xs); color: var(--fg-muted); }
.key-dot { width: 9px; height: 9px; border-radius: 3px; flex: none; }

/* =========================================================================
   Station list
   ========================================================================= */
.station-list { display: grid; gap: var(--space-2); }
/* These rows are anchors so they get real link semantics and a long-press
   menu, which means opting out of the global link colour and underline —
   otherwise every station name renders as yellow-on-black underlined text and
   the row stops reading as a row. */
.station-row, a.log-row { color: inherit; text-decoration: none; }
/* A fixed rhythm. The meta line was a run-on string — "100 reps · 6 kg → 3.0 m
   · level 6:00" — that wrapped on four of the eight rows, so row heights
   ping-ponged between 88px and 130px and the list read as broken rather than
   as a list. The meta is now one truncated line and the benchmark moved to the
   right column where it belongs, next to the number it is a reference for. */
.station-row {
  display: grid;
  grid-template-columns: 36px 1fr auto;
  align-items: center; gap: var(--space-3);
  min-height: 72px;
  padding: var(--space-3);
  background: var(--bg-card); border: 1px solid var(--line);
  border-radius: var(--radius);
  text-align: left; width: 100%;
  transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease), transform .12s var(--ease);
  -webkit-tap-highlight-color: transparent;
}
.station-row:hover { border-color: var(--line-strong); background: var(--bg-soft); }
.station-row:active { transform: scale(.985); }
.station-num {
  width: 30px; height: 30px; border-radius: 9px;
  display: grid; place-items: center;
  background: var(--bg-soft); border: 1px solid var(--line-strong);
  font-family: var(--font-display); font-size: 17px; font-weight: 900; font-style: italic;
  color: var(--fg-muted);
}
.station-row.has-data .station-num { background: color-mix(in srgb, var(--accent) 15%, transparent); border-color: color-mix(in srgb, var(--accent) 34%, transparent); color: var(--accent); }
.station-name { font-weight: 600; font-size: var(--text-base); }
.station-meta {
  font-size: var(--text-xs); color: var(--fg-muted); margin-top: 2px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.station-name { display: block; }
.station-ref { font-size: var(--text-xs); color: var(--fg-faint); margin-top: 2px; }
.station-best { text-align: right; display: grid; gap: 1px; justify-items: end; min-width: 78px; }
.station-best .metric-sm { color: var(--fg); }
.station-best .delta { font-size: var(--text-xs); font-weight: 700; }
.delta.up { color: var(--up); }
.delta.down { color: var(--down); }
.delta.flat { color: var(--fg-muted); }

.badge {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 8px; border-radius: 999px;
  font-size: 10px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase;
}
.badge.pb { background: var(--accent); color: var(--accent-ink); }
.badge.soft { background: var(--bg-soft); color: var(--fg-muted); border: 1px solid var(--line-strong); }
.badge.run { background: color-mix(in srgb, var(--run) 20%, transparent); color: var(--run); }
.badge.rox { background: color-mix(in srgb, var(--rox) 20%, transparent); color: var(--rox); }

/* =========================================================================
   Entry / history lists
   ========================================================================= */
.log-list { display: grid; gap: var(--space-2); }
.log-row {
  display: grid; grid-template-columns: auto 1fr auto; align-items: center;
  gap: var(--space-3); padding: 11px var(--space-3);
  background: var(--bg-card); border: 1px solid var(--line); border-radius: var(--radius);
}
.log-date { font-size: var(--text-xs); color: var(--fg-muted); width: 46px; }
.log-main { min-width: 0; }
.log-title { font-weight: 600; font-size: var(--text-md); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.log-sub { font-size: var(--text-xs); color: var(--fg-muted); }
.log-val { font-family: var(--font-display); font-size: 20px; font-weight: 800; }

.empty {
  text-align: center; padding: var(--space-7) var(--space-4);
  color: var(--fg-muted);
}
.empty-mark {
  width: 52px; height: 52px; margin: 0 auto var(--space-3);
  border-radius: 16px; display: grid; place-items: center;
  background: var(--bg-soft); border: 1px solid var(--line-strong); color: var(--fg-faint);
}
.empty h3 { color: var(--fg-soft); font-size: var(--text-base); margin-bottom: 5px; }

/* =========================================================================
   Charts
   ========================================================================= */
.chart-wrap { position: relative; width: 100%; }
.chart-wrap svg { display: block; width: 100%; height: auto; overflow: visible; }
.chart-axis { font-size: 9.5px; fill: var(--fg-faint); font-family: inherit; }
.chart-grid { stroke: var(--line); stroke-width: 1; }
.chart-line { fill: none; stroke: var(--accent); stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
.chart-area { fill: url(#roxfade); }
.chart-dot { fill: var(--bg-card); stroke: var(--accent); stroke-width: 2.2; }
.chart-dot.best { fill: var(--accent); }
/* Two reference lines, no longer distinguishable by hue now that the palette
   is monochrome — so they differ by dash pattern instead, and both are labelled
   at the right-hand end. */
.chart-target { stroke: var(--fg-soft); stroke-width: 1.6; stroke-dasharray: 7 4; }
.chart-bench { stroke: var(--fg-faint); stroke-width: 1.6; stroke-dasharray: 2 4; }
/* 2px surface gap between stacked fills, per the mark spec — without it the
   white run segment and the yellow station segment share an edge and read as
   one shape at the boundary. */
.splitbar span + span { box-shadow: -2px 0 0 0 var(--bg-card); }

.sparkline { display: block; height: 26px; width: 62px; overflow: visible; }
.sparkline path { fill: none; stroke: var(--accent-dim); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }

/* =========================================================================
   Sheets — a real <dialog> in the top layer
   ------------------------------------------------------------------------- */
.modal-host {
  /* The dialog is the full-viewport surface; the scrim is its ::backdrop and
     the sheet is the child. No wrapper, no z-index, no stacking context to
     lose a fight with. */
  position: fixed; inset: 0;
  width: 100%; max-width: none; height: 100%; max-height: none;
  margin: 0; padding: 0; border: none; background: none;
  display: flex; align-items: flex-end; justify-content: center;
  overflow: visible;
}
.modal-host:not([open]) { display: none; }
.modal-host::backdrop {
  background: rgba(0,0,0,.66);
  -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
}

.modal {
  width: 100%; max-width: 560px;
  max-height: min(calc(100dvh - var(--kb-h, 0px) - 24px), 820px);
  background: var(--bg-card);
  border: 1px solid var(--line-strong);
  border-bottom: none;
  border-radius: var(--radius-xl) var(--radius-xl) 0 0;
  box-shadow: var(--shadow-lg);
  display: flex; flex-direction: column;
  padding-bottom: env(safe-area-inset-bottom);
  will-change: transform;
}
@media (min-width: 640px) {
  .modal-host { align-items: center; }
  .modal { border-radius: var(--radius-xl); border-bottom: 1px solid var(--line-strong); }
}

/* Enter AND exit. `display` and `overlay` are discrete properties — without
   allow-discrete on both, the sheet animates in and then vanishes on close,
   because the browser flips them at frame zero. @starting-style supplies the
   pre-existence state so the entrance has somewhere to come from. */
@supports (transition-behavior: allow-discrete) {
  .modal-host {
    transition: opacity .26s var(--ease), display .34s allow-discrete, overlay .34s allow-discrete;
    opacity: 1;
  }
  .modal-host::backdrop { transition: opacity .26s var(--ease), display .34s allow-discrete, overlay .34s allow-discrete; opacity: 1; }
  .modal-host:not([open]) { opacity: 0; }
  .modal-host:not([open])::backdrop { opacity: 0; }
  @starting-style {
    .modal-host[open] { opacity: 0; }
    .modal-host[open]::backdrop { opacity: 0; }
  }

  .modal { transition: transform .38s var(--spring-soft); transform: translateY(0); }
  .modal-host:not([open]) .modal { transform: translateY(6%); }
  @starting-style { .modal-host[open] .modal { transform: translateY(100%); } }
  @media (min-width: 640px) {
    @starting-style { .modal-host[open] .modal { transform: translateY(24px) scale(.97); } }
  }
}
/* Engines without allow-discrete still get an entrance, just no exit. */
@supports not (transition-behavior: allow-discrete) {
  .modal { animation: sheet-up .3s var(--spring-soft); }
  @keyframes sheet-up { from { transform: translateY(100%) } to { transform: translateY(0) } }
}
@media (prefers-reduced-motion: reduce) {
  .modal, .modal-host, .modal-host::backdrop { transition-duration: .01ms !important; animation: none !important; }
}

.modal-grip { width: 38px; height: 4px; border-radius: 999px; background: var(--line-strong); margin: 9px auto 0; flex: none; cursor: grab; }
.modal-head {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
  padding: var(--space-3) var(--space-4) var(--space-2);
  flex: none; touch-action: none; cursor: grab;
}
.modal-head h2 { font-size: var(--text-lg); touch-action: none; }
.modal-body {
  padding: var(--space-2) var(--space-4) var(--space-4);
  overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain;
}
.modal-foot {
  padding: var(--space-3) var(--space-4) calc(var(--space-4) + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line);
  display: flex; gap: var(--space-2); flex: none;
}
.kb-open .modal-foot { padding-bottom: var(--space-3); }
.modal-foot .btn { flex: 1; }
.modal-close {
  width: 44px; height: 44px; border-radius: 999px;
  display: grid; place-items: center; flex: none;
  background: var(--bg-soft); color: var(--fg-muted);
}
.modal-close:hover { background: var(--bg-lift); color: var(--fg); }

/* =========================================================================
   Toast
   ========================================================================= */
.toast {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom) + 26px);
  z-index: 300;
  max-width: min(90vw, 420px);
  padding: 11px 18px; border-radius: var(--radius-pill);
  background: #FFFFFF; color: #000000;
  font-size: var(--text-md); font-weight: 600;
  box-shadow: var(--shadow-lg);
  animation: toast-in .26s var(--spring-soft);
}
.toast.is-pb { background: var(--accent); }
@keyframes toast-in { from { opacity: 0; transform: translateX(-50%) translateY(10px) } to { opacity: 1; transform: translateX(-50%) translateY(0) } }

/* =========================================================================
   Onboarding
   ========================================================================= */
.onboard { max-width: 480px; margin: 0 auto; padding-top: var(--space-5); }
.onboard-step { animation: step-in .3s var(--spring-soft); }
@keyframes step-in { from { opacity: 0; transform: translateY(10px) } to { opacity: 1; transform: none } }
.onboard h1 { font-size: var(--text-2xl); margin-bottom: 6px; }
.onboard .lede { color: var(--fg-muted); margin: 0 0 var(--space-5); font-size: var(--text-base); }
.onboard-dots { display: flex; gap: 6px; justify-content: center; margin-top: var(--space-5); }
.onboard-dots span { width: 6px; height: 6px; border-radius: 999px; background: var(--line-strong); transition: all .3s var(--ease); }
.onboard-dots span.on { background: var(--accent); width: 20px; }

.pick {
  display: grid; gap: var(--space-2);
}
.pick button {
  display: grid; gap: 3px; text-align: left;
  padding: var(--space-4);
  background: var(--bg-card); border: 1px solid var(--line);
  border-radius: var(--radius); color: var(--fg);
  transition: all var(--dur-fast) var(--ease);
}
.pick button:hover { border-color: var(--line-strong); background: var(--bg-soft); }
.pick button.active {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 9%, var(--bg-card));
  box-shadow: var(--glow);
}
.pick .pick-name { font-weight: 700; font-size: var(--text-base); }
.pick .pick-blurb { font-size: var(--text-md); color: var(--fg-muted); }

/* Prescribed-weight readout on the onboarding + settings screens */
.loads { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-2); }
.load-tile {
  padding: 10px var(--space-3); border-radius: var(--radius-sm);
  background: var(--bg-soft); border: 1px solid var(--line);
}
.load-tile .lt-name { font-size: var(--text-xs); color: var(--fg-muted); }
.load-tile .lt-val { font-family: var(--font-display); font-size: 19px; font-weight: 800; color: var(--strength); }

/* =========================================================================
   Race sim editor
   ========================================================================= */
.simgrid { display: grid; gap: 6px; }
.simrow {
  display: grid; grid-template-columns: 28px 1fr 104px; align-items: center; gap: var(--space-2);
  padding: 6px; border-radius: var(--radius-sm);
  background: var(--bg-card); border: 1px solid var(--line);
}
.simrow.is-run { background: color-mix(in srgb, var(--run) 7%, var(--bg-card)); }
.simrow.is-rox { background: color-mix(in srgb, var(--rox) 8%, var(--bg-card)); }
.simrow .sim-ord {
  display: grid; place-items: center; width: 26px; height: 26px; border-radius: 8px;
  font-size: 11px; font-weight: 800; background: var(--bg-soft); color: var(--fg-muted);
}
.simrow.is-run .sim-ord { background: color-mix(in srgb, var(--run) 22%, transparent); color: var(--run); }
.simrow.is-rox .sim-ord { background: color-mix(in srgb, var(--rox) 22%, transparent); color: var(--rox); }
.simrow .sim-name { font-size: var(--text-md); font-weight: 600; }
.simrow .sim-sub { font-size: 10.5px; color: var(--fg-muted); }
.simrow input { min-height: 40px; padding: 8px; font-family: var(--font-display); font-size: 19px; font-weight: 800; text-align: center; }

.sim-total {
  display: flex; align-items: baseline; justify-content: space-between;
  padding: var(--space-3) var(--space-4); border-radius: var(--radius);
  background: color-mix(in srgb, var(--accent) 10%, var(--bg-soft));
  border: 1px solid color-mix(in srgb, var(--accent) 26%, transparent);
  margin-top: var(--space-3);
}

/* =========================================================================
   Utility
   ========================================================================= */
.scroll-x { overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none; }
.scroll-x::-webkit-scrollbar { display: none; }
.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;
}
.note {
  padding: var(--space-3); border-radius: var(--radius-sm);
  background: var(--bg-soft); border-left: 3px solid var(--line-strong);
  font-size: var(--text-md); color: var(--fg-muted);
}
.note.warn { border-left-color: var(--warn); }
.note.accent { border-left-color: var(--accent); }

/* Toast that can be acted on — the stopwatch saves without asking, so the
   correction path has to be in the confirmation itself. */
.toast.has-action { display: flex; align-items: center; gap: var(--space-3); padding-right: 6px; }
.toast-btn {
  padding: 7px 14px; min-height: 44px;
  border-radius: 999px; background: rgba(0,0,0,.14);
  font-size: var(--text-md); font-weight: 800; color: #000000;
}
.toast-btn:active { background: rgba(0,0,0,.24); }

/* =========================================================================
   Timer & race mode
   -------------------------------------------------------------------------
   Different rules from the rest of the app: one enormous number, one enormous
   button in the bottom third where the thumb already rests, and no dock — a
   mis-tap that navigates away mid-race is the worst thing this screen can do.
   ========================================================================= */
.race {
  display: flex; flex-direction: column;
  min-height: calc(100dvh - var(--topbar-h) - env(safe-area-inset-top) - env(safe-area-inset-bottom) - 8px);
  gap: var(--space-3);
}
.race-head { display: flex; align-items: flex-start; gap: var(--space-2); }
.race-seg { font-size: var(--text-xl); font-weight: 700; letter-spacing: -0.015em; line-height: 1.15; }

.race-clock { text-align: center; padding: var(--space-3) 0 0; }
.race-total {
  font-size: clamp(58px, 21vw, 88px);
  line-height: .9; color: var(--accent);
  font-variant-numeric: tabular-nums;
}
/* State is carried by the clock's colour, and the hierarchy has to match what
   the state MEANS. Yellow = a segment is running. White = you're in a
   transition, which is still live time and still costing you the race. Grey is
   reserved for paused — the only state where the clock isn't moving. Painting
   the transition grey made the largest number on screen the dimmest thing on
   it, while it was actively counting. */
.race.is-rox .race-total { color: #FFFFFF; }
.race.is-paused .race-total { color: var(--fg-faint); }
.race.is-running .race-total { color: var(--accent); }
.race-proj { margin-top: 6px; font-size: var(--text-md); color: var(--fg-soft); }
.race-proj strong { font-weight: 700; }

.race-subs { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-2); }
.race-sub {
  display: grid; gap: 2px; justify-items: center;
  padding: 10px; border-radius: var(--radius);
  background: var(--bg-card); border: 1px solid var(--line);
}
.race-sub-label { font-size: var(--text-xs); font-weight: 700; text-transform: uppercase; letter-spacing: .08em; color: var(--fg-muted); }
.race-sub-val { font-size: 26px; font-style: normal; font-weight: 800; }
.race.is-rox .race-sub:first-child .race-sub-val { color: #FFFFFF; }

/* Sixteen pips. Faster to read at a glance than "segment 11 of 16", and it
   still works in peripheral vision while you're moving. */
.race-progress { display: flex; gap: 4px; }
.race-progress .pip {
  flex: 1; height: 5px; border-radius: 999px;
  background: var(--line-strong); transition: background .2s var(--ease), transform .2s var(--ease);
}
.race-progress .pip.run { background: color-mix(in srgb, var(--run) 30%, var(--line-strong)); }
.race-progress .pip.on { background: var(--accent); }
.race-progress .pip.on.run { background: var(--run); }
.race-progress .pip.cur { transform: scaleY(1.9); background: var(--fg); }

.race-laps { flex: 1; overflow-y: auto; overscroll-behavior: contain; display: grid; gap: 4px; align-content: start; }
.lap {
  display: grid; grid-template-columns: 1fr auto 62px; align-items: center; gap: var(--space-2);
  padding: 8px 11px; border-radius: var(--radius-sm);
  background: var(--bg-card); border: 1px solid var(--line);
}
.lap.is-run { background: color-mix(in srgb, var(--run) 7%, var(--bg-card)); }
.lap-name { font-size: var(--text-md); font-weight: 600; }
.lap-time { font-size: 19px; font-style: normal; font-weight: 800; }
.lap .delta { text-align: right; font-size: var(--text-xs); font-weight: 700; }

/* Actions pinned to the thumb zone. This is the only part of the screen that
   has to be reachable one-handed on a 6.7" phone while breathing hard. */
.race-actions {
  position: sticky; bottom: 0;
  padding: var(--space-3) 0 calc(env(safe-area-inset-bottom) + var(--space-2));
  background: linear-gradient(to top, var(--bg) 62%, transparent);
  display: grid; gap: var(--space-2);
}
.race-minor { display: grid; grid-auto-flow: column; gap: var(--space-2); }
.race-minor .btn { min-height: 44px; }
.race-minor .btn[disabled] { opacity: .35; }
.race.is-rox .race-actions .btn.big { background: #FFFFFF; color: #000000; box-shadow: 0 6px 22px rgba(255,255,255,.20); }
.race-hint { text-align: center; min-height: 15px; }

.race-intro .howto { margin: 0; padding-left: 20px; display: grid; gap: 8px; font-size: var(--text-md); color: var(--fg-soft); }
.race-intro .howto strong { color: var(--fg); }
.race-done { text-align: center; padding-top: var(--space-4); }
.race-done .card { text-align: left; }

/* =========================================================================
   FAB speed dial
   ========================================================================= */
.dial-backdrop {
  position: fixed; inset: 0; z-index: 70;
  background: rgba(0,0,0,.7);
  -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
  animation: fade-in .16s var(--ease);
}
.dial {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: calc(env(safe-area-inset-bottom) + var(--tabbar-h) + 26px);
  z-index: 71; width: min(calc(100% - 32px), 380px);
  display: grid; gap: var(--space-2);
}
.dial-item {
  display: flex; align-items: center; gap: var(--space-3);
  width: 100%; min-height: 60px; padding: 12px var(--space-4);
  border-radius: var(--radius-lg); text-align: left;
  background: var(--bg-card); border: 1px solid var(--line-strong);
  box-shadow: var(--shadow);
  animation: dial-in .26s var(--spring-soft) backwards;
}
.dial-item:nth-child(1) { animation-delay: .02s }
.dial-item:nth-child(2) { animation-delay: .06s }
.dial-item:nth-child(3) { animation-delay: .10s }
@keyframes dial-in { from { opacity: 0; transform: translateY(14px) scale(.96) } to { opacity: 1; transform: none } }
.dial-item:active { transform: scale(.98); background: var(--bg-soft); }
.dial-icon {
  width: 38px; height: 38px; flex: none; border-radius: 11px;
  display: grid; place-items: center;
  background: var(--bg-soft); color: var(--accent);
}
.dial-item.primary .dial-icon { background: var(--accent); color: var(--accent-ink); }
.dial-name { font-weight: 700; font-size: var(--text-base); }
.dial-sub { font-size: var(--text-xs); color: var(--fg-muted); }
.fab.is-open { transform: translateX(-50%) rotate(45deg); }

/* =========================================================================
   Install prompt / offline / update banners
   ========================================================================= */
.banner {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: calc(env(safe-area-inset-bottom) + var(--tabbar-h) + 20px);
  z-index: 65; width: min(calc(100% - 24px), 460px);
  display: flex; align-items: center; gap: var(--space-3);
  padding: 12px var(--space-4);
  border-radius: var(--radius-lg);
  background: var(--bg-card); border: 1px solid var(--line-strong);
  box-shadow: var(--shadow-lg);
  animation: dial-in .3s var(--spring-soft);
}
.banner.update { border-color: color-mix(in srgb, var(--accent) 40%, transparent); }
.banner-text { flex: 1; min-width: 0; }
.banner-title { font-weight: 700; font-size: var(--text-md); }
.banner-sub { font-size: var(--text-xs); color: var(--fg-muted); }
.banner .btn { flex: none; }
.banner-dismiss { width: 34px; height: 34px; flex: none; border-radius: 999px; display: grid; place-items: center; color: var(--fg-muted); }

.offline-pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px; border-radius: 999px;
  background: color-mix(in srgb, var(--warn) 16%, transparent);
  border: 1px solid color-mix(in srgb, var(--warn) 34%, transparent);
  color: var(--warn); font-size: var(--text-xs); font-weight: 700;
}
.ios-install-steps { display: grid; gap: var(--space-3); margin: 0; padding: 0; list-style: none; counter-reset: s; }
.ios-install-steps li {
  display: grid; grid-template-columns: 26px 1fr; gap: var(--space-3); align-items: start;
  font-size: var(--text-md); color: var(--fg-soft);
}
.ios-install-steps li::before {
  counter-increment: s; content: counter(s);
  width: 26px; height: 26px; border-radius: 999px;
  display: grid; place-items: center;
  background: var(--accent); color: var(--accent-ink); font-weight: 800; font-size: var(--text-sm);
}

/* =========================================================================
   Dashboard density
   ========================================================================= */
.quickrow { display: flex; gap: var(--space-2); overflow-x: auto; padding-bottom: 2px; scrollbar-width: none; }
.quickrow::-webkit-scrollbar { display: none; }
.quick {
  flex: none; display: grid; gap: 2px; justify-items: start;
  padding: 10px 14px; min-width: 112px; min-height: 56px;
  border-radius: var(--radius); text-align: left;
  background: var(--bg-card); border: 1px solid var(--line);
}
.quick:active { transform: scale(.97); }
.quick-name { font-size: var(--text-md); font-weight: 600; white-space: nowrap; }
.quick-last { font-size: var(--text-xs); color: var(--fg-muted); }

.resume-card {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-3) var(--space-4); margin-bottom: var(--space-3);
  border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--accent) 12%, var(--bg-card));
  border: 1px solid color-mix(in srgb, var(--accent) 34%, transparent);
}
.resume-dot { width: 9px; height: 9px; border-radius: 999px; background: var(--accent); flex: none; animation: pulse-dot 1.6s ease-in-out infinite; }
@keyframes pulse-dot { 0%,100% { opacity: 1 } 50% { opacity: .25 } }

/* Deltas carry a glyph as well as a colour — red/green alone fails for the
   ~8% of men with a colour vision deficiency, which in a Hyrox app is not a
   rounding error. */
.delta.up::before { content: "▾ "; }
.delta.down::before { content: "▴ "; }
.delta.flat::before { content: ""; }

@media (prefers-reduced-motion: reduce) {
  .dial-item, .banner, .resume-dot, .countdown-pill.is-soon { animation: none; }
}

/* These labels are spans (so the whole row stays one button, not a nest of
   divs inside a button) and spans are inline — which ran the name and the
   description together as "Log a sessionType in what you did". They need to be
   told to stack. */
.dial-name, .dial-sub,
.banner-title, .banner-sub { display: block; }
.dial-sub, .banner-sub { margin-top: 1px; }

/* The solo stopwatch has no lap list to fill the column, so the sticky action
   block sat directly under the clock with a screen of dead space below it —
   and the one button you need mid-piece ended up in the middle of the screen
   instead of under your thumb. Let the clock take the slack and centre in it. */
.race.is-solo .race-clock { flex: 1; display: grid; align-content: center; padding: 0; }
.race.is-solo .race-actions { margin-top: auto; }

/* =========================================================================
   Chart interaction
   -------------------------------------------------------------------------
   The hit circles are invisible and 30px across; the visible marks stay small.
   A touch target should never be limited to the size of the thing it points at.
   ========================================================================= */
.chart-hit { fill: transparent; stroke: none; cursor: pointer; }
.chart-hit.is-on { fill: color-mix(in srgb, var(--accent) 14%, transparent); }
.chart-cross { stroke: var(--line-strong); stroke-width: 1.5; stroke-dasharray: 3 3; pointer-events: none; }
.chart-tip {
  position: absolute; z-index: 3; pointer-events: none;
  display: grid; gap: 1px; justify-items: center;
  padding: 6px 10px; border-radius: 10px;
  background: #FFFFFF; color: #000000;
  box-shadow: var(--shadow);
  white-space: nowrap;
}
.chart-tip-val { font-family: var(--font-display); font-size: 19px; font-weight: 800; line-height: 1; }
.chart-tip-date { font-size: 10.5px; font-weight: 600; opacity: .62; }
.chart-table { border-collapse: collapse; }

/* =========================================================================
   Cold start & confidence
   -------------------------------------------------------------------------
   A projected finish time is only shown once the athlete's own work is behind
   it. Where it IS shown, how much of it is measured travels with it as a chip
   rather than as a footnote.
   ========================================================================= */
.hero-top { align-items: flex-start; }
.hero-proj { text-align: right; flex: none; display: grid; gap: 4px; justify-items: end; min-width: 116px; }
.hero-proj .metric { color: var(--fg); }
.hero-proj .metric.is-provisional { color: var(--fg-soft); opacity: .78; }

.conf {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 8px; border-radius: 999px;
  font-size: 10.5px; font-weight: 800; letter-spacing: .05em; text-transform: uppercase;
  border: 1px solid transparent;
}
.conf-accent { background: color-mix(in srgb, var(--accent) 18%, transparent); color: var(--accent); border-color: color-mix(in srgb, var(--accent) 36%, transparent); }
.conf-ok     { background: rgba(255,255,255,.10); color: var(--fg-soft); border-color: rgba(255,255,255,.2); }
.conf-warn   { background: color-mix(in srgb, var(--warn) 15%, transparent); color: var(--warn); border-color: color-mix(in srgb, var(--warn) 32%, transparent); }
.conf-muted  { background: var(--bg-soft); color: var(--fg-muted); border-color: var(--line-strong); }

.hero-empty { margin-top: var(--space-4); padding-top: var(--space-4); border-top: 1px solid var(--line); }
.hero-empty p { margin: 0 0 var(--space-3); color: var(--fg-soft); }

/* "What should I do next" belongs on the first screen, as one thing, not as a
   conclusion the user has to draw from four cards. */
/* Two stacked lines, not a label and a caption fighting for the same row —
   "Work on Sandbag Lunges" beside "your biggest gap to level" wrapped to three
   lines on a 390px screen and read as two unrelated fragments. */
.hero-next {
  display: flex; align-items: center; gap: var(--space-3);
  margin-top: var(--space-3); padding: 11px var(--space-3);
  min-height: 52px;
  border-radius: var(--radius); text-decoration: none; color: var(--fg);
  background: rgba(255,255,255,.05); border: 1px solid var(--line-strong);
}
.hero-next:active { background: rgba(255,255,255,.09); }
.hero-next svg { flex: none; color: var(--fg-muted); }
.hero-next-label { display: block; font-size: var(--text-md); font-weight: 600; }
.hero-next-label strong {
  color: var(--accent); font-weight: 800;
  text-transform: uppercase; letter-spacing: .06em; font-size: var(--text-xs);
}
.hero-next-why { display: block; font-size: var(--text-xs); color: var(--fg-muted); margin-top: 1px; }

.empty-actions { display: flex; gap: var(--space-2); justify-content: center; flex-wrap: wrap; margin-top: var(--space-3); }

/* Simulation editor: eight rounds, not sixteen anonymous boxes. */
.round { margin-bottom: var(--space-3); }
.round-head {
  display: flex; align-items: center; gap: var(--space-2);
  margin-bottom: 6px;
}
.round-num {
  width: 22px; height: 22px; border-radius: 7px; flex: none;
  display: grid; place-items: center;
  background: var(--bg-soft); border: 1px solid var(--line-strong);
  font-size: 11px; font-weight: 800; color: var(--fg-muted);
}
.round-label { font-size: var(--text-xs); font-weight: 700; text-transform: uppercase; letter-spacing: .08em; color: var(--fg-muted); }
.round.is-done .round-num { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }

.sim-total.is-sticky {
  position: sticky; bottom: calc(env(safe-area-inset-bottom) + var(--tabbar-h) + 8px);
  z-index: 5; backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
}

/* Anchors styled as controls must not keep the user-agent underline. Several
   of these ARE links on purpose — a real href gives them back-button
   behaviour, long-press-to-copy and keyboard semantics a <button> would throw
   away — but a button with an underline through its label reads as broken. */
a.btn, a.linkbtn, a.chip, a.dial-item, a.hero-next, a.resume-card, a.quick,
a.tile, a.station-row, a.log-row, a.lap { text-decoration: none; }
a.btn:hover, a.linkbtn:hover { text-decoration: none; }

/* ═══════════════════════════════════════════════════════════════════════════
   2026 platform layer
   ---------------------------------------------------------------------------
   Everything below is progressively enhanced. Each block is either inside an
   @supports or uses a property that older engines simply drop, so the app
   degrades to exactly what it was rather than to something broken.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Typography: let the browser do the ragging ─────────────────────────────
   text-wrap:balance evens the line lengths of a heading instead of leaving one
   orphaned word; pretty stops single-word last lines in body copy. Both are
   free — no JS, no measuring, and no layout shift. */
h1, h2, h3, .h-screen, .hero-race-name, .empty h3, .banner-title, .dial-name {
  text-wrap: balance;
}
p, .small, .tiny, .hero-sub, .screen-sub, .station-meta, .dial-sub {
  text-wrap: pretty;
}

/* ── Animating to auto ──────────────────────────────────────────────────────
   interpolate-size unlocks height transitions to keyword values, so the
   disclosures on the dashboard can actually open rather than snapping. */
@supports (interpolate-size: allow-keywords) {
  :root { interpolate-size: allow-keywords; }
  .disclose::details-content {
    block-size: 0;
    overflow: clip;
    transition: block-size .34s var(--spring-soft), content-visibility .34s allow-discrete;
  }
  .disclose[open]::details-content { block-size: auto; }
}

/* ── Discrete transitions ───────────────────────────────────────────────────
   `display` and `overlay` are discrete properties: without allow-discrete they
   flip instantly and the exit animation never plays, which is why almost every
   hand-built toast in the world fades IN and then vanishes. @starting-style
   supplies the "before it existed" state so the entrance animates too. */
@supports (transition-behavior: allow-discrete) {
  .toast {
    transition: opacity .22s var(--ease), transform .3s var(--spring-soft),
                display .3s allow-discrete;
  }
  .toast[hidden] { display: none; opacity: 0; transform: translateX(-50%) translateY(10px); }
  @starting-style {
    .toast:not([hidden]) { opacity: 0; transform: translateX(-50%) translateY(10px); }
  }
  .banner {
    transition: opacity .24s var(--ease), transform .34s var(--spring-soft),
                display .34s allow-discrete;
  }
  @starting-style {
    .banner { opacity: 0; transform: translateX(-50%) translateY(16px) scale(.97); }
  }
}

/* ── Scroll-driven entrances ────────────────────────────────────────────────
   Cards rise as they enter the viewport, on the compositor, with no scroll
   listener and no IntersectionObserver. `view()` ties the animation to the
   element's own position in the scrollport, so it is frame-accurate during a
   fling in a way a JS observer never is.

   Deliberately subtle: 10px and a little opacity. A training log is read at
   speed and a big staggered reveal would be an obstacle, not a delight.

   The failure mode to watch for with `fill: both` is content that never enters
   the range holding its `from` state — i.e. invisible cards. Verified against
   both edge cases rather than assumed: on a scrollable page every in-view card
   computes to opacity 1 at every scroll position, and on a page too short to
   scroll at all (a fresh install on a tall screen) the timeline is inactive
   and Chrome resolves every card to fully visible. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    @keyframes rise {
      from { opacity: 0; transform: translateY(10px); }
      to   { opacity: 1; transform: none; }
    }
    .view > .card,
    .view > .station-list > *,
    .view > .tiles > * {
      animation: rise linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 34%;
    }
  }
}

/* ── Container queries ──────────────────────────────────────────────────────
   The tiles and the prescribed-weight grid respond to the width of the CARD
   they sit in, not the viewport — so they stay correct if a card is ever put
   in a sidebar, a split view, or a wider tablet column. */
.card, .tiles, .loads { container-type: inline-size; }
@container (min-width: 420px) {
  .loads { grid-template-columns: repeat(3, 1fr); }
  .tiles:not(.three) { grid-template-columns: repeat(4, 1fr); }
}

/* ── Inputs ─────────────────────────────────────────────────────────────────
   field-sizing lets the notes field grow with its content instead of sitting
   at a fixed height with a scrollbar inside it. */
@supports (field-sizing: content) {
  textarea { field-sizing: content; min-height: 3lh; max-height: 12lh; }
}

/* ── Scrollbar gutter ───────────────────────────────────────────────────────
   Reserve the space so content doesn't jump sideways when a list grows past
   the fold on desktop. */
@supports (scrollbar-gutter: stable) {
  .modal-body, .race-laps { scrollbar-gutter: stable; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   View transitions
   ---------------------------------------------------------------------------
   The persistent chrome is named so it is captured as its OWN layer. Without
   this the header and dock live inside ::view-transition-old(root) and get
   cross-faded along with the content — the whole app appears to blink on every
   tab press, which is worse than no transition at all. Named, they are matched
   old-to-new, sit still, and only the content moves.
   ═══════════════════════════════════════════════════════════════════════════ */
@supports (view-transition-name: none) {
  #topbar  { view-transition-name: chrome-top; }
  #tabbar  { view-transition-name: chrome-dock; }
  #fab     { view-transition-name: chrome-fab; }
  #toast   { view-transition-name: chrome-toast; }

  ::view-transition-group(chrome-top),
  ::view-transition-group(chrome-dock),
  ::view-transition-group(chrome-fab) { animation-duration: .01ms; }

  /* Content: a short cross-fade plus a small directional slide. 220ms and 16px
     — long enough to read as continuous, short enough that a fast tapper never
     waits on it. */
  ::view-transition-old(root), ::view-transition-new(root) {
    animation-duration: .22s;
    animation-timing-function: var(--ease-out);
    mix-blend-mode: normal;   /* the default plus-lighter blows out on black */
  }
  ::view-transition-old(root) { animation-name: vt-out; }
  ::view-transition-new(root) { animation-name: vt-in; }

  @keyframes vt-out { to { opacity: 0; transform: translateX(var(--vt-out, 0)); } }
  @keyframes vt-in { from { opacity: 0; transform: translateX(var(--vt-in, 0)); } }

  html[data-nav="forward"] { --vt-out: -16px; --vt-in: 22px; }
  html[data-nav="back"]    { --vt-out: 22px;  --vt-in: -16px; }
  html[data-nav="lateral"] { --vt-out: 0; --vt-in: 0; }

  /* The shared element. Its group is what actually travels — the old and new
     snapshots cross-fade inside a box that morphs from the row's geometry to
     the heading's, on a spring so it lands rather than stops. */
  ::view-transition-group(morph-hero) {
    animation-duration: .38s;
    animation-timing-function: var(--spring-soft);
  }
  ::view-transition-old(morph-hero),
  ::view-transition-new(morph-hero) {
    animation-duration: .28s;
    height: 100%;
    object-fit: contain;
  }

  @media (prefers-reduced-motion: reduce) {
    ::view-transition-group(*),
    ::view-transition-old(*),
    ::view-transition-new(*) { animation: none !important; }
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   THE RACE SPINE — the app's signature graphic
   ---------------------------------------------------------------------------
   One idea, two orientations. A Hyrox race is a fixed course, and until this
   existed every screen drew it as a vertical stack of equal rows — the shape
   of a settings page, and nothing you would recognise a second time.
   ═══════════════════════════════════════════════════════════════════════════ */

.spine { padding: var(--space-2) 0 var(--space-1); }
.spine-track {
  display: flex; align-items: center; gap: 0;
  width: 100%;
}
/* The run between stations. Thin, recessive, and the majority of the track by
   width — because running IS the majority of the race, and a course diagram
   that hides that is lying about the event. */
.sp-run {
  flex: 1 1 auto; height: 2px; min-width: 5px;
  background: var(--line-strong);
  transition: background .3s var(--ease);
}
.sp-run.on { background: color-mix(in oklab, var(--run) 55%, transparent); }

.sp-stn {
  position: relative;
  flex: 0 0 auto;
  width: 26px; height: 26px;
  display: grid; place-items: center;
  border-radius: 8px;
  background: var(--bg-soft);
  border: 1.5px solid var(--line-strong);
  color: var(--fg-faint);
  font-family: var(--font-display);
  font-size: 14px; font-weight: 800; font-style: italic;
  text-decoration: none;
  transition: transform .24s var(--spring-tight), background .24s var(--ease),
              border-color .24s var(--ease), color .24s var(--ease);
}
/* The node is drawn at 26px but TAPPED at 44. Growing the visual mark to 44
   would make the spine a row of chunky buttons and destroy the thing it is
   for — a course you read at a glance — so the target is extended past the
   paint instead. At a 390px viewport the node centres land ~44.7px apart, so
   the enlarged targets sit shoulder to shoulder without overlapping. */
.sp-stn::before { content: ""; position: absolute; inset: -10px; border-radius: 13px; }
.sp-stn:active { transform: scale(.88); }
.sp-stn.on {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
  box-shadow: 0 0 18px -4px color-mix(in oklab, var(--accent) 55%, transparent);
}
.spine.is-compact .sp-stn { width: 22px; height: 22px; font-size: 12px; border-radius: 7px; }

/* ── Vertical course (the Stations screen) ────────────────────────────────── */
.course { list-style: none; margin: 0; padding: 0; }

.course-leg {
  display: flex; align-items: center;
  /* 17px centres the 2px line under the 36px node column; the 29px gap then
     lands the run label on the same left edge as the station names, so the
     course reads as one column instead of two ragged ones. */
  gap: 29px;
  padding-left: 17px;
  height: 34px;
}
.course-leg-line {
  width: 2px; height: 100%; flex: none;
  background: repeating-linear-gradient(to bottom,
    var(--line-strong) 0 5px, transparent 5px 10px);
}
.course-leg-label {
  font-size: var(--text-xs); font-weight: 600; letter-spacing: .04em;
  color: var(--fg-faint);
}
.course-finish { display: flex; align-items: center; gap: 29px; padding-left: 17px; height: 34px; }
.course-finish-label {
  color: var(--accent); font-weight: 800;
  text-transform: uppercase; letter-spacing: .12em;
}

.course-item { margin: 0; }
/* The node sits ON the line, so the row reads as a stop on a course rather
   than as the next item in a list. */
.course-item .station-row {
  background: none; border: none; box-shadow: none;
  padding: 6px 0; min-height: 60px;
  grid-template-columns: 36px 1fr auto;
}
.course-item .station-row:hover { background: none; }
.course-item .station-num {
  width: 36px; height: 36px; border-radius: 11px;
  font-size: 17px;
  background: var(--bg-soft); border: 1.5px solid var(--line-strong);
  color: var(--fg-muted);
  box-shadow: 0 0 0 5px var(--bg);    /* punches a hole in the dashed line */
  transition: transform .2s var(--spring-tight);
}
.course-item .station-row:active .station-num { transform: scale(.92); }
.course-item .station-row.has-data .station-num {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
}

/* ═══════════════════════════════════════════════════════════════════════════
   SURFACE HIERARCHY
   ---------------------------------------------------------------------------
   Every card used to carry a background AND a 1px border AND a shadow — three
   separators doing one separator's job — at identical weight, radius and
   padding. The result was a queue of interchangeable boxes with no composition:
   nothing looked more important than anything else, so the eye had nowhere to
   land.

   Three tiers now, and most content is tier 2:

     .card.hero    the one thing this screen is about. Bleeds, glows, biggest type.
     .card         a SECTION: a header and its content, separated by space.
                   No box at all. Space is the separator.
     .card.panel   a genuine container — a chart, a form group, a bordered list.
                   Only where content must be visibly enclosed.
   ═══════════════════════════════════════════════════════════════════════════ */

.card {
  background: none;
  border: none;
  box-shadow: none;
  border-radius: 0;
  padding: 0;
  margin-bottom: var(--space-7);
}

/* Section headers earn the display face and a short accent rule. An 11px grey
   uppercase label is what you use when you have nothing to say; this is a
   masthead for the section under it. */
.card > h2, .card-title, .card > .row-between > h2 {
  display: flex; align-items: center; gap: 9px;
  margin: 0 0 var(--space-3);
  font-family: var(--font-display);
  font-size: 15px; font-weight: 800;
  text-transform: uppercase; letter-spacing: .1em;
  color: var(--fg-soft);
}
.card > h2::before, .card > .row-between > h2::before {
  content: ""; width: 14px; height: 3px; border-radius: 2px;
  background: var(--accent); flex: none;
}

/* Tier 3 — a real container, used sparingly. */
.card.panel {
  background: var(--bg-card);
  border-radius: var(--radius-xl);
  padding: var(--pad);
  margin-bottom: var(--space-5);
}
.card.panel > h2 { margin-bottom: var(--space-4); }

/* Tier 1 — the hero. No border, a wash that fades rather than a boxed card,
   and the only place the accent glow is allowed. */
.card.card-hero {
  position: relative;
  background: none;
  border: none;
  padding: 0 0 var(--space-5);
  margin-bottom: var(--space-6);
}
.card.card-hero::before {
  content: ""; position: absolute; z-index: -1;
  inset: -28px -24px auto -24px; height: 300px;
  background:
    radial-gradient(90% 100% at 22% 0%, color-mix(in oklab, var(--accent) 14%, transparent), transparent 68%),
    radial-gradient(70% 90% at 96% 4%, rgba(255,255,255,.05), transparent 62%);
  pointer-events: none;
  mask-image: linear-gradient(to bottom, #000 40%, transparent 100%);
}

/* Scale contrast. The hero countdown is the largest thing in the app by a wide
   margin — it is the only number that is true every single day, and the one
   the whole product is organised around. */
.hero-days .metric-xl { font-size: clamp(72px, 23vw, 104px); letter-spacing: -0.03em; }
.hero-race-name { font-size: var(--text-xl); font-weight: 700; letter-spacing: -0.02em; }
.hero-proj .metric-lg { font-size: 34px; letter-spacing: -0.02em; }

/* Lists that used to sit inside a card now need their own containment. */
.log-list > .log-row, .station-list > .station-row {
  background: var(--bg-card);
  border: none;
  border-radius: var(--radius);
}
.log-list > a.log-row:active, .station-list > a.station-row:active { background: var(--bg-soft); }
.station-list > .station-row:hover { background: var(--bg-soft); border-color: transparent; }

.tile { background: var(--bg-card); border: none; border-radius: var(--radius); padding: var(--space-4); }
.load-tile { background: var(--bg-card); border: none; }
.simrow { background: var(--bg-card); border: none; }
.lap { border: none; }
.hero-next { background: var(--bg-card); border-color: transparent; }
.resume-card { border-radius: var(--radius-lg); }
.quick { background: var(--bg-card); border: none; }
.note { background: var(--bg-card); }
.race-sub { background: var(--bg-card); border: none; }

/* The dock and header keep their glass; nothing else competes with them. */

/* ── Progressive disclosure ─────────────────────────────────────────────────
   The explanations behind a projection matter — they are what stops the number
   being a black box — but they should not cost a third of a screen every time
   the app opens. A quiet, small, uppercase control that animates open (see the
   interpolate-size block above for the height transition). */
.disclose { margin-top: var(--space-3); }
.disclose > summary {
  list-style: none;
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 40px; padding: 6px 0;
  cursor: pointer;
  font-size: var(--text-xs); font-weight: 700;
  text-transform: uppercase; letter-spacing: .09em;
  color: var(--fg-muted);
  -webkit-user-select: none; user-select: none;
}
.disclose > summary::-webkit-details-marker { display: none; }
.disclose > summary::marker { content: ""; }
.disclose > summary:hover { color: var(--fg-soft); }
.disclose > summary::after {
  content: ""; width: 6px; height: 6px; margin-left: 1px;
  border-right: 1.7px solid currentColor; border-bottom: 1.7px solid currentColor;
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform .24s var(--spring-soft);
}
.disclose[open] > summary::after { transform: rotate(-135deg) translate(-1px, -1px); }
.disclose p { margin: 0 0 8px; font-size: var(--text-xs); color: var(--fg-muted); }
