/* ============================================================================
   LOCKEDFIT — KIT
   The design system as real CSS. Every screen is assembled from these; nothing
   gets styled ad hoc. The whole point is that there is no unstyled primitive
   available to reach for at 1am.

   Colour has exactly three jobs and they never overlap:
     monochrome ~90%  structure. The primary button is filled white.
     gold       ~7%   A PERSON TOUCHED THIS — keyholder verification, a
                      prescribed session, rank, a sealed week. Never a button,
                      never navigation, never decoration.
     heat       ~3%   LIVE, RIGHT NOW — a running clock, the set you are on.
                      One heat element per screen, never on something inert.
   Verified green is a STATE (a row background), not an accent, so "done" never
   competes with "tappable".
   ============================================================================ */

/* 🔴 --nickel-2 IS THE COLOUR OF EVERY LABEL IN THE PRODUCT, applied at
   8.5–10.5px, weight 800, uppercase, .17em tracking — the hardest text here to
   read. It was pushed one step past the AA floor and there is no tier below it
   to catch the fall. Measured, WCAG 2.1, before → after:

     dark  on --anvil    3.86 → 5.10
     dark  on --forge    4.11 → 5.44
     dark  on --steel    3.54 → 4.69
     dark  on --steel-2  3.23 → 4.27   ← STILL SHORT OF 4.5. See below.
     light on --anvil    3.74 → 5.50
     light on --forge    3.24 → 4.77
     light on --steel    3.42 → 5.03
     light on --steel-2  3.13 → 4.60

   Every one of those figures is computed, not estimated. Seven of the eight
   surfaces were failing AA before this, not the two the audit happened to spot.

   The one remaining shortfall is deliberate and is the honest trade. Clearing
   4.5 on --steel-2 as well needs --nickel-2 lightened to about #848e9b, which
   sits so close to --nickel (#96a1ae) that the two label tiers stop being
   distinguishable and the hierarchy the system is built on collapses. The
   alternative — darkening --steel-2 to ~#1a1e24 — recolours input backgrounds
   and trend pills across the app to win 0.23 of a contrast point.

   --steel-2 carries values, not labels: the set-row inputs and the trend pill.
   Body-copy contrast is met everywhere a LABEL actually appears. Recorded here
   rather than left as a silent "contrast fixed" claim that is not quite true. */
:root{
  --forge:#090a0c; --anvil:#111318; --steel:#191c22; --steel-2:#20242b; --steel-3:#2a2f38;
  --edge:rgba(206,216,228,.11); --edge-2:rgba(206,216,228,.06); --edge-3:rgba(206,216,228,.20);
  --chrome:#eef2f6; --nickel:#96a1ae; --nickel-2:#7d8794;
  --gold:#d4af37; --gold-hi:#f2d675; --gold-wash:rgba(212,175,55,.13); --gold-line:rgba(212,175,55,.34);
  --heat:#ff4a1c; --heat-wash:rgba(255,74,28,.13); --heat-line:rgba(255,74,28,.36);
  --go:#43c98a; --go-wash:rgba(67,201,138,.13); --bad:#ff5a5a; --bad-wash:rgba(255,90,90,.12);
  --r-sm:8px; --r:12px; --r-lg:16px; --r-full:999px;
  --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:24px; --s6:32px;
  --lift:0 10px 34px rgba(0,0,0,.55);
  --ease:cubic-bezier(.2,.8,.3,1);
  --nav-h:56px;
  --font:-apple-system,"SF Pro Text","Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
  --font-d:-apple-system,"SF Pro Display","Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
@media (prefers-color-scheme:light){
  :root{
    --forge:#edeff2; --anvil:#fff; --steel:#f3f5f8; --steel-2:#e8ebf0; --steel-3:#dde1e8;
    --edge:rgba(16,20,28,.14); --edge-2:rgba(16,20,28,.07); --edge-3:rgba(16,20,28,.24);
    --chrome:#0e1218; --nickel:#5a6472; --nickel-2:#5f6a78;
    --gold:#8a6d1e; --gold-hi:#6d5514; --gold-wash:rgba(138,109,30,.11); --gold-line:rgba(138,109,30,.30);
    --heat:#d63b12; --heat-wash:rgba(214,59,18,.10); --heat-line:rgba(214,59,18,.30);
    --go:#1f8d5c; --go-wash:rgba(31,141,92,.11); --bad:#c0392b; --bad-wash:rgba(192,57,43,.10);
    --lift:0 8px 26px rgba(16,20,28,.12);
  }
}

*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{height:100%}
body{
  margin:0;background:var(--forge);color:var(--nickel);
  font:16px/1.55 var(--font);-webkit-font-smoothing:antialiased;
  overscroll-behavior-y:none;
}

/* ── TYPE ────────────────────────────────────────────────────────────────── */
.t-d{font-family:var(--font-d);font-weight:800;letter-spacing:-.03em;color:var(--chrome);text-wrap:balance}
.t-h1{font:800 clamp(30px,7vw,44px)/1.02 var(--font-d);letter-spacing:-.033em;color:var(--chrome);margin:0;text-wrap:balance}
.t-h2{font:800 21px/1.15 var(--font-d);letter-spacing:-.025em;color:var(--chrome);margin:0}
.t-h3{font:750 16px/1.3 var(--font);letter-spacing:-.015em;color:var(--chrome);margin:0}
.t-num{font-variant-numeric:tabular-nums;font-feature-settings:'tnum';font-weight:800;letter-spacing:-.025em;color:var(--chrome)}
.t-lbl{font-size:9.5px;font-weight:800;letter-spacing:.17em;text-transform:uppercase;color:var(--nickel-2)}
.t-code{font-family:ui-monospace,"SF Mono",Menlo,Consolas,monospace;letter-spacing:.2em;color:var(--gold);font-weight:700}
.t-mut{color:var(--nickel-2)}
.t-sm{font-size:13.5px}

/* ── LAYOUT ──────────────────────────────────────────────────────────────── */
.screen{display:none;flex-direction:column;min-height:100%}
.screen.is-active{display:flex}
.pad{padding:var(--s4)}
.stack{display:flex;flex-direction:column;gap:var(--s3)}
.row{display:flex;align-items:center;gap:var(--s3)}
.grow{flex:1;min-width:0}
.center{align-items:center;justify-content:center;text-align:center}
.app-body{flex:1;overflow-y:auto;-webkit-overflow-scrolling:touch;
  padding:var(--s4) var(--s4) calc(var(--nav-h) + env(safe-area-inset-bottom) + var(--s5))}
.topbar{display:flex;align-items:center;justify-content:space-between;gap:var(--s3);
  padding:calc(env(safe-area-inset-top) + var(--s3)) var(--s4) var(--s3);
  position:sticky;top:0;background:var(--forge);z-index:5}

/* ── BUTTON ──────────────────────────────────────────────────────────────── */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--s2);
  height:48px;padding:0 20px;border:0;border-radius:var(--r);cursor:pointer;
  font:750 15.5px/1 var(--font);letter-spacing:-.01em;color:var(--chrome);
  background:var(--steel-2);box-shadow:inset 0 0 0 1px var(--edge);
  transition:transform .12s var(--ease),filter .15s var(--ease),background .15s var(--ease)}
.btn:active{transform:scale(.975)}
.btn:focus-visible{outline:2px solid var(--gold);outline-offset:3px}
.btn[disabled],.btn.is-busy{opacity:.4;pointer-events:none}
.btn--primary{background:var(--chrome);color:var(--forge);box-shadow:none}
.btn--primary:hover{filter:brightness(.93)}
.btn--live{background:var(--heat);color:#fff;box-shadow:none}
.btn--ghost{background:transparent;color:var(--nickel)}
.btn--ghost:hover{color:var(--chrome);box-shadow:inset 0 0 0 1px var(--edge-3)}
.btn--danger{background:transparent;color:var(--bad);box-shadow:inset 0 0 0 1px var(--bad)}
.btn--block{width:100%}
.btn--sm{height:36px;padding:0 14px;font-size:13.5px;border-radius:var(--r-sm)}
.btn--link{background:none;box-shadow:none;height:auto;padding:4px;color:var(--nickel);
  font-weight:600;font-size:14px;text-decoration:underline;text-underline-offset:3px}
.btn--link:hover{color:var(--chrome)}
.spin{width:15px;height:15px;border:2px solid currentColor;border-right-color:transparent;
  border-radius:50%;animation:lf-spin .7s linear infinite}
@keyframes lf-spin{to{transform:rotate(360deg)}}

/* ── FIELD ───────────────────────────────────────────────────────────────── */
.field{display:flex;flex-direction:column;gap:6px}
.field > label{font-size:9.5px;font-weight:800;letter-spacing:.17em;text-transform:uppercase;color:var(--nickel-2)}
.input{height:50px;padding:0 15px;border:0;border-radius:var(--r);background:var(--steel);
  box-shadow:inset 0 0 0 1px var(--edge);color:var(--chrome);font:500 16px/1 var(--font);
  transition:box-shadow .18s var(--ease)}
.input::placeholder{color:var(--nickel-2)}
.input:focus{outline:0;box-shadow:inset 0 0 0 1.5px var(--gold-line)}
.input.is-bad{box-shadow:inset 0 0 0 1.5px var(--bad)}

/* ── SURFACES ────────────────────────────────────────────────────────────── */
.card{background:var(--anvil);border-radius:var(--r-lg);padding:var(--s4);
  box-shadow:inset 0 0 0 1px var(--edge)}
.card--earned{box-shadow:inset 0 0 0 1px var(--gold-line);
  background:linear-gradient(158deg,var(--gold-wash),transparent 62%),var(--anvil)}
.card--live{box-shadow:inset 0 0 0 1px var(--heat-line);
  background:linear-gradient(158deg,var(--heat-wash),transparent 62%),var(--anvil)}
.list{border-radius:var(--r-lg);overflow:hidden;box-shadow:inset 0 0 0 1px var(--edge)}
.lrow{display:flex;align-items:center;gap:var(--s3);padding:12px var(--s4);
  background:var(--anvil);cursor:pointer;transition:background .15s var(--ease);width:100%;
  border:0;text-align:left;font:inherit;color:inherit}
.lrow:hover{background:var(--steel)}
.lrow + .lrow{box-shadow:inset 0 1px 0 var(--edge-2)}
.lrow__thumb{width:40px;height:40px;border-radius:var(--r-sm);background:var(--steel-2);
  flex:0 0 auto;display:flex;align-items:center;justify-content:center;color:var(--nickel)}
.lrow__t{font-size:15px;font-weight:650;color:var(--chrome);letter-spacing:-.01em}
.lrow__s{font-size:12.5px;color:var(--nickel-2)}

/* ── BITS ────────────────────────────────────────────────────────────────── */
.chip{display:inline-flex;align-items:center;gap:5px;height:22px;padding:0 8px;border-radius:5px;
  font-size:9.5px;font-weight:800;letter-spacing:.13em;text-transform:uppercase;white-space:nowrap;
  background:var(--steel-2);color:var(--nickel);box-shadow:inset 0 0 0 1px var(--edge)}
.chip--earned{background:var(--gold-wash);color:var(--gold);box-shadow:inset 0 0 0 1px var(--gold-line)}
.chip--live{background:var(--heat-wash);color:var(--heat);box-shadow:inset 0 0 0 1px var(--heat-line)}
.chip--done{background:var(--go-wash);color:var(--go);box-shadow:inset 0 0 0 1px rgba(67,201,138,.32)}
.stat__v{font:800 26px/1.05 var(--font);letter-spacing:-.03em;color:var(--chrome);font-variant-numeric:tabular-nums}
.stat__v.is-live{color:var(--heat)} .stat__v.is-earned{color:var(--gold)}
.stat__v .u{font-size:13px;font-weight:600;color:var(--nickel-2);letter-spacing:0;margin-left:2px}
.stat__k{font-size:10px;font-weight:800;letter-spacing:.15em;text-transform:uppercase;color:var(--nickel-2);margin-top:4px}
.empty{text-align:center;padding:40px 22px}
.empty__t{font:750 17px/1.3 var(--font);color:var(--chrome);margin-bottom:5px}
.empty__s{font-size:13.5px;color:var(--nickel-2);max-width:34ch;margin:0 auto 18px}

/* ── TAB BAR ─────────────────────────────────────────────────────────────── */
.tabbar{position:fixed;left:0;right:0;bottom:0;display:flex;background:var(--anvil);
  box-shadow:inset 0 1px 0 var(--edge);padding-bottom:env(safe-area-inset-bottom);z-index:10}
.tab{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;
  height:var(--nav-h);color:var(--nickel-2);font-size:9.5px;font-weight:800;letter-spacing:.07em;
  text-transform:uppercase;cursor:pointer;background:none;border:0;
  transition:color .15s var(--ease)}
/* 🔴 NOT gold. The kit's own rule 141 lines above says gold is "never a
   button, never navigation, never decoration" — an active tab is navigation,
   and spending gold on it teaches that gold means "where you are", which
   destroys its ability to mean A PERSON TOUCHED THIS. */
.tab.is-on{color:var(--chrome)}
.tab svg{width:20px;height:20px}

/* ── SHEET ───────────────────────────────────────────────────────────────
   Replaces native confirm()/prompt(). A browser dialog renders in the OS chrome,
   ignores the design system entirely, and on iOS says "lockedfit.com says" above
   your copy — which reads as a scam warning, not a product. */
.scrim{position:fixed;inset:0;background:rgba(0,0,0,.62);z-index:50;opacity:0;
  pointer-events:none;transition:opacity .2s var(--ease)}
.scrim.is-on{opacity:1;pointer-events:auto}
.sheet{position:fixed;left:0;right:0;bottom:0;z-index:51;background:var(--anvil);
  border-radius:var(--r-lg) var(--r-lg) 0 0;padding:10px var(--s4)
  calc(env(safe-area-inset-bottom) + var(--s4));box-shadow:0 -1px 0 var(--edge),var(--lift);
  transform:translateY(101%);transition:transform .26s var(--ease)}
.sheet.is-on{transform:translateY(0)}
.sheet__grab{width:34px;height:4px;border-radius:2px;background:var(--steel-3);margin:0 auto 16px}
.sheet__t{font:800 19px/1.2 var(--font-d);letter-spacing:-.025em;color:var(--chrome);margin:0 0 6px}
.sheet__s{font-size:13.5px;color:var(--nickel-2);margin:0 0 var(--s4)}
.sheet__acts{display:flex;flex-direction:column;gap:var(--s2)}

/* ── TOAST ───────────────────────────────────────────────────────────────── */
.toast{position:fixed;left:50%;bottom:calc(var(--nav-h) + env(safe-area-inset-bottom) + 16px);
  transform:translate(-50%,12px);opacity:0;pointer-events:none;z-index:60;
  display:flex;align-items:center;gap:10px;padding:12px 16px;border-radius:var(--r);
  background:var(--steel-2);box-shadow:inset 0 0 0 1px var(--edge),var(--lift);
  font-size:14px;color:var(--chrome);font-weight:600;max-width:calc(100vw - 32px);
  transition:opacity .2s var(--ease),transform .2s var(--ease)}
.toast.is-on{opacity:1;transform:translate(-50%,0)}
.toast--bad{color:var(--bad)}

/* ── HERO ────────────────────────────────────────────────────────────────
   Screens need a hero, not three floating numbers. The mockups had one and the
   first build did not, which is most of why the live app read as a quick pass. */
.hero{display:flex;align-items:center;gap:var(--s4);padding:var(--s2) 0 var(--s5)}
.hero__ring{flex:0 0 auto}
.hero__copy{flex:1;min-width:0}
.hero__t{font:800 clamp(24px,6.2vw,32px)/1.02 var(--font-d);letter-spacing:-.035em;
  color:var(--chrome);margin:6px 0 8px;text-wrap:balance}
.hero__s{font-size:14px;color:var(--nickel-2);margin:0}
.lockring{display:block}
.lockring__n{font:800 27px/1 var(--font-d);letter-spacing:-.03em;fill:var(--chrome);
  font-variant-numeric:tabular-nums}
.lockring__d{font-size:9px;font-weight:800;letter-spacing:.15em;fill:var(--nickel-2)}

/* Week strip — seven cells, so a glance answers "how is this week going". */
.week{display:grid;grid-template-columns:repeat(7,1fr);gap:5px}
.week__d{aspect-ratio:1;border-radius:8px;background:var(--steel);
  box-shadow:inset 0 0 0 1px var(--edge-2);display:flex;align-items:center;
  justify-content:center;font-size:9.5px;font-weight:800;letter-spacing:.05em;
  color:var(--nickel-2);text-transform:uppercase}
.week__d.is-done{background:var(--go-wash);color:var(--go);box-shadow:inset 0 0 0 1px rgba(67,201,138,.3)}
.week__d.is-today{box-shadow:inset 0 0 0 1.5px var(--edge-3);color:var(--chrome)}

/* Stat strip with hairline dividers rather than floating numbers in space. */
.strip{display:grid;grid-auto-flow:column;grid-auto-columns:1fr;
  background:var(--edge-2);gap:1px;border-radius:var(--r);overflow:hidden;
  box-shadow:inset 0 0 0 1px var(--edge)}
.strip > div{background:var(--anvil);padding:13px 14px}

.section-h{display:flex;align-items:baseline;justify-content:space-between;
  gap:12px;margin:var(--s5) 0 var(--s3)}
/* 🔴 NOT gold, for the same reason, and this one is a DESCENDANT selector so it
   also golded the count badge beside every heading. Nine headings across the
   app, none of which a person touched. */
.section-h .t-lbl{color:var(--nickel-2)}

/* Segmented control — a setting with two answers should show both, not hide one
   behind a toggle whose current state you have to infer. */
.seg{display:grid;grid-auto-flow:column;grid-auto-columns:1fr;gap:3px;padding:3px;
  background:var(--steel);border-radius:var(--r);box-shadow:inset 0 0 0 1px var(--edge)}
.seg__b{height:38px;border:0;border-radius:9px;background:transparent;cursor:pointer;
  font:700 14px/1 var(--font);color:var(--nickel);letter-spacing:-.01em;
  transition:background .15s var(--ease),color .15s var(--ease)}
.seg__b.is-on{background:var(--chrome);color:var(--forge)}
.seg__b:active{transform:scale(.98)}

.range{-webkit-appearance:none;appearance:none;width:100%;height:26px;background:transparent}
.range::-webkit-slider-runnable-track{height:4px;border-radius:2px;background:var(--steel-3)}
.range::-moz-range-track{height:4px;border-radius:2px;background:var(--steel-3)}
.range::-webkit-slider-thumb{-webkit-appearance:none;width:22px;height:22px;border-radius:50%;
  background:var(--chrome);margin-top:-9px;box-shadow:0 1px 4px rgba(0,0,0,.4)}
.range::-moz-range-thumb{width:22px;height:22px;border:0;border-radius:50%;background:var(--chrome)}

/* ── THE SET ROW ─────────────────────────────────────────────────────────
   The most-touched component in the product: forty taps a session, four times a
   week, one sweaty hand. Full-bleed rows, 52px tall, a 32px tick target. */
.exblock{margin-bottom:var(--s5)}
.exhead{display:flex;align-items:center;gap:10px;padding:0 var(--s4) 8px}
.exhead__thumb{width:34px;height:34px;border-radius:50%;background:var(--steel-2);flex:0 0 auto;
  display:flex;align-items:center;justify-content:center}
.exhead__n{font-size:15.5px;font-weight:700;color:var(--chrome);letter-spacing:-.01em}
.exhead__s{font-size:12px;color:var(--nickel-2)}
.exhead__x{background:none;border:0;color:var(--nickel-2);font-size:20px;padding:4px 8px;cursor:pointer}

.set-row__echo{margin-left:6px;color:var(--nickel);font-variant-numeric:tabular-nums}
.set-head,.set-row{display:grid;grid-template-columns:32px 1fr 62px 52px 42px;gap:0 8px;
  align-items:center;padding:0 var(--s4)}

/* 🔴 THIS RULE HAD NEVER APPLIED, AND IT SAT ABOVE THE BASE RULE FOR MONTHS.
   Both selectors are one class — specificity (0,1,0) each — so source order
   decides, and the base `.set-head,.set-row` below it won every time. Measured
   in a browser rather than argued: a `.set-row.set-row--single` computed
   `32px 48px 62px 52px 42px`, byte-identical to a plain `.set-row`.

   So every hold, rep-count and distance row has been rendering on the FIVE
   column grid with only FOUR children: the single input lands in the 62px cell
   meant for a weight, the tick's `margin-left:auto` parks it at the right edge
   of the 52px cell, and a dead 42px gutter sits beside it. Head and row shear
   identically, which is exactly why nobody could see it was wrong — the labels
   still line up with the boxes, they are just all in the wrong places, and
   Previous gets LESS width instead of the extra width this comment promises.

   A hold, a rep count and a distance are ONE number, so they get one column and
   the freed width goes to Previous — the cell that actually drives the session. */
.set-head--single,.set-row--single{grid-template-columns:32px 1fr 76px 42px}

/* A timed set that can be filmed carries a camera between the input and the
   tick. Its own modifier, NOT a widened `--single`: `single` is true for every
   one-field metric (METRICS gives `time`, `reps` and the single-column cases one
   field), so widening it would put a camera on every rep row and on a run. */
.set-head--proof,.set-row--proof{grid-template-columns:32px 1fr 76px 38px 42px}
.set-head{height:26px}
.set-head span{font-size:8.5px;font-weight:800;letter-spacing:.13em;text-transform:uppercase;color:var(--nickel-2)}
.set-head .c{text-align:center}
.set-row{height:52px;font-variant-numeric:tabular-nums;position:relative;
  transition:background .18s var(--ease)}
.set-row + .set-row{box-shadow:inset 0 1px 0 var(--edge-2)}
.set-row__n{font:800 15px/1 var(--font);color:var(--chrome);background:none;border:0;padding:0;
  text-align:left;cursor:pointer}
.set-row__prev{font-size:12.5px;color:var(--nickel-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.set-row__in{height:36px;width:100%;border:0;border-radius:var(--r-sm);background:var(--steel-2);
  box-shadow:inset 0 0 0 1px var(--edge);text-align:center;
  font:700 15px/1 var(--font);color:var(--chrome);font-variant-numeric:tabular-nums;
  transition:box-shadow .18s var(--ease),background .18s var(--ease)}
.set-row__in::placeholder{color:var(--nickel-2);font-weight:600}
.set-row__in:focus{outline:0;box-shadow:inset 0 0 0 1.5px var(--gold-line)}
.set-row__tick{width:32px;height:32px;border-radius:var(--r-sm);margin-left:auto;cursor:pointer;
  background:transparent;box-shadow:inset 0 0 0 1.5px var(--edge-3);color:transparent;border:0;
  display:flex;align-items:center;justify-content:center;
  transition:background .18s var(--ease),box-shadow .18s var(--ease),transform .12s var(--ease)}
.set-row__tick:active{transform:scale(.9)}
.set-row.is-done{background:var(--go-wash)}
.set-row.is-done .set-row__prev{color:var(--nickel)}
.set-row.is-done .set-row__in{background:transparent;box-shadow:none}
.set-row.is-done .set-row__tick{background:var(--go);box-shadow:none;color:#06170f}
.set-row.is-warmup .set-row__n{color:var(--nickel-2);font-size:12.5px}
.set-row.is-pr::after{content:"PR";position:absolute;left:var(--s4);bottom:4px;
  font-size:8px;font-weight:800;letter-spacing:.13em;color:var(--gold)}

.resttimer{position:fixed;left:0;right:0;bottom:calc(env(safe-area-inset-bottom));
  display:flex;align-items:center;gap:12px;padding:12px var(--s4);background:var(--anvil);
  box-shadow:inset 0 1px 0 var(--edge),0 -8px 24px rgba(0,0,0,.35);z-index:12}
.resttimer__track{flex:1;height:4px;border-radius:2px;background:var(--steel-3);overflow:hidden}
.resttimer__fill{height:100%;background:var(--heat);border-radius:2px;transition:width 1s linear}

/* ── THE FIGURE ──────────────────────────────────────────────────────────
   Exercise pictograms from js/figure.js. Everything inherits currentColor, so a
   list row, a sheet and a share card each set the tone once and the drawing
   follows. Equipment is deliberately quieter than the body: the pose carries the
   information, the barbell is only context. */
.fig{display:block;color:var(--chrome)}
.fig .fig-far{opacity:.34}          /* far-side limbs — depth without a second colour */
.fig .fig-head{fill:currentColor;stroke:none}
.fig .fig-kit{opacity:.5}
.fig .fig-plate{fill:currentColor;stroke:none}
/* The body map is 1:2.1, so it gets a slot shaped like it rather than a square
   it has to overflow. */
.lrow__thumb--map{width:26px;height:46px;border-radius:7px;background:transparent;
  box-shadow:none}
.bodymap{display:block;margin:0 auto}
.lrow__thumb .fig{color:var(--nickel)}
.lrow:hover .fig{color:var(--chrome)}

.hide{display:none!important}
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}

/* ── LEAGUE ──────────────────────────────────────────────────────────────
   The competition layer. One board, no cohorts — see js/league.js for why.
   Gold appears exactly twice on this screen (the top three, and your own row);
   heat exactly once (the live round closing). Nothing else earns either. */
.lbl{font:800 10.5px/1 var(--font);letter-spacing:.15em;text-transform:uppercase;
  color:var(--nickel-2)}
.hint{font-size:12.5px;line-height:1.5;color:var(--nickel-2);text-align:center;
  margin:10px auto 0;max-width:36ch}
.dot{width:3px;height:3px;border-radius:50%;background:var(--nickel-2);display:inline-block;
  vertical-align:middle;margin:0 2px}
.backlink{background:none;border:0;color:var(--nickel);font:600 13px/1 var(--font);
  padding:6px 2px;margin-bottom:var(--s3);cursor:pointer}
.backlink:hover{color:var(--chrome)}
.spin--lg{width:26px;height:26px;border-width:3px}

.note{background:var(--steel);box-shadow:inset 0 0 0 1px var(--edge-2)}
.note__t{font:750 14.5px/1.3 var(--font);color:var(--chrome);margin-bottom:3px}
.note__s{font-size:12.5px;line-height:1.55;color:var(--nickel-2)}

/* Event card in the lobby */
.ev{display:block;width:100%;text-align:left;border:0;cursor:pointer;margin-bottom:var(--s3)}
.ev__top{display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--s2)}
.ev__kind{font:800 10.5px/1 var(--font);letter-spacing:.14em;text-transform:uppercase;
  color:var(--nickel-2)}
.ev__name{font:800 22px/1.15 var(--font-d);letter-spacing:-.02em;color:var(--chrome)}
.ev__blurb{font-size:13px;line-height:1.5;color:var(--nickel);margin-top:5px}
.ev__foot{display:flex;align-items:center;gap:7px;margin-top:var(--s3);
  font-size:12px;color:var(--nickel-2)}

/* The round hero. The target is the biggest number on the screen because it is
   the only one you have to beat. */
.hero-ev{text-align:center;padding:var(--s5) var(--s4);
  background:linear-gradient(170deg,var(--steel-2),var(--anvil) 62%)}
.hero-ev__r{font:800 10.5px/1 var(--font);letter-spacing:.16em;text-transform:uppercase;
  color:var(--nickel-2)}
.hero-ev__t{font:800 68px/1 var(--font-d);letter-spacing:-.045em;color:var(--chrome);
  font-variant-numeric:tabular-nums;margin:10px 0 6px}
.hero-ev__s{font-size:13px;color:var(--nickel)}
.hero-ev__foot{display:flex;justify-content:space-between;margin-top:var(--s4);
  padding-top:var(--s3);box-shadow:inset 0 1px 0 var(--edge-2);
  font-size:12px;color:var(--nickel-2)}
.hero-ev__foot .live{color:var(--heat);font-weight:700}
.hero-ev__foot .ok{color:var(--go);font-weight:700}
.hero-ev.is-done{box-shadow:inset 0 0 0 1px rgba(67,201,138,.28)}

/* Attrition. The field thinning IS the story of an elimination event, so it gets
   the chart and the leaderboard does not. */
.attr{background:var(--anvil);border-radius:var(--r-lg);padding:var(--s4);
  margin:var(--s4) 0;box-shadow:inset 0 0 0 1px var(--edge-2)}
.attr__head{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:var(--s3)}
.attr__n{font-size:12px;color:var(--nickel);font-variant-numeric:tabular-nums}
.attr__bars{display:flex;align-items:flex-end;gap:3px;height:76px}
.attr__col{flex:1;display:flex;flex-direction:column;justify-content:flex-end;
  align-items:center;gap:5px;height:100%;min-width:0}
.attr__col i{display:block;width:100%;background:var(--steel-3);border-radius:2px 2px 0 0;
  transition:height .4s var(--ease)}
.attr__col:last-child i{background:var(--heat)}
.attr__col span{font:700 9px/1 var(--font);color:var(--nickel-2);font-variant-numeric:tabular-nums}

/* Board */
.lb{background:var(--anvil);border-radius:var(--r-lg);overflow:hidden;
  box-shadow:inset 0 0 0 1px var(--edge-2)}
.lb__head,.lb__row{display:grid;grid-template-columns:34px 1fr 42px 62px;align-items:center;
  gap:var(--s2);padding:11px var(--s4)}
.lb__head{font:800 9.5px/1 var(--font);letter-spacing:.14em;text-transform:uppercase;
  color:var(--nickel-2);box-shadow:inset 0 -1px 0 var(--edge-2)}
.lb__row{box-shadow:inset 0 -1px 0 var(--edge-2);font-size:14px;color:var(--chrome)}
.lb__row:last-child{box-shadow:none}
.lb__n{font-variant-numeric:tabular-nums;color:var(--nickel-2);font-weight:700;font-size:13px}
.lb__row:nth-child(2) .lb__n,.lb__row:nth-child(3) .lb__n,.lb__row:nth-child(4) .lb__n{color:var(--gold)}
.lb__who{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600}
.lb__r,.lb__t{font-variant-numeric:tabular-nums;text-align:right;font-weight:700}
.lb__t{color:var(--nickel)}
.lb__row.is-me{background:var(--gold-wash);
  box-shadow:inset 0 -1px 0 var(--edge-2),inset 2px 0 0 var(--gold)}
.lb__you{font-style:normal;font-size:9px;font-weight:800;letter-spacing:.1em;
  text-transform:uppercase;color:var(--gold);margin-left:6px}
.lb__row.is-out{opacity:.42}
.lb__sep{padding:9px var(--s4);font:800 9.5px/1 var(--font);letter-spacing:.14em;
  text-transform:uppercase;color:var(--nickel-2);background:var(--steel);
  box-shadow:inset 0 -1px 0 var(--edge-2)}
.lb__none{padding:26px var(--s4);text-align:center;font-size:13px;color:var(--nickel-2)}

/* ── CAPTURE ─────────────────────────────────────────────────────────────
   Full-screen, above everything, no tab bar: a hold in progress is a modal
   commitment, the same argument as the logger. The clock counts UP — a
   countdown to the target quietly tells you when it is safe to stop trying. */
.capture{position:fixed;inset:0;z-index:80;background:#000;overflow:hidden;
  display:flex;flex-direction:column}
/* 🔴 THE VIEWFINDER USED TO LIE, AND IT LIED IN THE TIGHTER DIRECTION.
   This was `inset:0; width:100%; height:100%; object-fit:cover` — the video box
   was the whole viewport. But the FILE is always 9:16: drawCover() composes into
   a 270x480 canvas. A modern phone viewport is taller than 9:16 (iPhone 15 is
   393x852 = 0.461; a 20:9 Android is 0.450), so covering the viewport crops ~18%
   more width than the recording does. The athlete framed against a tighter box
   than the one being saved, which made proof.js's own instruction — "Prop the
   phone so the whole movement is in frame" — impossible to satisfy: the frame
   they were shown was not the frame that was recorded.

   A 9:16 box, full width, centred, is WYSIWYG. It costs a black band top and
   bottom on a tall phone, which is what every camera app does and what makes the
   preview trustworthy. */
.capture__cam{position:absolute;left:0;right:0;top:50%;transform:translateY(-50%);
  width:100%;height:auto;aspect-ratio:9/16;max-height:100%;object-fit:cover}
.capture__scrim{position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(180deg,rgba(0,0,0,.62) 0%,rgba(0,0,0,0) 28%,
    rgba(0,0,0,0) 52%,rgba(0,0,0,.78) 100%)}
.capture__top{position:relative;display:flex;align-items:center;justify-content:space-between;
  padding:calc(env(safe-area-inset-top) + 12px) var(--s4) 0}
.capture__x{width:38px;height:38px;border:0;border-radius:50%;background:rgba(0,0,0,.42);
  color:#fff;font-size:24px;line-height:1;cursor:pointer;backdrop-filter:blur(8px)}
.capture__code{font:700 13px/1 ui-monospace,Menlo,monospace;letter-spacing:.22em;
  color:var(--gold);background:rgba(0,0,0,.42);padding:9px 12px;border-radius:var(--r-sm);
  backdrop-filter:blur(8px)}
.capture__mid{position:relative;flex:1;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:6px;padding:0 var(--s5)}
.capture__clock{font:800 84px/1 var(--font-d);letter-spacing:-.05em;color:#fff;
  font-variant-numeric:tabular-nums;text-shadow:0 2px 30px rgba(0,0,0,.6)}
.capture__target{font-size:13px;color:rgba(255,255,255,.72)}
.capture__target b{color:#fff;font-variant-numeric:tabular-nums}
.capture__bar{width:min(320px,72vw);height:3px;border-radius:2px;
  background:rgba(255,255,255,.22);overflow:hidden;margin-top:12px}
.capture__bar i{display:block;height:100%;background:var(--heat);transition:width .12s linear}
.capture.is-past .capture__bar i{background:var(--go)}
.capture.is-past .capture__clock{color:var(--go)}
.capture__foot{position:relative;display:flex;flex-direction:column;align-items:center;
  gap:14px;padding:0 var(--s4) calc(env(safe-area-inset-bottom) + 26px)}
.capture__meta{font-size:12.5px;color:rgba(255,255,255,.66);
  font-variant-numeric:tabular-nums;min-height:16px}
/* 🔴 The label is a LITERAL near-black, never var(--forge). Light mode redefines
   --forge to #edeff2, which on this #fff button measured 1.15:1 — the word
   vanished while .capture's forced black backdrop kept the screen looking fine.
   This is the only control that starts and stops a proof recording. */
.capture__go{width:78px;height:78px;border-radius:50%;border:4px solid rgba(255,255,255,.9);
  background:#fff;color:#090a0c;font:800 14px/1 var(--font);cursor:pointer;
  transition:transform .14s var(--ease),background .2s}
.capture__go:active{transform:scale(.93)}
.capture__go.is-live{background:var(--heat);border-color:rgba(255,255,255,.55);color:#fff}
.capture__go[disabled]{opacity:.4;pointer-events:none}
.capture__work{position:absolute;inset:0;z-index:2;background:rgba(6,7,9,.94);
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;
  padding:var(--s5);text-align:center;color:#fff;backdrop-filter:blur(10px)}
.capture__workt{font:750 16px/1.3 var(--font);font-variant-numeric:tabular-nums}
.capture__works{font-size:12.5px;color:rgba(255,255,255,.6);max-width:30ch}

/* ── TODAY'S LIVE EVENT CARD ─────────────────────────────────────────────
   The only heat on Today, and only while a round is actually open. Once the
   hold is logged the card goes green — "live" on something already finished is
   the misuse the kit exists to prevent. */
.evcard{display:block;width:100%;text-align:left;border:0;cursor:pointer;
  margin-top:var(--s4);position:relative;overflow:hidden}
.evcard.is-live{box-shadow:inset 0 0 0 1px var(--heat-line)}
.evcard.is-done{box-shadow:inset 0 0 0 1px rgba(67,201,138,.30)}
.evcard__top{display:flex;align-items:center;justify-content:space-between;
  gap:var(--s3);margin-bottom:var(--s2)}
.evcard__k{font:800 10.5px/1 var(--font);letter-spacing:.14em;text-transform:uppercase;
  color:var(--nickel-2)}
.evcard__t{font:800 19px/1.2 var(--font-d);letter-spacing:-.015em;color:var(--chrome)}
.evcard__s{font-size:12.5px;line-height:1.5;color:var(--nickel);margin-top:4px}
.evcard__hold{display:flex;align-items:baseline;gap:8px;margin-top:10px}
.evcard__hold b{font:800 40px/1 var(--font-d);letter-spacing:-.04em;color:var(--heat);
  font-variant-numeric:tabular-nums}
.evcard__hold span{font-size:12.5px;color:var(--nickel-2)}

/* ── CHARTS ──────────────────────────────────────────────────────────────
   Progress over time. Steel, never gold — a chart is data, and gold in this
   kit means a person touched it. The only gold a chart ever carries is a
   single point that was verified or competed.

   These render from js/chart.js as inline SVG strings, so they theme for free
   and cost no database read: every series is computed from the workout history
   get_lockedfit_home already shipped. */
.chart{display:block;width:100%;height:auto}
.chart__last{font:800 13px/1 var(--font);fill:var(--chrome);
  font-variant-numeric:tabular-nums;letter-spacing:-.02em}
.spark{display:block;width:64px;height:20px;flex:0 0 auto;opacity:.9}

/* The figure above a chart: one hero, the measure's name, and the change. */
.chartcard{background:var(--anvil);border-radius:var(--r-lg);padding:var(--s4) var(--s4) var(--s3);
  box-shadow:inset 0 0 0 1px var(--edge)}
.chartcard__top{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--s3)}
.chartcard__v{font:800 40px/1 var(--font-d);letter-spacing:-.04em;color:var(--chrome);
  font-variant-numeric:tabular-nums}
.chartcard__k{font-size:10px;font-weight:800;letter-spacing:.15em;text-transform:uppercase;
  color:var(--nickel-2);margin-top:6px}
.chartcard__span{display:flex;justify-content:space-between;margin-top:2px;
  font-size:10.5px;font-weight:700;letter-spacing:.06em;color:var(--nickel-2);
  font-variant-numeric:tabular-nums}
/* One session is not a trend. Say so in words rather than drawing a flat line. */
.chartcard__one{font-size:12.5px;color:var(--nickel-2);padding:var(--s3) 0 var(--s2)}

/* Direction as SHAPE, never colour — green here is a state (verified), and
   borrowing it for "improving" would make done and improving the same signal. */
.trendpill{display:inline-flex;align-items:center;gap:5px;padding:5px 9px;border-radius:var(--r-full);
  background:var(--steel-2);font:800 12px/1 var(--font);color:var(--nickel);
  font-variant-numeric:tabular-nums;white-space:nowrap}
.trendpill__g{font-size:9px;line-height:1}
.trendpill--up{color:var(--chrome)}
.trendpill--down{color:var(--nickel-2)}
.trendpill--flat .trendpill__g{opacity:.6}

/* The exercise picker on the progress screen. */
.exrow{display:flex;align-items:center;gap:var(--s3);padding:12px var(--s4);width:100%;
  background:none;border:0;cursor:pointer;text-align:left;color:inherit}
.exrow + .exrow{box-shadow:inset 0 1px 0 var(--edge-2)}
.exrow:active{background:var(--steel-2)}
.exrow__n{display:block;font:700 14.5px/1.25 var(--font);color:var(--chrome)}
.exrow__s{display:block;font-size:11.5px;color:var(--nickel-2);margin-top:2px;
  font-variant-numeric:tabular-nums}
.exrow__thumb{width:30px;height:30px;flex:0 0 auto;opacity:.75}

/* ── ROUTINE BUILDER ─────────────────────────────────────────────────────
   The prescription grid: sets, rep range, rest. Deliberately plain number
   fields rather than steppers — a routine is written once and read many times,
   so typing 5 beats tapping + four times. */
.rbgrid{display:flex;gap:var(--s2);padding:10px var(--s4) 14px;align-items:flex-end;flex-wrap:wrap}
.rbgrid label{display:flex;flex-direction:column;gap:5px;flex:1 1 60px;min-width:56px;
  font-size:9.5px;font-weight:800;letter-spacing:.14em;text-transform:uppercase;color:var(--nickel-2)}
.rbgrid .set-row__in{width:100%}

/* ── THE RECORD BUTTON ───────────────────────────────────────────────────
   The centre slot of the nav. White fill, because in this kit white fill IS
   the primary action and this is the most primary action in the product.
   Deliberately NOT gold and NOT heat: gold means a person touched this and
   heat means live right now — a button that has not been pressed is neither.
   It goes heat only while a capture is actually running. */
.tab--rec{flex:0 0 76px;position:relative}
.tab__rec{display:flex;align-items:center;justify-content:center;
  width:50px;height:50px;border-radius:50%;
  background:var(--chrome);color:var(--forge);
  box-shadow:0 4px 14px rgba(0,0,0,.4);
  transition:transform .14s var(--ease),background .2s var(--ease)}
.tab__rec svg{width:25px;height:25px}
.tab--rec:active .tab__rec{transform:scale(.92)}
.tab--rec.is-live .tab__rec{background:var(--heat);color:#fff}
@media (prefers-color-scheme:light){
  .tab__rec{box-shadow:0 4px 14px rgba(16,20,28,.18)}
}

/* ── THE TWO-MARK SCREEN ─────────────────────────────────────────────────
   Reference 390x844. Everything below the frame is thumb-reachable, because
   this is the screen an athlete uses out of breath, one-handed, immediately
   after the hardest thing they did that week. */
.marks{position:fixed; inset:0; z-index:60; background:var(--forge);
  display:flex; flex-direction:column; gap:var(--s2);
  padding:calc(env(safe-area-inset-top) + var(--s2)) var(--s4)
          calc(env(safe-area-inset-bottom) + var(--s3))}
.marks__top{display:flex; align-items:center; justify-content:space-between; gap:var(--s3)}

/* THE FRAME ITSELF — with the clock and the nonce baked into the pixels. This
   is not a preview of the evidence, it IS the evidence. */
.marks__frame{position:relative; flex:1 1 auto; min-height:0; border-radius:var(--r);
  overflow:hidden; background:#000; display:flex; align-items:center; justify-content:center}
.marks__img{max-width:100%; max-height:100%; display:block; object-fit:contain}
.marks__bake{position:absolute; inset:0; pointer-events:none}
.marks__clk{position:absolute; left:10px; top:9px; font:800 22px/1 var(--font);
  font-variant-numeric:tabular-nums; color:#fff; text-shadow:0 1px 8px rgba(0,0,0,.75)}
.marks__code{position:absolute; right:10px; top:12px; font-size:11px;
  text-shadow:0 1px 8px rgba(0,0,0,.75)}

/* The magnifier sits ABOVE the touch point: the main image is under your hand
   during a drag, so showing the detail there would be showing it to your palm. */
.marks__mag{position:absolute; bottom:calc(100% + 8px); transform:translateX(-50%);
  width:76px; border-radius:var(--r-sm); overflow:hidden; background:#000;
  box-shadow:0 8px 22px rgba(0,0,0,.55), inset 0 0 0 1px var(--edge-3); z-index:4}
.marks__mag img{display:block; width:100%}
.marks__mag span{position:absolute; left:4px; bottom:3px; font:800 10px/1 var(--font);
  font-variant-numeric:tabular-nums; color:#fff; text-shadow:0 1px 5px #000}

/* THE FILMSTRIP. Full-bleed, 40 thumbs, shaded span = what counts. */
.marks__strip{position:relative; height:46px; border-radius:var(--r-sm); overflow:visible;
  background:var(--steel); box-shadow:inset 0 0 0 1px var(--edge); touch-action:none;
  cursor:ew-resize; flex:0 0 auto}
.marks__strip canvas{display:block; width:100%; height:46px; border-radius:var(--r-sm);
  image-rendering:auto; opacity:.55}
.marks__strip.is-bad{box-shadow:inset 0 0 0 1.5px var(--heat)}
.marks__shade{position:absolute; top:0; bottom:0; background:rgba(150,161,174,.24);
  box-shadow:inset 0 0 0 1px var(--edge-3); pointer-events:none; border-radius:2px}
.marks__pin{position:absolute; top:-4px; bottom:-4px; width:2.5px; margin-left:-1.25px;
  background:var(--chrome); border-radius:2px; pointer-events:none;
  box-shadow:0 0 0 1px rgba(0,0,0,.5)}
.marks__pin::after{content:''; position:absolute; left:50%; top:-5px; width:9px; height:9px;
  margin-left:-4.5px; border-radius:2px; background:var(--chrome); transform:rotate(45deg)}
.marks__pin--f{background:var(--nickel)}
.marks__pin--f::after{background:var(--nickel)}
.marks__grid{text-align:center; margin-top:-2px}

.marks__step{display:grid; grid-auto-flow:column; grid-auto-columns:1fr; gap:var(--s2)}
.marks__step .btn{font-variant-numeric:tabular-nums}
.marks__seg .seg__b{flex-direction:column; gap:2px; height:46px}
.marks__seg .seg__b b{font:800 14px/1 var(--font); font-variant-numeric:tabular-nums}
.marks__seg .seg__b.is-locked{opacity:.45}

.marks__out{text-align:center}
.marks__v{font:800 44px/1 var(--font-d); letter-spacing:-.04em; color:var(--chrome);
  font-variant-numeric:tabular-nums}
.marks__warn{margin:var(--s2) 0 0; font-size:12.5px; line-height:1.5; color:var(--nickel-2);
  min-height:2.6em}
.marks__warn.is-bad{color:var(--heat)}
.marks__warn.is-note{color:var(--nickel)}
.marks__go .btn.is-ready{}

.marks__sheet{position:absolute; inset:0; z-index:5; background:rgba(0,0,0,.62);
  display:flex; align-items:flex-end; backdrop-filter:blur(3px)}
.marks__sheetc{width:100%; background:var(--anvil); border-radius:var(--r-lg) var(--r-lg) 0 0;
  padding:var(--s5) var(--s4) calc(env(safe-area-inset-bottom) + var(--s4));
  display:flex; flex-direction:column; gap:var(--s3); box-shadow:0 -14px 40px rgba(0,0,0,.5)}
.marks__step .btn{gap:5px}
.marks__step .btn span{font-variant-numeric:tabular-nums}
/* Watch-it-back, floated over the frame's lower edge. Steel, not heat: the
   clip is a recording being reviewed, not something happening right now. */
.marks__play{position:absolute; left:50%; bottom:10px; transform:translateX(-50%);
  display:inline-flex; align-items:center; gap:7px; height:32px; padding:0 14px;
  border:0; border-radius:var(--r-full); cursor:pointer;
  background:rgba(12,14,17,.72); color:#fff; backdrop-filter:blur(8px);
  font:700 11.5px/1 var(--font); letter-spacing:.01em;
  font-variant-numeric:tabular-nums; box-shadow:0 2px 10px rgba(0,0,0,.4)}
.marks__play:active{transform:translateX(-50%) scale(.95)}
.marks__play.is-live{background:rgba(12,14,17,.86)}
/* The per-exercise note. Last session's note is the PLACEHOLDER — grey, and it
   clears the moment you type, because that is what a placeholder does. */
.exnote{display:block; width:calc(100% - var(--s4) * 2); margin:0 var(--s4) 8px;
  height:32px; padding:0 10px; border:0; border-radius:var(--r-sm);
  background:var(--steel); box-shadow:inset 0 0 0 1px var(--edge-2);
  color:var(--chrome); font:500 12.5px/1 var(--font)}
.exnote::placeholder{color:var(--nickel-2); font-weight:500}
.exnote:focus{outline:0; box-shadow:inset 0 0 0 1.5px var(--edge-3)}
.lrow__note{display:block;font-size:12px;line-height:1.45;color:var(--nickel);
  margin-top:3px;font-style:italic}
.exrow__go{flex:0 0 auto;margin-left:6px;font-size:19px;line-height:1;color:var(--nickel-2);
  padding:0 2px}

/* ── THE OUTDOOR SESSION ─────────────────────────────────────────────────
   No map. The screen's argument is that it measures honestly and keeps no
   route, so the hero is the clock and the evidence is the gap ledger. */
.run{position:fixed;inset:0;z-index:60;background:var(--forge);display:flex;flex-direction:column;
  padding:calc(env(safe-area-inset-top) + var(--s2)) var(--s4)
          calc(env(safe-area-inset-bottom) + var(--s3))}
.run__top{display:flex;align-items:center;justify-content:space-between;gap:var(--s3);
  flex:0 0 auto;margin-bottom:var(--s4)}
.run__brief{flex:1;display:flex;flex-direction:column;gap:var(--s4);justify-content:center}
.run__brief .t-sm{margin:0;max-width:34ch}
.run__briefgo{display:flex;flex-direction:column;gap:var(--s2);margin-top:var(--s2)}
.run__live{flex:1;display:flex;flex-direction:column;gap:var(--s3);min-height:0}
.run__hero{text-align:center;padding:var(--s3) 0 var(--s2)}
.run__clock{font:800 clamp(52px,16vw,68px)/1 var(--font-d);letter-spacing:-.045em;
  color:var(--chrome);font-variant-numeric:tabular-nums}
.run__splits{flex:1;overflow-y:auto;-webkit-overflow-scrolling:touch;min-height:0}
.run__acts{flex:0 0 auto;display:flex;gap:var(--s2)}
.run__acts .btn{flex:1}

/* ── THE GAMES TUNNEL ────────────────────────────────────────────────────
   games.html has no tab bar — it is one errand, not a place. .app-body
   reserves var(--nav-h) at the bottom for a bar that is not there, which
   parks the primary action ~90px short of the bottom of the screen. Scoped to
   the tunnel rather than changed in .app-body, because every screen in
   app.html DOES sit above the bar and needs that room. */
body.tunnel .app-body{padding-bottom:calc(env(safe-area-inset-bottom) + var(--s5))}

/* ONE BIG FIGURE AND WHAT IT IS. The result IS the screen here, so it gets
   the size the run clock gets rather than .hero__t, which is a headline in a
   flex ROW next to a ring -- used as a figure it renders a 4:12 at 24px and
   45px wide, which is how this was first built. .run__hero/.run__clock are
   the same shape under a name that only fits one screen; fold them into this
   next time run.js is open. */
.bigfig{text-align:center;padding:var(--s3) 0 var(--s2)}
.bigfig__v{font:800 clamp(52px,16vw,68px)/1 var(--font-d);letter-spacing:-.045em;
  color:var(--chrome);font-variant-numeric:tabular-nums}
.bigfig__k{font:800 10px/1.3 var(--font);letter-spacing:.16em;text-transform:uppercase;
  color:var(--nickel-2);margin-top:var(--s2)}

/* ── CAPTURE: SELF-TIMER, FLIP, PRE-ROLL COUNTDOWN ───────────────────────
   Literal #fff and near-black throughout, never var(--chrome)/var(--forge).
   .capture forces a black backdrop regardless of theme, so a token that light
   mode redefines measures ~1.15:1 here — the same trap documented on
   .capture__go, which is why that one is a literal too. */
.capture__tools{display:flex;align-items:center;gap:8px}
.capture__tool{display:inline-flex;align-items:center;gap:5px;height:38px;padding:0 12px;
  border:0;border-radius:19px;background:rgba(0,0,0,.42);color:rgba(255,255,255,.72);
  font:800 12px/1 var(--font);font-variant-numeric:tabular-nums;cursor:pointer;
  backdrop-filter:blur(8px);transition:color .15s,background .15s}
.capture__tool.is-on{color:#fff;background:rgba(255,255,255,.18)}
.capture__tool:active{transform:scale(.94)}
.capture__tool span:empty{display:none}

/* The pre-roll owns the middle of the screen: the clock, the pacing line and
   the bar all describe a hold that has not started, and leaving them up while a
   number counts down is two clocks arguing. */
.capture.is-counting .capture__clock,
.capture.is-counting .capture__target,
.capture.is-counting .capture__bar{visibility:hidden}
.capture__count{position:absolute;inset:0;z-index:1;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:10px;pointer-events:none}
.capture__countn{font:800 clamp(120px,42vw,190px)/1 var(--font-d);letter-spacing:-.06em;
  color:#fff;font-variant-numeric:tabular-nums;text-shadow:0 4px 40px rgba(0,0,0,.7);
  animation:lf-count .85s var(--ease)}
.capture__counts{font:700 13px/1 var(--font);letter-spacing:.08em;text-transform:uppercase;
  color:rgba(255,255,255,.8);text-shadow:0 2px 14px rgba(0,0,0,.7)}
@keyframes lf-count{from{transform:scale(1.5);opacity:.5}to{transform:scale(1);opacity:1}}
@media (prefers-reduced-motion:reduce){.capture__countn{animation:none}}

/* The camera on a timed set row. Nickel, not gold and not heat: filming is a
   thing you can do, not a thing a person has touched and not a thing that is
   live. It sits between the input and the tick so the tick stays the rightmost
   control on every row shape — muscle memory for "done" must not move. */
.set-row__film{width:34px;height:34px;border-radius:var(--r-sm);border:0;padding:0;cursor:pointer;
  background:transparent;color:var(--nickel-2);display:flex;align-items:center;justify-content:center;
  transition:color .18s var(--ease),background .18s var(--ease)}
.set-row__film:active{transform:scale(.92)}
.set-row__film:hover{color:var(--chrome);background:var(--steel-2)}
.set-row.is-done .set-row__film{color:var(--nickel-2);opacity:.55}

/* The lens chip. Text, not an icon: "Wide" and "1x" mean something, a lens
   glyph means "there is a lens". Hidden until refreshLenses() finds more than
   one on the side you are on — most iPhones through Safari expose exactly one,
   and a control that does nothing is worse than no control. */
.capture__tool[data-lens]{min-width:38px;font:800 12px/1 var(--font);letter-spacing:.02em}

/* ── THE ACTIVE-WORKOUT BAR ──────────────────────────────────────────────
   Sits directly above the tab bar. z-index 11: over .app-body, under the tab
   bar (10 is the bar itself; the rest timer used to claim 12 for this same
   slot and now renders inside here instead). */
.workbar{position:fixed;left:0;right:0;z-index:11;display:flex;align-items:center;gap:var(--s3);
  width:100%;border:0;text-align:left;cursor:pointer;overflow:hidden;
  bottom:calc(var(--nav-h) + env(safe-area-inset-bottom));
  padding:10px var(--s4);background:var(--anvil);
  box-shadow:inset 0 1px 0 var(--edge), 0 -8px 24px rgba(0,0,0,.34)}
.workbar:active{background:var(--steel)}
/* Heat, and this is the one place it is right: a workout in progress is the
   definition of live right now, and there is never a second one on screen. */
.workbar__pulse{flex:0 0 auto;width:8px;height:8px;border-radius:50%;background:var(--heat);
  animation:lf-pulse 2s var(--ease) infinite}
@keyframes lf-pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.45;transform:scale(.8)}}
@media (prefers-reduced-motion:reduce){.workbar__pulse{animation:none}}
.workbar__body{flex:1;min-width:0}
.workbar__t{display:block;font:700 14px/1.2 var(--font);color:var(--chrome);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.workbar__s{display:block;font-size:11.5px;color:var(--nickel-2);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.workbar__clock{flex:0 0 auto;font-size:16px;font-variant-numeric:tabular-nums;color:var(--chrome)}
.workbar__clock.is-rest{color:var(--heat)}
.workbar__go{flex:0 0 auto;color:var(--nickel-2);display:flex;align-items:center}
/* The rest countdown, as a hairline draining along the top edge of the bar. */
.workbar__rest{position:absolute;left:0;top:0;height:2px;background:var(--heat);
  width:0;opacity:0;transition:width 1s linear}
.workbar__rest.is-on{opacity:1}
/* The scroll container gives back exactly the height the bar covers, or the
   last card on every tab screen sits underneath it. Same pattern as
   body.tunnel below. */
body.has-workbar .app-body{padding-bottom:calc(var(--nav-h) + 52px + env(safe-area-inset-bottom) + var(--s5))}
/* Toasts sit exactly where the bar goes; lift them above it while it is up. */
body.has-workbar .toast{bottom:calc(var(--nav-h) + 52px + env(safe-area-inset-bottom) + 16px)}

/* ── THE MOVEMENT PICKER ──────────────────────────────────────────────────
   An overlay, not a screen. It is opened FROM a routine builder or a live
   workout and must return there — a screen would put it in SCREENS and in the
   back stack, and show() would have to know which screen sent you.
   z-index 12 sits above .workbar (11) deliberately: while you are choosing a
   movement, the bar you would be going back to is not the thing to tap. */
.picker{position:fixed;inset:0;z-index:12;display:flex;flex-direction:column;
  background:var(--forge);animation:pickerIn .16s ease-out both}
.picker.hide{display:none}
@keyframes pickerIn{from{opacity:0;transform:translateY(10px)}}
@media (prefers-reduced-motion:reduce){.picker{animation:none}}
.picker__head{display:flex;align-items:center;gap:var(--s2);flex:none;
  padding:calc(env(safe-area-inset-top,0px) + var(--s3)) var(--s3) var(--s2);
  box-shadow:inset 0 -1px 0 var(--edge)}
.picker__head .t-lbl:first-of-type{flex:1;min-width:0}
.picker__x{background:none;border:0;color:var(--nickel-2);font-size:26px;
  line-height:1;padding:0 4px;cursor:pointer;flex:none}
.picker__body{flex:1;overflow-y:auto;-webkit-overflow-scrolling:touch;
  display:flex;flex-direction:column;gap:var(--s2);
  padding:var(--s3) var(--s3) calc(env(safe-area-inset-bottom,0px) + var(--s4))}
/* Only the list scrolls; the search and the chips stay reachable with one hand
   while the list is long. */
.picker__body > .list{flex:1;min-height:0}
/* The bar is not a target while the picker is up — it would navigate out from
   under the choice being made. */
body.has-picker .workbar{display:none}

/* ── COMMENTS ─────────────────────────────────────────────────────────────
   Rides the existing .sheet (bottom sheet, .is-on, shared #scrim) rather than
   inventing a second sheet mechanism — there was already one and a parallel
   one is how two dismissal behaviours end up in the same app. */
.cm-list{max-height:46vh;overflow-y:auto;-webkit-overflow-scrolling:touch;
  margin-bottom:var(--s3)}
.cm-row{padding:10px 0}
.cm-row + .cm-row{box-shadow:inset 0 1px 0 var(--edge)}
.cm-row__h{display:flex;align-items:baseline;gap:8px;font-size:12.5px}
.cm-row__h b{color:var(--chrome);font-weight:700}
.cm-row__b{font-size:14px;color:var(--nickel);margin-top:3px;
  overflow-wrap:anywhere}
/* The composer stays put while the list scrolls under it — a comment box that
   scrolls away is a comment nobody finishes. */
.cm-compose{display:flex;gap:var(--s2);align-items:center}
.cm-compose .input{flex:1;min-width:0}

/* ── A SESSION'S SETS, ON THE CARD ────────────────────────────────────────
   Collapsed by default (.hide); the toggle flips the class in the DOM rather
   than re-rendering, because renderFeed() rebuilds every card and a re-render
   would close the panel on the next like anywhere in the feed. */
.fs{margin-top:var(--s3);padding-top:var(--s3);box-shadow:inset 0 1px 0 var(--edge)}
.fs.hide{display:none}
.fs__ex + .fs__ex{margin-top:var(--s3)}
.fs__t{font-size:13px;font-weight:700;color:var(--chrome)}
.fs__sets{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}
.fs__s{display:inline-flex;align-items:center;gap:6px;font-size:12.5px;
  color:var(--nickel);padding:5px 9px;border-radius:8px;
  box-shadow:inset 0 0 0 1px var(--edge);font-variant-numeric:tabular-nums}
.fs__n{font-size:10.5px;font-weight:700;color:var(--nickel-2)}

/* ── IDENTITY ─────────────────────────────────────────────────────────────
   A monogram on a hue derived from the athlete's id — deterministic, so the
   same person is the same colour every render and two people differ.
   🔴 THE SLOT IS SHAPED FOR A PHOTO. When image screening exists, an <img>
   replaces the span and nothing else on the card moves. */
.mono{display:inline-flex;align-items:center;justify-content:center;flex:none;
  border-radius:50%;color:var(--chrome);font-weight:800;font-size:15px;
  box-shadow:inset 0 0 0 1px var(--edge)}

/* ── ONBOARDING ───────────────────────────────────────────────────────────
   One question per screen, full height, with the actions pinned at the bottom
   so the thumb never travels and the answer options never reflow under it. */
#screen-onboard .app-body{display:flex;flex-direction:column;
  padding-top:calc(env(safe-area-inset-top,0px) + var(--s5))}
.ob-dots{display:flex;gap:5px;margin-bottom:var(--s5)}
.ob-dot{height:3px;flex:1;border-radius:2px;background:var(--steel-3)}
.ob-dot.is-on{background:var(--chrome)}
.ob-opt{display:flex;flex-direction:column;align-items:flex-start;gap:2px;
  width:100%;text-align:left;padding:14px var(--s4);border:0;cursor:pointer;
  border-radius:var(--r);background:var(--steel-2);
  box-shadow:inset 0 0 0 1px var(--edge)}
/* Gold means a person touched this — and a chosen answer is exactly that.
   It is the one place in onboarding it appears. */
.ob-opt.is-on{box-shadow:inset 0 0 0 1.5px var(--gold)}
.ob-opt:active{transform:scale(.99)}
.ob-opt__l{font-size:15px;font-weight:700;color:var(--chrome)}
.ob-opt__s{font-size:12.5px;color:var(--nickel-2)}
/* The capped-week sentence. Not heat, not gold: it is information, not an
   error and not an achievement. */
.ob-note{margin-top:var(--s4);padding:12px var(--s4);border-radius:var(--r);
  background:var(--steel);box-shadow:inset 0 0 0 1px var(--edge);
  font-size:13px;color:var(--nickel);line-height:1.5}
.ob-foot{margin-top:auto;padding-top:var(--s5);display:flex;flex-direction:column;
  gap:var(--s2);padding-bottom:calc(env(safe-area-inset-bottom,0px) + var(--s4))}
