/* The Lazy GymRat — one commitment: dark, high contrast, thumb-first. */

:root {
  --bg: #0b0c0e;
  --surface: #15171b;
  --surface-2: #1d2026;
  --line: #2a2e36;
  --text: #f2f3f5;
  --muted: #8b9199;
  /* Lifted from #616872, which measured 3.19:1 on --surface and failed
     WCAG AA for body text. It labels 12px uppercase section titles and rep
     ranges — read at arm's length, on a floor, often by someone over forty.
     #7c8490 measures 4.75:1 and stays a clear step below --muted. */
  --dim: #7c8490;
  --accent: #c4f43a;
  /* The accent at reduced strength, for a bar that means "real work, short
     of the target". It has to read clearly as the same colour family as
     --accent while never being mistaken for it, because the difference
     between the two IS the information on the eight-week chart. */
  --accent-soft: #6f8a24;
  --accent-ink: #12160a;
  --warn: #ffb340;
  --bad: #ff6b4a;
  --radius: 14px;
  --radius-lg: 20px;
  --tap: 48px;
  /* The system stack on purpose: this app installs to a home screen and has
     to open offline, so it never waits on a web font. */
  --font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  --shell: 520px;
}

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
  overscroll-behavior-y: none;
}

button, input, select, textarea {
  font: inherit;
  color: inherit;
}

button { cursor: pointer; border: 0; background: none; }

h1, h2, h3, p { margin: 0; }

.shell {
  max-width: var(--shell);
  margin: 0 auto;
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--bg);
}

/* ------------------------------------------------------------------ topbar */

.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: calc(env(safe-area-inset-top, 0px) + 14px) 18px 12px;
  background: linear-gradient(var(--bg) 72%, rgba(11, 12, 14, 0));
}

.wordmark {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: -0.01em;
}

.wordmark .mark {
  width: 26px;
  height: 26px;
  border-radius: 8px;
  background: var(--accent);
  color: var(--accent-ink);
  display: grid;
  place-items: center;
}

.wordmark .mark svg { width: 18px; height: 18px; display: block; }

.streak-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  /* Tall enough to hit: this is a button when a trial is running. */
  min-height: 40px;
  padding: 8px 13px;
  border-radius: 999px;
  background: var(--surface);
  border: 1px solid var(--line);
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
}

.streak-pill b { color: var(--text); }

/* The weekly counter: the number large and the unit small, so the thing that
   moves when you log a set is the thing the eye lands on. */
.bar-right { display: flex; align-items: center; gap: 8px; }

.count-pill { gap: 5px; }
.count-pill b { font-size: 17px; line-height: 1; }
.count-pill span {
  font-size: 11px;
  font-weight: 600;
  line-height: 1.1;
  color: var(--muted);
  max-width: 4.2em;
}

/* THE WEEKLY SCOREBOARD. Stacked rather than inline, because "3 / 6" over
   "cleared" reads as one figure with a caption, and "3 / 6 cleared" on one
   line reads as a sentence fragment somebody has to parse.

   It is a real button - it opens the sheet that explains the whole game -
   so it carries the 40px floor .streak-pill sets and a pressed state. It
   already looked tappable before it was; that was the tell. */
.score-pill {
  position: relative;
  overflow: hidden;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  padding: 6px 14px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* THE MOMENTUM, BEHIND THE MILESTONE. Driven by weekReport.progress, which
   is completion-aware: it cannot fill the pill while any target is still
   short of its direct work, so a full pill and X / X always arrive
   together. Deliberately faint - it is a background, and the fraction on
   top of it is what is being read. */
.score-fill {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  background: rgba(196, 244, 58, 0.16);
  border-right: 1px solid rgba(196, 244, 58, 0.28);
  transition: width 420ms cubic-bezier(0.2, 0.9, 0.3, 1);
  pointer-events: none;
}
.score-face {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
}
/* Cleared already takes the accent across the whole pill, so a brighter
   band inside it would only muddy the one state that should read instantly. */
.score-pill.cleared .score-fill { background: none; border-right: 0; }

@media (prefers-reduced-motion: reduce) {
  .score-fill { transition: none; }
}
.score-pill b { font-size: 17px; line-height: 1.05; letter-spacing: -0.01em; }
.score-pill span {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  line-height: 1;
  color: var(--muted);
}
.score-pill:active { transform: scale(0.96); }

/* The finish line, reached. The only place in the top bar that ever takes
   the accent, so it cannot be confused with the ordinary state. */
.score-pill.cleared {
  border-color: var(--accent);
  background: rgba(196, 244, 58, 0.12);
}
.score-pill.cleared b { color: var(--accent); font-size: 13px; letter-spacing: 0.04em; }
.score-pill.cleared span { color: var(--accent); }

/* The two figures inside the scoreboard sheet, side by side so the thing
   people get confused about - sets against targets - is visibly two
   different readings of one week rather than one number contradicting
   itself. */
.goal-figures {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 10px;
  margin: 16px 0 14px;
}
.goal-figures > div {
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.02);
}
.goal-figures b { display: block; font-size: 20px; line-height: 1.1; color: var(--text); }
.goal-figures span { display: block; margin-top: 3px; font-size: 11.5px; color: var(--muted); }

/* Secondary, and it looks it. The trial clock has a full card in Setup; this
   is the reminder that sends somebody to read it, and it must never be the
   reason the weekly count is missing from the top of the screen. */
.trial-chip {
  border: 1px solid var(--line);
  background: transparent;
  color: var(--muted);
  font-family: inherit;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.02em;
  padding: 7px 10px;
  min-height: 34px;
  border-radius: 999px;
  cursor: pointer;
}

.trial-chip:active { background: var(--surface); }

/* A quiet aside inside the walkthrough — guidance, not an instruction. */
.hint {
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--muted);
}

.hint b { color: var(--text); }

/* ---------------------------------------------------------------- switch */

.switch {
  flex: none;
  width: 52px;
  height: 32px;
  border-radius: 99px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  padding: 3px;
  display: flex;
  align-items: center;
  transition: background 160ms ease, border-color 160ms ease;
}

.switch > i {
  width: 24px;
  height: 24px;
  border-radius: 99px;
  background: var(--dim);
  transition: transform 160ms ease, background 160ms ease;
}

.switch.on { background: var(--accent); border-color: var(--accent); }
.switch.on > i { background: var(--accent-ink); transform: translateX(20px); }

@media (prefers-reduced-motion: reduce) {
  .switch, .switch > i { transition: none; }
}

/* ------------------------------------------------------------ celebration */

/* Clearing a muscle's week is the payoff the whole system is built toward,
   and until now it just quietly filled a bar. One sweep of the accent, over
   in a beat, so it reads as earned without standing between someone and
   their next set. Removed from the DOM as soon as it finishes. */
.flare {
  position: fixed;
  inset: 0;
  z-index: 60;
  pointer-events: none;
  background:
    radial-gradient(120% 60% at 50% 100%, rgba(196, 244, 58, 0.30), transparent 62%),
    radial-gradient(90% 50% at 50% 0%, rgba(196, 244, 58, 0.16), transparent 60%);
  animation: flare 1.1s ease-out forwards;
}

@keyframes flare {
  0%   { opacity: 0; }
  18%  { opacity: 1; }
  100% { opacity: 0; }
}

/* The bar that just filled, called out on the Week tab so the thing that
   happened is still visible when you go looking for it. */
.mrow.just-cleared .track > i { animation: swell 900ms ease-out; }
.mrow.just-cleared .name { color: var(--accent); }

@keyframes swell {
  0%   { filter: brightness(1); }
  35%  { filter: brightness(1.7); }
  100% { filter: brightness(1); }
}

@media (prefers-reduced-motion: reduce) {
  .flare { display: none; }
  .mrow.just-cleared .track > i { animation: none; }
}

/* -------------------------------------------------------------------- view */

.view {
  flex: 1;
  padding: 4px 18px 132px;
}

/* Only a tab change animates. Logging a set must not flash the whole list. */
.view.enter { animation: rise 180ms ease-out; }

@keyframes rise {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .view.enter { animation: none; }
}

.section-title {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--dim);
  margin: 26px 0 10px;
}

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 18px;
}

.card + .card { margin-top: 12px; }

.muted { color: var(--muted); }
.small { font-size: 13px; }
.tiny { font-size: 12px; }

/* ------------------------------------------------------------- week header */

/* Orientation, not action. It sits above the workout because "am I on pace"
   is worth knowing before he picks a set, and it is kept tight for the same
   reason: every pixel here pushes the exercise itself further down a phone.
   Measured at 115px before this block, 92px after. */
.card.weekbar { padding: 14px 16px; }
.card.weekbar .weekbar-head { margin-bottom: 8px; }
.card.weekbar .weekbar-head .big { font-size: 25px; }
.card.weekbar .pace { margin-top: 8px; }

.weekbar-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 10px;
}

.weekbar-head .big {
  font-size: 30px;
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1;
}

.weekbar-head .big span {
  font-size: 15px;
  font-weight: 600;
  color: var(--muted);
  letter-spacing: 0;
}

.bar {
  height: 8px;
  border-radius: 99px;
  background: var(--surface-2);
  overflow: hidden;
}

.bar > i {
  display: block;
  height: 100%;
  border-radius: 99px;
  background: var(--accent);
  transition: width 320ms ease;
}

.pace {
  margin-top: 10px;
  font-size: 13px;
  color: var(--muted);
}

.pace b { color: var(--text); }
.pace.behind b { color: var(--warn); }

/* The goal's own standing, reported next to the week's.

   It sits one step brighter than .pace because it is the line the person
   who set a goal actually came for, and it is colour-coded by state rather
   than by severity: clearing a target and being three sets from clearing
   one are both good news and both read in the accent, behind reads in the
   warn colour, and on pace stays quiet. Nothing here shouts - a goal you
   are behind on is still a goal you are working on. */
/* The goal's own name, so the sentence after it cannot be mistaken for a
   statement about a muscle bar. */
.gl-label {
  display: block;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  opacity: 0.75;
  margin-bottom: 3px;
}

.goal-line {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--line);
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
}

.goal-line.clear,
.goal-line.close { color: var(--accent); }
.goal-line.behind { color: var(--warn); }
/* Not warn and not accent. Equipment Limited is neither a warning about
   the person's effort nor progress to celebrate - it is a fact about the
   room, and colouring it like a shortfall would say the opposite. */
.goal-line.limited { color: var(--dim); }

/* On the Week screen it is a standalone paragraph rather than a footer to
   the card above, so it carries its own spacing and no rule. */
.week-sub + .goal-line {
  margin: 0 0 14px;
  padding-top: 0;
  border-top: 0;
  font-size: 14px;
}

/* ------------------------------------------------------------- next up card */

/* The chart's own header: title on the left, range toggle on the right. */
.chart-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 26px 0 10px;
}

.range {
  display: flex;
  gap: 2px;
  padding: 2px;
  border-radius: 999px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  flex: none;
}

/* 40px tall, the same floor .streak-pill sets for a pill that is a real
   button. 29px was comfortable to look at and mean to hit on a phone. */
.range button {
  min-height: 40px;
  padding: 0 15px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 700;
  color: var(--muted);
}

.range button.on { background: var(--accent); color: var(--accent-ink); }

/* Fourteen columns in the width eight used to have, so the gap closes and
   the labels drop to the day of the month. */
.weeks.days { gap: 3px; }
.weeks.days .lab { font-size: 9px; }
.weeks.days .col > i { border-radius: 3px 3px 1px 1px; }

/* The day's frame, sitting between the week bar and the move. Deliberately
   not a card: it is a caption on what comes next, not a thing in its own
   right, and giving it a box would make it compete with the set it
   introduces. */
.lean {
  margin: 16px 2px 10px;
  font-size: 13px;
  color: var(--muted);
}

.lean b { color: var(--accent); font-weight: 700; display: block; font-size: 15px; }
/* The qualifier underneath rather than trailing the muscle on one line:
   the muscle is what somebody needs, the reason is why it is there, and a
   comma between them made both of them prose. */
.lean span { display: block; margin-top: 1px; font-size: 12px; color: var(--muted); }

.next {
  border-color: #333a20;
  background:
    radial-gradient(120% 100% at 0% 0%, rgba(196, 244, 58, 0.09), transparent 60%),
    var(--surface);
}

.next .why {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent);
}

.next .dot {
  width: 7px;
  height: 7px;
  border-radius: 99px;
  background: currentColor;
}

.next h2 {
  font-size: 27px;
  font-weight: 800;
  letter-spacing: -0.025em;
  line-height: 1.1;
  margin: 12px 0 6px;
}

.next .target {
  font-size: 15px;
  color: var(--muted);
  font-weight: 500;
}

.next .target b { color: var(--text); font-weight: 700; }

/* What he is chasing. Deliberately the quietest of the three cards: it is
   a standing fact rather than a thing to do right now, and it sits above
   two cards that both want a tap. */
.rec {
  border-color: var(--line);
  background: var(--surface);
}

.rec .why { color: var(--muted); }
.rec h2 { font-size: 22px; }

/* Today's challenge. It reuses .next wholesale, because it is the same
   card doing the same job, and only changes the accent so the two do not
   read as one long block of volt. */
.chal {
  border-color: #3a2f14;
  background:
    radial-gradient(120% 100% at 0% 0%, rgba(255, 179, 64, 0.10), transparent 60%),
    var(--surface);
}

.chal .why { color: #ffb340; }

/* Why the twist is worth doing. Its own class rather than .fail, which
   means "this is what close to failure feels like" and is not that. */
.chal .because {
  margin-top: 8px;
  color: #ffb340;
}

/* The merged card, where the challenge and the suggested move are the same
   exercise. The cue above it says how to do the move at all; this says what
   is different about it today, so it is boxed and amber rather than running
   on as more grey instruction. Both amber values are the ones already used
   on this card, measured over the composited panel: #ffb340 lands near
   10.7:1 and the body text near 15:1. */
.chal .twist {
  margin-top: 14px;
  padding: 12px 14px;
  border: 1px solid #3a2f14;
  border-radius: var(--radius);
  background: rgba(255, 179, 64, 0.07);
  font-size: 14px;
  line-height: 1.5;
  color: var(--text);
}

.chal .twist-name {
  margin-bottom: 6px;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #ffb340;
}

/* Done: the card stays, quietly. Vanishing it would take away the only
   evidence he did the thing. */
.chal.done {
  border-color: var(--line);
  background: var(--surface);
}

.chal.done .why,
.chal.done h2 { color: var(--muted); }

.chal.done h2 { font-size: 19px; margin-bottom: 4px; }

.cue {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
  font-size: 14px;
  color: var(--muted);
  line-height: 1.5;
}

/* The line that only shows on a move he has never logged, and the one that
   shows when he taps Easy. Set in the accent so it reads as guidance from the
   app rather than as more of the exercise description, and sized down so it
   never competes with the instruction it sits beside. */
.opener {
  margin-top: 10px;
  padding: 9px 11px;
  border-left: 2px solid var(--accent);
  border-radius: 0 8px 8px 0;
  background: rgba(196, 244, 58, 0.06);
  font-size: 13px;
  line-height: 1.45;
  color: var(--text);
}

/* Same shape, amber, for the one that is a correction rather than a welcome. */
.opener.warn {
  margin-top: 12px;
  border-left-color: var(--warn);
  background: rgba(255, 179, 64, 0.07);
}

.cue .fail {
  margin-top: 8px;
  color: var(--warn);
}

/* "Make it yours" — the one knob on this move. Set apart from the cue rather
   than trailing it, because the cue is what to do and this is what to decide.
   Accent-tinted and not amber: amber belongs to the daily challenge, and this
   is always available on moves that have it. */
.adapt {
  margin-top: 12px;
  padding: 11px 13px;
  border: 1px solid #2c3a17;
  border-radius: var(--radius);
  background: rgba(196, 244, 58, 0.06);
  font-size: 14px;
  line-height: 1.5;
  color: var(--text);
}

.adapt-label {
  display: block;
  margin-bottom: 5px;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  /* 8.9:1 on the composited panel. */
  color: var(--accent);
}

/* The setup check, on the ten moves that hang bodyweight on furniture.
   Built off .adapt because it sits in the same place and reads the same
   way - a boxed note with a label - and warm rather than volt, because
   this is the one thing on the sheet that is not an opportunity. */
/* Tighter than .adapt, which it was built from. A setup check is short by
   design and sits ABOVE the effort buttons, so every pixel it takes is a
   pixel of the thing that logs the set - and on a 360x640 phone the
   straight-leg chair dip went over the fold the moment this block existed.
   Compact suits a warning anyway; it is not something to be browsed. */
/* Informational, not a warning: this one reports a swap rather than asking
   somebody to check something before they start, so it is deliberately
   quieter than .setup above and sits in the neutral card colour. */
.serve-note {
  margin-top: 10px;
  padding: 9px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.03);
  font-size: 13px;
  line-height: 1.45;
  color: var(--dim);
}
.serve-note b { color: var(--text); font-weight: 600; }

.setup {
  margin-top: 10px;
  padding: 9px 12px;
  border: 1px solid #4a3418;
  border-radius: var(--radius);
  background: rgba(255, 179, 64, 0.07);
  font-size: 13.5px;
  line-height: 1.45;
  color: var(--text);
}

/* Inline rather than a label on its own line. The check sits ABOVE the
   effort buttons, and on a 360x640 phone a first-time risky move was
   stacking the rep-guess note and this block over the save until the thing
   that logs the set fell off the bottom. A whole line of uppercase label
   was the cheapest 17px to give back, and the words still lead. */
.setup b {
  font-weight: 800;
  /* Shoulders orange on the composited panel, same family as .adapt's
     volt and a comparable contrast ratio. */
  color: #ffb340;
}

/* Quieter than the failure cue: this is an option for extending a set, not
   an instruction, and it must not compete with "stop when". */
.cue .partials {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px dashed var(--line);
  color: var(--dim);
  font-size: 13px;
}

.btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  min-height: 54px;
  border-radius: var(--radius);
  background: var(--accent);
  color: var(--accent-ink);
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -0.01em;
  /* The checkout button is an anchor, so it needs the link styling reset. */
  text-decoration: none;
  transition: transform 90ms ease, filter 140ms ease;
}

.btn:active { transform: scale(0.985); filter: brightness(0.94); }

.btn.ghost {
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--line);
  min-height: var(--tap);
  font-weight: 600;
  font-size: 15px;
}

.btn.danger { background: var(--surface-2); color: var(--bad); border: 1px solid var(--line); }

.btn-row { display: flex; gap: 10px; margin-top: 14px; }
.btn-row .btn { flex: 1; }

.link {
  color: var(--accent);
  font-weight: 600;
  font-size: 14px;
  text-decoration: none;
  padding: 6px 0;
}

/* ------------------------------------------------------------ muscle rows */

.mrow {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 4px 10px;
  padding: 13px 0;
  border-bottom: 1px solid var(--line);
}

.mrow:last-child { border-bottom: 0; padding-bottom: 0; }
.mrow:first-child { padding-top: 0; }

.mrow .name {
  font-size: 15px;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 9px;
}

.mrow .swatch {
  width: 9px;
  height: 9px;
  border-radius: 3px;
}

.mrow .count { font-size: 14px; font-weight: 600; color: var(--muted); }
.mrow .count b { color: var(--text); }

/* The one row that reports a condition instead of a number. Smaller,
   because it is words where the others have a figure, and in the warn
   colour so the eye reads it as "not finished" before reading it at all -
   which is the job the missing tick cannot do on its own. */
.mrow .count.short-flag {
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--warn);
  text-align: right;
  line-height: 1.2;
}
.mrow .track { grid-column: 1 / -1; }

/* Says what the tick is waiting for. Only ever on a row whose bar is already
   full, so it reads as the remaining condition rather than as a complaint
   about the number above it. */
.mnote {
  grid-column: 1 / -1;
  margin-top: 6px;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--muted);
}

.mnote b { color: var(--text); }

/* Not an error state. The muscle has done the volume; it has done it the
   wrong way round, and the app is asking for one or two specific sets. */
.mrow.short .count b { color: var(--text); }

.track {
  position: relative;
  height: 7px;
  border-radius: 99px;
  background: var(--surface-2);
  overflow: hidden;
}

.track > i {
  position: absolute;
  inset: 0 auto 0 0;
  border-radius: 99px;
  transition: width 320ms ease;
}

.track > u {
  position: absolute;
  top: -2px;
  bottom: -2px;
  width: 2px;
  background: rgba(255, 255, 255, 0.34);
}

.mrow.done .count b { color: var(--accent); }

/* -------------------------------------------------------------- today list */

.logrow {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 0;
  border-bottom: 1px solid var(--line);
}

.logrow:last-child { border-bottom: 0; padding-bottom: 0; }

.logrow .time {
  font-variant-numeric: tabular-nums;
  font-size: 13px;
  color: var(--dim);
  width: 62px;
  flex: none;
}

.logrow .what { flex: 1; min-width: 0; }
.logrow .what .n { font-size: 15px; font-weight: 600; }
.logrow .what .d { font-size: 13px; color: var(--muted); }

.logrow .undo {
  color: var(--dim);
  font-size: 13px;
  font-weight: 600;
  padding: 8px;
  flex: none;
  /* Same trick the quota steppers use: the label stays small, the touch
     target does not. It was 49x35 and had no halo at all, which made it the
     one control in the app that undoes something and the hardest to hit -
     and it sits inches from a thumb scrolling the day's log. */
  position: relative;
}

.logrow .undo::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100%;
  height: 44px;
  min-width: 44px;
  transform: translate(-50%, -50%);
}

.tag {
  display: inline-block;
  padding: 2px 7px;
  border-radius: 6px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  background: var(--surface-2);
  color: var(--muted);
}

.tag.half { color: var(--warn); }

.empty {
  text-align: center;
  padding: 26px 12px;
  color: var(--dim);
  font-size: 14px;
}

/* --------------------------------------------------------------- triggers */

.trigger {
  display: flex;
  align-items: center;
  gap: 13px;
  width: 100%;
  padding: 13px 0;
  border-bottom: 1px solid var(--line);
  text-align: left;
}

.trigger:last-of-type { border-bottom: 0; }

.tick {
  width: 24px;
  height: 24px;
  flex: none;
  border-radius: 8px;
  border: 2px solid var(--line);
  display: grid;
  place-items: center;
  font-size: 13px;
  color: transparent;
}

.trigger.on .tick { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.trigger.on .label { color: var(--muted); text-decoration: line-through; }

.trigger .label { flex: 1; font-size: 15px; font-weight: 500; }
.trigger .x { color: var(--dim); padding: 6px; font-size: 15px; }

/* Today is deliberately sparse. Secondary systems stay available without
   arriving as another stack of full-height cards. */
.today-empty {
  padding: 2px 2px 6px;
  color: var(--dim);
  font-size: 14px;
}

.spots-quiet {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 2px 2px 6px;
  color: var(--dim);
  font-size: 13.5px;
}

.spots-quiet .link { flex: none; }

.spots-fold { padding: 0 16px; }

.spots-fold > summary {
  min-height: 68px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  cursor: pointer;
  list-style: none;
}

.spots-fold > summary::-webkit-details-marker { display: none; }

.spots-summary {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.spots-summary b {
  color: var(--text);
  font-size: 14px;
  font-weight: 700;
}

.spots-summary span {
  color: var(--dim);
  font-size: 12px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.spots-open {
  flex: none;
  color: var(--muted);
  font-size: 12px;
  font-weight: 700;
}

.spots-fold[open] .spots-open::after { content: ' less'; }
.spots-fold[open] .spots-open { font-size: 0; }
.spots-fold[open] .spots-open::after { font-size: 12px; }

.spots-list {
  border-top: 1px solid var(--line);
  padding-bottom: 4px;
}

.compact-maintenance {
  min-height: 58px;
  padding: 10px 14px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
}

.compact-maintenance .compact-copy {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.compact-maintenance .compact-copy b {
  font-size: 13.5px;
  color: var(--text);
}

.compact-maintenance .compact-copy span {
  font-size: 12px;
  color: var(--dim);
}

.compact-maintenance .compact-action {
  flex: none;
  color: var(--accent);
  font-weight: 700;
}

/* ------------------------------------------------------------------ moves */

/* Push / pull / legs / posture shelves. Heavier than a pattern head so the
   eye lands on the group first and the ladders read as its contents. */
.group-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin: 30px 0 2px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--line);
}

.group-head:first-of-type { margin-top: 24px; }

.group-name {
  font-size: 20px;
  font-weight: 800;
  letter-spacing: -0.02em;
}

.group-count {
  flex: none;
  font-size: 12px;
  font-weight: 600;
  color: var(--dim);
}

.group-blurb {
  margin: 8px 0 0;
  font-size: 13px;
  line-height: 1.45;
  color: var(--muted);
}

.pattern-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 12px;
}

.pattern-head h3 { font-size: 17px; font-weight: 700; letter-spacing: -0.01em; }

.rung {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 11px 12px;
  border-radius: 12px;
  text-align: left;
  border: 1px solid transparent;
}

.rung + .rung { margin-top: 4px; }

.rung .lv {
  width: 26px;
  height: 26px;
  flex: none;
  border-radius: 8px;
  background: var(--surface-2);
  color: var(--dim);
  display: grid;
  place-items: center;
  font-size: 12px;
  font-weight: 700;
}

.rung .nm { flex: 1; font-size: 15px; font-weight: 500; }
/* This column holds two very different strings. On an open rung it is
   "12-25" and takes no room. On a locked one it is "needs floor + table +
   chair", which is long enough to fight the name column for width - see the
   note on .rung .hits below for which one wins and why. */
.rung .rp {
  font-size: 12px;
  color: var(--dim);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* Which part of the muscle this rung builds, under its name.

   An <i> rather than a second span because it sits INSIDE .nm: the name
   span is the flex child that takes the leftover width, so a block element
   in there wraps under the name and stops short of the rep count instead of
   running behind it. Reset the italics - the tag is for the line break. */
.rung .hits {
  display: block;
  margin-top: 1px;
  font-size: 11.5px;
  font-style: normal;
  font-weight: 400;
  line-height: 1.3;
  color: var(--dim);

  /* These three lines are load-bearing, and not for the reason they look
     like they are.

     The obvious reading is "stop long labels wrapping". The real job is
     upstream of that. A flex item's min-width is auto, which resolves to
     its min-content width, so making this child unbreakable makes the whole
     .nm column unbreakable too: the name column now refuses to shrink below
     the label, and the "needs floor + table + chair" text to its right is
     what gives way instead.

     Without it, measured at 360px on a man whose only anchor was the floor,
     the lock text held one line, squeezed the name column to 90px and
     wrapped 22 of the 81 labels onto a second line. Nothing looked broken -
     the exercise NAMES are short enough to survive 90px - the ladder just
     quietly stopped being scannable.

     The ellipsis is the leftover safety net for a label that somehow still
     does not fit: a clipped label is survivable, rows of different heights
     are not. A test asserts nothing is actually clipped today, so this
     stays a net rather than somewhere labels quietly go to die. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* On the rung you are on now, the one that has to stay readable at a
   glance. The name goes bold there, which would drag this with it. */
.rung.current .hits { font-weight: 400; color: var(--muted); }

.rung.current {
  background: var(--surface-2);
  border-color: #3d4526;
}

.rung.current .lv { background: var(--accent); color: var(--accent-ink); }
.rung.current .nm { font-weight: 700; }

.rung.locked { opacity: 0.4; }
.rung.locked .rp { color: var(--dim); }

/* --------------------------------------------------------------- level up */

.levelup {
  border-color: #4a4520;
  background: linear-gradient(180deg, rgba(255, 179, 64, 0.1), transparent), var(--surface);
}

.levelup h3 { font-size: 17px; font-weight: 700; margin-bottom: 6px; }

/* ------------------------------------------------------------------ stats */

.stat-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}

.stat {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 15px;
}

.stat .n {
  font-size: 26px;
  font-weight: 800;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
}

.stat .l { font-size: 12px; color: var(--muted); margin-top: 2px; }

.weeks {
  display: flex;
  align-items: stretch;
  gap: 6px;
  height: 92px;
  margin-top: 6px;
}

/* The columns must stretch to the full 92px or the percentage heights
   inside them have nothing to resolve against. */
.weeks .w { flex: 1; height: 100%; display: flex; flex-direction: column; align-items: center; gap: 6px; }

.weeks .col {
  width: 100%;
  flex: 1;
  display: flex;
  align-items: flex-end;
}

/* A week he trained but did not finish. This was --surface-2, one step off
   the card it sits on, which rendered twelve logged sets as an empty chart.
   The bar exists to show him the work; it has to be visible. */
.weeks .col > i {
  width: 100%;
  border-radius: 5px 5px 2px 2px;
  background: var(--accent-soft);
  min-height: 3px;
}

/* A week with nothing in it. Kept as a faint floor rather than nothing at
   all, so eight weeks always read as eight columns.

   Named "untrained" and not "empty": .empty is already a placeholder style
   further up this file carrying `padding: 26px 12px`, and a column that
   quietly picked that up lost 52px of height and dragged its own date label
   26px out of line with every other one. Class names in this stylesheet are
   global, so a bare word is somebody else's word. */
.weeks .w.untrained .col > i { background: var(--line); }

.weeks .w.hit .col > i { background: var(--accent); }
.weeks .w.current .col > i { outline: 1px solid var(--dim); }
.weeks .lab { font-size: 10px; color: var(--dim); font-variant-numeric: tabular-nums; }

/* --------------------------------------------------------------- settings */

.setting {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 14px 0;
  border-bottom: 1px solid var(--line);
}

.setting:last-child { border-bottom: 0; padding-bottom: 0; }
.setting:first-child { padding-top: 0; }

/* The one text field in the app outside the license box. 16px is not a
   style choice: iOS zooms the whole page in on focus for anything smaller,
   and the page never zooms back out. */
.namefield {
  width: 148px;
  padding: 11px 13px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-2);
  color: var(--text);
  font-size: 16px;
  text-align: right;
}

.namefield::placeholder { color: var(--dim); }
.namefield:focus { outline: 2px solid var(--accent); outline-offset: 1px; }

/* A challenge from a person, so it is warmer than the record card it sits
   above and cooler than the amber the daily challenge owns. */
.card.rec.rival {
  border-color: #24405c;
  background:
    radial-gradient(120% 100% at 0% 0%, rgba(74, 168, 255, 0.10), transparent 60%),
    var(--surface);
}

.card.rec.rival .why { color: #6fbaff; }

/* The permanent "challenge a friend" card on the Week tab. Same blue as the
   rival card, because both are about a person rather than the product, and
   this is the other end of the same exchange. */
.card.share-card {
  border-color: #24405c;
  background:
    radial-gradient(120% 100% at 0% 0%, rgba(74, 168, 255, 0.10), transparent 60%),
    var(--surface);
}

.card.share-card .why { color: #6fbaff; }
.card.share-card h2 { margin-bottom: 6px; }
.setting .k { font-size: 15px; font-weight: 500; }
.setting .sub { font-size: 12px; color: var(--dim); }

.stepper { display: flex; align-items: center; gap: 6px; flex: none; }

.stepper button {
  width: 34px;
  height: 34px;
  border-radius: 10px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  font-size: 17px;
  font-weight: 700;
  line-height: 1;
  display: grid;
  place-items: center;
  /* The chip stays 34px; the touch target grows to 44 without moving a
     pixel of the layout. These get tapped repeatedly with a thumb. */
  position: relative;
}

.stepper button::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 44px;
  height: 44px;
  transform: translate(-50%, -50%);
}

.stepper .val {
  min-width: 30px;
  text-align: center;
  font-size: 16px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.chips { display: flex; flex-wrap: wrap; gap: 8px; }

.chip {
  padding: 10px 14px;
  border-radius: 11px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  font-size: 14px;
  font-weight: 600;
  color: var(--muted);
}

.chip.on { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }

.opt {
  display: block;
  width: 100%;
  text-align: left;
  padding: 15px;
  border-radius: var(--radius);
  background: var(--surface-2);
  border: 1px solid var(--line);
}

.opt + .opt { margin-top: 9px; }
.opt .t { font-size: 16px; font-weight: 700; display: flex; justify-content: space-between; gap: 10px; }
.opt .t em { font-style: normal; color: var(--muted); font-weight: 600; font-size: 14px; }
.opt .b { font-size: 13px; color: var(--muted); margin-top: 3px; }
.opt.on { border-color: var(--accent); background: rgba(196, 244, 58, 0.08); }
.opt.on .t { color: var(--accent); }

textarea.io {
  width: 100%;
  min-height: 110px;
  border-radius: 12px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  color: var(--text);
  padding: 12px;
  font-size: 13px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  resize: vertical;
}

input.text {
  width: 100%;
  height: var(--tap);
  border-radius: 12px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  padding: 0 14px;
  font-size: 15px;
}

/* Every focusable thing, not an enumerated list of classes. The old rule
   named .btn and .chip only, which left the move rungs, the tab bar, the
   quota steppers, the preset options and the switches with no visible focus
   at all — so anyone navigating by keyboard or switch control could not see
   where they were. A blanket rule also cannot drift as new controls land. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 6px;
}

input.text:focus, textarea.io:focus {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ----------------------------------------------------------------- tabbar */

.tabbar {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: 0;
  width: 100%;
  max-width: var(--shell);
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 2px;
  padding: 8px 10px calc(env(safe-area-inset-bottom, 0px) + 8px);
  background: rgba(11, 12, 14, 0.92);
  backdrop-filter: blur(12px);
  border-top: 1px solid var(--line);
  z-index: 30;
}

.tab {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 7px 0;
  border-radius: 11px;
  color: var(--dim);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.01em;
}

.tab svg { width: 21px; height: 21px; fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
.tab.on { color: var(--accent); }

/* --------------------------------------------------------------- log sheet */

.sheet-wrap {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: rgba(5, 6, 7, 0.72);
  animation: fade 150ms ease-out;
}

@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes slide { from { transform: translateY(18px); } to { transform: none; } }

.sheet {
  width: 100%;
  max-width: var(--shell);
  background: var(--surface);
  border-radius: 24px 24px 0 0;
  border-top: 1px solid var(--line);
  padding: 10px 18px calc(env(safe-area-inset-bottom, 0px) + 20px);
  animation: slide 200ms cubic-bezier(0.2, 0.8, 0.3, 1);
  max-height: 92dvh;
  overflow-y: auto;
}

@media (prefers-reduced-motion: reduce) {
  .sheet, .sheet-wrap { animation: none; }
}

.grabber {
  width: 38px;
  height: 4px;
  border-radius: 99px;
  background: var(--line);
  margin: 0 auto 16px;
}

.sheet h2 {
  font-size: 21px;
  font-weight: 800;
  letter-spacing: -0.02em;
}

.sheet .sub { font-size: 13px; color: var(--muted); margin-top: 2px; }

/* ------------------------------------------------------------ week cleared */

/* A week is the one thing in this product nobody can see while they are
   inside it, so the screen that finally shows it leads with the number and
   lets everything else be small. The type scale does the arguing: the count
   is enormous, the promise is a sentence, and the detail rows are a
   receipt. */
.sheet.recap .recap-head {
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
}

.sheet.recap .recap-big {
  font-size: 52px;
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.05;
  margin-top: 6px;
}

.sheet.recap .recap-big span {
  font-size: 18px;
  font-weight: 700;
  color: var(--muted);
  letter-spacing: -0.01em;
}

.sheet.recap .recap-say {
  font-size: 15px;
  font-weight: 600;
  line-height: 1.4;
  margin-top: 10px;
}

.sheet.recap .recap-rows {
  margin-top: 18px;
  border-top: 1px solid var(--line);
}

/* Label left, fact right, and the fact is allowed to wrap onto its own line
   on a narrow phone rather than being squeezed into an ellipsis. A number
   somebody just earned is not a detail to truncate. */
.sheet.recap .recap-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 14px;
  padding: 10px 0;
  border-bottom: 1px solid var(--line);
  font-size: 13px;
}

.sheet.recap .recap-row .k { color: var(--muted); flex: 0 1 auto; }
.sheet.recap .recap-row .v {
  font-weight: 700;
  text-align: right;
  flex: 1 1 auto;
  min-width: 0;
}

/* The part of the muscle, directly under the muscles it credits. Given the
   accent rather than another grey so the two lines read as two different
   facts rather than one wrapped sentence. */
.sheet .hits-line {
  font-size: 13px;
  font-weight: 600;
  color: var(--accent-soft);
  margin-top: 3px;
}

.big-stepper {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  margin: 18px 0 6px;
}

.big-stepper button {
  width: 62px;
  height: 62px;
  flex: none;
  border-radius: 18px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  font-size: 27px;
  font-weight: 300;
  line-height: 1;
  display: grid;
  place-items: center;
}

.big-stepper .reps { text-align: center; flex: 1; }
.big-stepper .reps .n {
  font-size: 46px;
  font-weight: 800;
  letter-spacing: -0.04em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.big-stepper .reps .u { font-size: 12px; color: var(--dim); text-transform: uppercase; letter-spacing: 0.1em; font-weight: 700; }

/* The calibration count is typed rather than only tapped: from empty, the
   +/- pair would mean thirty taps for somebody who did thirty reps. An
   input inherits none of the page's type, so it is restated here. */
input.n {
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font-family: inherit;
  text-align: center;
  -webkit-appearance: none;
  appearance: none;
}

input.n::placeholder { color: var(--line); opacity: 1; }
input.n:focus { outline: none; }
/* The ring belongs around the whole number block, not the bare glyph. */
.big-stepper .reps:focus-within {
  outline: 2px solid var(--accent);
  outline-offset: 8px;
  border-radius: 12px;
}

.big-stepper button[disabled] { opacity: 0.35; }

/* As many equal columns as there are buttons, rather than three.

   It was three, and three was right for as long as every move took the same
   three answers. One move now refuses All Out, and a fixed three-column
   grid rendered its two buttons at two thirds width with a dead column on
   the right - which reads as a missing button, which is exactly the
   impression the safety note underneath is there to prevent. */
.effort { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 8px; margin-top: 8px; }

.effort button {
  padding: 12px 6px;
  border-radius: 12px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  text-align: center;
}

.effort .e-l { font-size: 14px; font-weight: 700; }
.effort .e-n { font-size: 10.5px; color: var(--dim); margin-top: 2px; line-height: 1.3; }
.effort button.on { border-color: var(--accent); background: rgba(196, 244, 58, 0.1); }
.effort button.on .e-l { color: var(--accent); }

.swap {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 12px 0;
  margin-top: 4px;
  border-top: 1px solid var(--line);
  color: var(--muted);
  font-size: 14px;
  font-weight: 600;
}

/* ------------------------------------------------------------------ toast */

.toast {
  position: fixed;
  left: 50%;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 88px);
  transform: translateX(-50%);
  z-index: 70;
  background: var(--accent);
  color: var(--accent-ink);
  padding: 12px 18px;
  border-radius: 999px;
  font-size: 14px;
  font-weight: 700;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
  animation: pop 200ms cubic-bezier(0.2, 0.9, 0.3, 1);
  max-width: 88vw;
  text-align: center;
  /* It floats over the log button, so taps must pass straight through it. */
  pointer-events: none;
}

/* SAME CHANNEL, STRONGER RESPONSE. A set that clears a target and a set
   that clears a WEEK are both reported by this one element - no second
   banner, nothing to dismiss, and a man can read either and immediately
   log another set or put the phone down.

   What escalates is weight, not mechanism: the target state gets a ring
   and a little more room, and the week state gets the full-width treatment
   with the headline on its own line. The week state is the only one of the
   three that happens once a week, so it is the only one allowed to be
   unmissable. */
/* The volume comparison on Stats: three numbers on one line, with the unit
   said once underneath so none of them has to carry a label of its own. */
.vol-compare {
  margin: 10px 2px 0;
  font-size: 13.5px;
  color: var(--muted);
  line-height: 1.5;
}
.vol-compare b { color: var(--text); font-weight: 700; }
.vol-compare span {
  display: block;
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--dim);
  margin-top: 2px;
}

.toast.target {
  padding: 13px 20px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5), 0 0 0 3px rgba(196, 244, 58, 0.22);
}

.toast.week {
  padding: 15px 22px;
  font-size: 15px;
  letter-spacing: 0.01em;
  max-width: 92vw;
  box-shadow: 0 14px 40px rgba(0, 0, 0, 0.6), 0 0 0 5px rgba(196, 244, 58, 0.3);
}

/* The last animation in the app without a reduced-motion answer. Fourteen
   others have one; this was simply missed. The toast still appears, it just
   stops springing in. */
@media (prefers-reduced-motion: reduce) {
  .toast { animation: none; }
}

@keyframes pop {
  from { opacity: 0; transform: translate(-50%, 10px) scale(0.96); }
  to { opacity: 1; transform: translate(-50%, 0) scale(1); }
}

/* ------------------------------------------------------------- onboarding */

.onb {
  flex: 1;
  display: flex;
  flex-direction: column;
  padding: calc(env(safe-area-inset-top, 0px) + 28px) 22px calc(env(safe-area-inset-bottom, 0px) + 22px);
}

.onb .body { flex: 1; }

.onb .kicker {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
}

.onb h1 {
  font-size: 33px;
  font-weight: 800;
  letter-spacing: -0.035em;
  line-height: 1.08;
  margin: 12px 0 14px;
}

.onb p { color: var(--muted); font-size: 15px; line-height: 1.55; }
.onb p + p { margin-top: 12px; }
.onb .lede { color: var(--text); font-size: 17px; }

.pitch { margin: 22px 0; }
.pitch li {
  list-style: none;
  padding: 11px 0;
  border-bottom: 1px solid var(--line);
  font-size: 15px;
  display: flex;
  gap: 11px;
  align-items: flex-start;
}
.pitch { padding: 0; }
.pitch li b { color: var(--text); font-weight: 600; }
.pitch li span { color: var(--accent); font-weight: 700; flex: none; }

.dots { display: flex; gap: 6px; justify-content: center; margin-bottom: 16px; }
.dots i { width: 6px; height: 6px; border-radius: 99px; background: var(--line); }
.dots i.on { background: var(--accent); width: 18px; }

.anchor-opt {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 13px 14px;
  border-radius: 12px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  text-align: left;
}

.anchor-opt + .anchor-opt { margin-top: 8px; }
.anchor-opt .t { font-size: 15px; font-weight: 600; }
.anchor-opt .h { font-size: 12.5px; color: var(--dim); }
.anchor-opt.on { border-color: var(--accent); background: rgba(196, 244, 58, 0.08); }
.anchor-opt .tick { width: 22px; height: 22px; }
.anchor-opt.on .tick { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }

/* ------------------------------------------------------------------ coach */

.coach-av {
  flex: none;
  display: grid;
  place-items: center;
  border-radius: 999px;
  background: linear-gradient(145deg, var(--accent), #9dcc23);
  color: var(--accent-ink);
  font-weight: 800;
  letter-spacing: -0.02em;
  box-shadow: 0 0 0 1px rgba(196, 244, 58, 0.28), 0 6px 18px rgba(196, 244, 58, 0.14);
}

.coach-hero { display: flex; justify-content: center; margin-bottom: 18px; }

.coach-line {
  display: flex;
  gap: 11px;
  align-items: flex-start;
  margin-top: 18px;
}

.coach-line.tight { margin-top: 10px; }
.coach-line.tight .coach-av { visibility: hidden; }
.coach-line.tight .coach-name { display: none; }

.coach-bubble {
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 4px 15px 15px 15px;
  padding: 12px 14px;
  flex: 1;
  min-width: 0;
}

.coach-line.tight .coach-bubble { border-radius: 15px; }

.coach-name {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 4px;
}

.coach-text { font-size: 14.5px; line-height: 1.55; color: var(--muted); }
.coach-text b { color: var(--text); font-weight: 600; }

/* ----------------------------------------------------------------- alerts */

.alert {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 16px 18px;
  margin-bottom: 12px;
}

.alert .alert-head {
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -0.01em;
  margin-bottom: 5px;
}

.alert .small { color: var(--muted); }

.alert.risk {
  border-color: #4a3320;
  background: linear-gradient(180deg, rgba(255, 107, 74, 0.11), transparent 70%), var(--surface);
}

.alert.risk .alert-head { color: var(--bad); }

.alert.coach-alert { padding-top: 4px; }
.alert.coach-alert .coach-line { margin-top: 14px; }

/* ------------------------------------------------------------ hold timer */

.btn.timer {
  margin-top: 14px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  color: var(--text);
  min-height: var(--tap);
  font-size: 15px;
}

.btn.timer.running { border-color: var(--accent); color: var(--accent); }

.btn.timer .pulse {
  width: 9px;
  height: 9px;
  border-radius: 999px;
  background: currentColor;
  animation: beat 1s ease-in-out infinite;
}

@keyframes beat { 50% { opacity: 0.25; } }

@media (prefers-reduced-motion: reduce) {
  .btn.timer .pulse { animation: none; }
}

/* -------------------------------------------------------- install steps */

.install-step {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  padding: 11px 0;
  border-bottom: 1px solid var(--line);
  font-size: 14.5px;
  color: var(--muted);
  line-height: 1.5;
}

.install-step:last-child { border-bottom: 0; padding-bottom: 0; }
.install-step:first-child { padding-top: 0; }
.install-step b { color: var(--text); font-weight: 600; }

.install-step .num {
  flex: none;
  width: 23px;
  height: 23px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--accent-ink);
  display: grid;
  place-items: center;
  font-size: 12px;
  font-weight: 800;
}

.ios-share {
  width: 15px;
  height: 15px;
  vertical-align: -2px;
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

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

.advanced { margin-top: 14px; }

.advanced summary {
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
  padding: 8px 0;
  list-style: none;
}

.advanced summary::-webkit-details-marker { display: none; }
.advanced summary::before { content: '+ '; color: var(--dim); }
.advanced[open] summary::before { content: '– '; }

/* --------------------------------------------------- walkthrough footer */

.toast.high { bottom: calc(env(safe-area-inset-bottom, 0px) + 160px); }

.dots-gap { height: 6px; margin-bottom: 16px; }

/* The onboarding advance control, pinned so it is never hunted for.

   Sticky keeps it in flow: on a short step it simply sits where it always
   did, and on a long one it rides the bottom of the screen while the
   content scrolls underneath. The background is opaque with a short fade
   above it so text passing behind does not appear to collide with the
   button. Bottom padding on the body means the last line of a step can
   always be scrolled clear of it rather than trapped behind. */
.onb-actions {
  position: sticky;
  bottom: calc(env(safe-area-inset-bottom, 0px) - 4px);
  padding-top: 14px;
  margin-top: 18px;
  background: var(--bg);
  z-index: 5;
}

.onb-actions::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 100%;
  height: 22px;
  background: linear-gradient(rgba(11, 12, 14, 0), var(--bg));
  pointer-events: none;
}

.onb .body { padding-bottom: 8px; }

/* A disabled advance button has to look disabled at a glance - the whole
   point of pinning it is that somebody always knows where they stand with
   it. 0.4 alone reads as "dim" on a dark screen; the flat background and
   the default cursor say "not yet" properly. */
.onb-actions .btn:disabled {
  opacity: 0.45;
  background: var(--surface-2);
  color: var(--muted);
  cursor: default;
}

.onb-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 10px;
  min-height: 30px;
}

.link.muted-link { color: var(--dim); }

.dots i.done { background: var(--dim); }

.btn:disabled { opacity: 0.4; }

/* ------------------------------------------------------------ the licence */

/* A paywalled rung is not the same as a broken one: it stays tappable and
   reads as an offer, where a move you lack the gear for is simply dimmed. */
.rung.paywalled {
  opacity: 1;
  border-color: #3d4526;
  background: rgba(196, 244, 58, 0.045);
}

.rung.paywalled .nm { color: var(--muted); }
.rung.paywalled .lv { background: transparent; font-size: 14px; }
.rung.paywalled .rp { color: var(--accent); font-weight: 700; }

.chip.chip-locked { color: var(--dim); border-style: dashed; }

.streak-pill.trial-pill {
  border-color: #3d4526;
  background: rgba(196, 244, 58, 0.1);
  color: var(--accent);
  cursor: pointer;
}

.streak-pill.trial-pill b { color: var(--accent); }

.locked-stat, .locked-panel {
  display: block;
  width: 100%;
  text-align: left;
  cursor: pointer;
}

.locked-panel { border-style: dashed; }
.locked-panel .alert-head { font-size: 16px; font-weight: 700; margin: 10px 0 5px; }
.locked-panel .small { color: var(--muted); }
.lock-mark { font-size: 20px; }

.unlock {
  border-color: #3d4526;
  background:
    radial-gradient(120% 100% at 100% 0%, rgba(196, 244, 58, 0.1), transparent 62%),
    var(--surface);
}

.unlock-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
}

.unlock-title { font-size: 18px; font-weight: 800; letter-spacing: -0.02em; }

.unlock-price {
  flex: none;
  font-size: 22px;
  font-weight: 800;
  color: var(--accent);
  letter-spacing: -0.02em;
}

.unlock-list { margin: 16px 0 18px; padding: 0; list-style: none; }

.unlock-list li {
  display: flex;
  gap: 10px;
  padding: 7px 0;
  font-size: 14.5px;
  color: var(--muted);
  line-height: 1.45;
}

.unlock-list li span { color: var(--accent); flex: none; font-weight: 700; }

.key-row { display: flex; gap: 8px; margin-top: 12px; }
.key-row .text { flex: 1; min-width: 0; }
.key-row .btn { width: auto; flex: none; padding: 0 18px; }

/* -------------------------------------------------- how to do the move */

.howto {
  margin: 14px 0 4px;
  padding: 14px;
  border-radius: 12px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  font-size: 14px;
  line-height: 1.55;
  color: var(--muted);
}

.howto-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--dim);
  margin-bottom: 6px;
}

.howto .fail { margin-top: 8px; color: var(--warn); }

.closer {
  margin: 12px 0 0;
  padding: 11px 14px;
  border-radius: 12px;
  background: rgba(196, 244, 58, 0.09);
  border: 1px solid #3d4526;
  color: var(--accent);
  font-size: 14.5px;
  font-weight: 600;
  text-align: center;
}

/* ----------------------------------------------------- the coached set */

.sheet.coached { padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 26px); }

.coached-head {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 22px;
}

.coached-name {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--accent);
}

.coached-move { font-size: 17px; font-weight: 700; letter-spacing: -0.01em; }

/* The line he is saying. Big enough to read at arm's length, mid-set,
   upside down on a floor — which is why the lines are short. */
.cheer-line {
  min-height: 116px;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  font-size: 31px;
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.12;
  text-wrap: balance;
}

.cheer-line.pop { animation: cheerpop 340ms cubic-bezier(0.2, 0.9, 0.3, 1); }

@keyframes cheerpop {
  from { opacity: 0; transform: translateY(8px) scale(0.97); }
  to { opacity: 1; transform: none; }
}

.cheer-clock {
  text-align: center;
  margin-top: 8px;
  font-variant-numeric: tabular-nums;
}

.cheer-clock span {
  font-size: 46px;
  font-weight: 800;
  letter-spacing: -0.04em;
  color: var(--accent);
  line-height: 1;
}

.cheer-clock em {
  display: block;
  font-style: normal;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--dim);
  margin-top: 6px;
}

@media (prefers-reduced-motion: reduce) {
  .cheer-line.pop { animation: none; }
}

[hidden] { display: none !important; }

/* The Week screen's own headline. It opened on a bare date range, which
   labels the screen without ever saying what it is for - and what it is for
   is the single clearest proof the app is doing something. */
.week-head {
  font-size: 26px;
  font-weight: 800;
  letter-spacing: -0.02em;
  margin-top: 6px;
}

.week-sub {
  font-size: 14.5px;
  line-height: 1.5;
  color: var(--muted);
  margin-top: 6px;
}

.week-sub b { color: var(--accent-soft); }

/* The mission line carries the weight now, so it is bigger than the old
   explanatory sub it replaced and reads as an instruction rather than a
   caption. */
.week-sub { font-size: 15.5px; color: var(--text); }

/* Volume and dates on one quiet line underneath. Deliberately smaller than
   the mission above it: the sets are real work and worth seeing, they are
   simply not the thing being finished. */
.week-vol {
  font-size: 13px;
  line-height: 1.4;
  color: var(--muted);
  margin-top: 4px;
}
.week-vol b { color: var(--text); font-weight: 700; }

/* ------------------------------------------------------------------ goals */

/* One tap changes what the whole week looks like, so these are full-width
   rows with room for a sentence rather than chips. Somebody choosing between
   "Glutes" and "Wider back" is not choosing between two words, they are
   choosing what the app does with five sets a week, and the blurb is what
   tells them that. */
.goals { display: flex; flex-direction: column; gap: 8px; }

.goal {
  width: 100%;
  text-align: left;
  padding: 12px 14px;
  border-radius: 14px;
  border: 1px solid var(--line);
  background: var(--surface);
}

.goal .t { font-size: 15px; font-weight: 700; }
.goal .b { font-size: 12.5px; color: var(--dim); margin-top: 3px; line-height: 1.45; }

.goal.on {
  border-color: var(--accent);
  background: linear-gradient(180deg, rgba(196, 244, 58, 0.07), transparent), var(--surface);
}

.goal.on .t { color: var(--accent); }
.goal.on .b { color: var(--muted); }

/* A quota the goal has moved. Accented so the five muscles that gave up a
   set are as visible as the one that gained them - the trade is the honest
   part of this feature and hiding half of it would make the boost look free. */
.stepper .val.shifted { color: var(--accent-soft); font-weight: 700; }

/* The badge on the Today card when the move you have been handed is one your
   goal asked for. Small, because it is a confirmation rather than news - but
   present, because a goal that silently rearranges the week is
   indistinguishable from an app that ignored it. */
.why .goal-flag {
  margin-left: 8px;
  padding: 2px 7px;
  border-radius: 99px;
  background: rgba(196, 244, 58, 0.12);
  color: var(--accent);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.02em;
  white-space: nowrap;
}

/* Mobility and prep: present, findable, and visibly not training.

   It sits under every muscle group and is toned down rather than up - a
   dimmer heading, a flatter card, no level chips. The whole product rests
   on a set meaning something, so the shelf that earns no sets must not look
   like the shelves that do. */
.prep-title { color: var(--dim); }

.prep-card { border-style: dashed; }

.rung.prep {
  grid-template-columns: 1fr auto;
  opacity: 0.92;
}

.rung.prep .nm { font-weight: 600; }

/* The end of a ladder, explained.

   A note rather than a banner. It appears under the last rung of a ladder
   somebody has run out of, and it is telling them something true about
   their setup - not selling them anything - so it is styled like the rest
   of the muted copy on the screen with one rule above it to separate it
   from the buttons. */
.ceiling {
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
  line-height: 1.5;
}

.ceiling b { color: var(--text); }

/* ---------------------------------------------------- the Moves tab, v52

   Two faces and one navigation bar. The library used to be the only face
   and it measured twelve and a half phone screens; the mode switch and the
   shelf bar exist so neither question - "where am I" and "what is in here"
   - has to be answered by scrolling past the other. */

.modes {
  display: flex;
  gap: 4px;
  padding: 4px;
  margin-bottom: 12px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 12px;
}

.mode {
  flex: 1;
  padding: 9px 10px;
  border-radius: 9px;
  font-size: 13px;
  font-weight: 600;
  color: var(--dim);
  background: transparent;
}

.mode.on {
  background: var(--surface-2);
  color: var(--text);
}

/* The environment bar. Closed it is one row: a label, what is currently
   on, and a caret. The summary is the part that has to give way, so it is
   the only flexible column and it ellipsizes - min-width:0 because a flex
   item will not shrink below its content without it, and without that the
   row pushes the caret off a 360px screen instead of truncating. */
.envbar {
  margin-bottom: 12px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 12px;
}

.env-head {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 11px 13px;
  background: transparent;
  text-align: left;
}

.env-title {
  flex: none;
  font-size: 13px;
  font-weight: 700;
  color: var(--text);
}

.env-sum {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: 12.5px;
  color: var(--dim);
}

.env-caret {
  flex: none;
  width: 16px;
  height: 16px;
  fill: none;
  stroke: var(--dim);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform .15s ease;
}

.envbar.open .env-caret { transform: rotate(180deg); }

.envbar.open .chips { padding: 0 13px 4px; }

.env-note { margin: 0; padding: 10px 13px 12px; }

@media (prefers-reduced-motion: reduce) {
  .env-caret { transition: none; }
}

/* Sticky, because a jump bar that scrolls away is a table of contents
   rather than navigation. It sits under the topbar, which is sticky too. */
.shelfbar {
  position: sticky;
  top: 52px;
  z-index: 4;
  display: flex;
  gap: 6px;
  overflow-x: auto;
  scrollbar-width: none;
  padding: 8px 0;
  margin-bottom: 4px;
  background: var(--bg);
}

.shelfbar::-webkit-scrollbar { display: none; }

.shelf-jump {
  flex: none;
  padding: 7px 12px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--dim);
  font-size: 12.5px;
  font-weight: 600;
  white-space: nowrap;
}

/* One ladder, one row. Same skeleton as .rung deliberately: this is the
   same information at a different altitude, and two different-looking
   lists of exercises would be two things to learn. */
.lvl {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 11px 12px;
  border-radius: 12px;
  text-align: left;
  border: 1px solid transparent;
}

.lvl + .lvl { margin-top: 4px; }

.lvl .lv {
  width: 26px;
  height: 26px;
  flex: none;
  border-radius: 8px;
  background: var(--accent);
  color: var(--accent-ink);
  display: grid;
  place-items: center;
  font-size: 12px;
  font-weight: 700;
}

.lvl .nm { flex: 1; font-size: 15px; font-weight: 600; }

.lvl .hits {
  display: block;
  margin-top: 1px;
  font-size: 11.5px;
  font-style: normal;
  font-weight: 400;
  line-height: 1.3;
  color: var(--dim);
}

.lvl .rp {
  font-size: 12px;
  color: var(--dim);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* The two states that carry no rung, and they must not look alike: one
   means "you have not asked for this", the other means "you asked and your
   room cannot supply it". Both are quieter than a trainable row; only the
   blocked one keeps a live colour on what it needs. */
.lvl.untracked,
.lvl.blocked { opacity: 0.72; }

.lvl.untracked .nm { font-weight: 500; }
.lvl.blocked .rp { color: var(--warn); }

.lvl-ceiling {
  margin: 6px 0 4px 38px;
  padding-top: 0;
  border-top: 0;
}

.level-card { padding-top: 10px; padding-bottom: 10px; }

/* Search: one input, and results that say where each move lives. */
.searchpane {
  position: sticky;
  top: 52px;
  z-index: 4;
  padding: 8px 0 10px;
  background: var(--bg);
}

.search-input {
  width: 100%;
  padding: 12px 14px;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--text);
  font-size: 15px;
}

.search-input::placeholder { color: var(--dim); }

.search-input:focus {
  outline: none;
  border-color: var(--accent-soft);
}

/* One quiet word. An accessory is real training that is not a step up, and
   the screen said nothing about that until now - the explanation sits once
   per list rather than on every row. */
.acc-tag {
  display: inline-block;
  margin-left: 7px;
  padding: 1px 6px;
  border-radius: 6px;
  background: var(--surface-2);
  color: var(--dim);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.02em;
  vertical-align: 1px;
}

/* Same shape as the accessory tag, different colour, because they are the
   same kind of thing - not a rung - for two different reasons. */
.alt-tag { color: var(--accent-soft); border-color: rgba(196, 244, 58, 0.3); }

/* The one line that says why this move, directly above the button that
   logs it. Quiet enough not to compete with the exercise name above it,
   present enough to be read without looking for it. */
.why-line {
  margin: 12px 0 0;
  font-size: 13px;
  line-height: 1.45;
  color: var(--dim);
}
.why-line b { color: var(--text); font-weight: 600; }

/* The comeback greeting. Warm but not loud: it is an acknowledgement on
   the way past, not an event, and it sits above a week card it must not
   outrank. No accent fill - the accent on this screen belongs to the
   button that logs a set. */
.card.comeback { border-color: rgba(196, 244, 58, 0.22); }
.card.comeback h3 { margin: 0 0 6px; font-size: 18px; font-weight: 700; }
.card.comeback p { margin: 0; color: var(--dim); line-height: 1.45; }

/* The semantic marker in the progression circle: A for a stand-in, + for
   extra training. It sits in the same 26px box as a rung number so no list
   reflows, but it must not compete with one. A scanning eye should still
   see the numbers as the ladder and these as the things beside it, so the
   glyph is lighter in weight and set back a little. */
.lv .mark-sem {
  font-weight: 600;
  opacity: 0.75;
  line-height: 1;
}

.acc-note { margin-top: 10px; }

/* The locked fold. A third of the library is equipment somebody does not
   own; it stays in the document and stops being the price of admission. */
/* THE TODAY DISCLOSURE. Same mechanic as .locked-fold below, different
   job and therefore a different weight: that one hides equipment somebody
   does not own, this one holds instructions somebody may not need right
   now. So it is a solid rule rather than a dashed one, and the summary
   reads as a heading rather than as a thing that was taken away. */
.howto-fold { margin-top: 14px; border-top: 1px solid var(--line); }

.howto-fold > summary {
  list-style: none;
  cursor: pointer;
  padding: 11px 2px 9px;
  color: var(--muted);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.01em;
  -webkit-tap-highlight-color: transparent;
}
.howto-fold > summary::-webkit-details-marker { display: none; }
.howto-fold > summary::after {
  content: '\203A';
  float: right;
  transform: rotate(90deg);
  transition: transform 0.15s ease;
  color: var(--dim);
}
.howto-fold[open] > summary::after { transform: rotate(-90deg); }
.howto-fold > summary:active { color: var(--text); }
/* The block inside keeps its own spacing; the fold only supplies the rule
   and the handle above it. */
.howto-fold .cue { margin-top: 2px; }

.locked-fold { margin-top: 10px; }

.locked-fold > summary {
  list-style: none;
  cursor: pointer;
  padding: 9px 12px;
  border-radius: 10px;
  border: 1px dashed var(--line);
  color: var(--dim);
  font-size: 12.5px;
  font-weight: 600;
}

.locked-fold > summary::-webkit-details-marker { display: none; }

.locked-fold > summary::after {
  content: ' \203A';
  display: inline-block;
  margin-left: 6px;
  transform: rotate(90deg);
}

.locked-fold[open] > summary {
  margin-bottom: 6px;
  border-style: solid;
}

.locked-fold[open] > summary::after { transform: rotate(-90deg); }

/* v66: one commanding Today hero; the rest of the week is supporting
   information and completed challenges are on-demand, not permanent cards. */
.card.next:not(.chal.done) {
  border-color: rgba(196,244,58,.26);
  background: radial-gradient(ellipse at 5% 0%, rgba(196,244,58,.075), transparent 55%), var(--surface);
}
.card.next h2 {
  font-size: clamp(30px, 8vw, 40px);
  letter-spacing: -.045em;
  line-height: 1.06;
}
.card.next .why { margin-bottom: 16px; }
.card.next .btn-row > .btn { min-height: 54px; }
.challenge-summary { padding: 14px 16px; }
.challenge-summary summary {
  cursor: pointer;
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  list-style: none;
}
.challenge-summary summary::-webkit-details-marker { display: none; }
.challenge-summary summary > span { color: var(--dim); font-size: 12px; font-weight: 700; }
.challenge-summary summary > b { color: var(--text); font-size: 13px; }
.challenge-summary > p { margin-top: 10px; }
.card.weekbar { margin-top: 16px; }

/* v67: Today has one prominent card. Supporting information is understated. */
.today-compact-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 19px 4px;
  margin: 0 0 2px;
  border: 0;
  border-bottom: 1px solid var(--border, #292b32);
  border-radius: 0;
  background: transparent;
}
.today-compact-row b { display: block; font-size: 15px; color: var(--text, #f5f5f5); }
.today-compact-row span span, .today-compact-row > div > span {
  display: block; margin-top: 5px; color: var(--dim, #888a93); font-size: 13px;
}
.today-compact-row .link { flex: 0 0 auto; }
details.today-compact-row { display: block; }
details.today-compact-row > summary { display: flex; justify-content: space-between; gap: 12px; align-items: center; cursor: pointer; list-style: none; }
details.today-compact-row > summary::-webkit-details-marker { display: none; }
.today-row-chevron { color: var(--dim, #888a93); font-size: 13px; }
details.today-compact-row .logrow { padding: 14px 0; }
details.spots-fold.today-compact-row summary { padding: 0; }
details.spots-fold.today-compact-row .spots-list { margin-top: 16px; }
details.spots-fold.today-compact-row .spots-summary { min-width: 0; }

/* The week, reported without a card's worth of furniture: a divider row
   under the hero rather than a second headline. It stacks, so it opts out
   of the flex layout the other compact rows use. */
.today-week-row { display: block; }
.today-week-row .weekbar-head { align-items: center; margin-bottom: 12px; }
.today-week-row .weekbar-head b { display: block; font-size: 15px; color: var(--text); }
.today-week-row .weekbar-head > div > span {
  display: block;
  margin-top: 5px;
  font-size: 13px;
  color: var(--dim);
}
.today-week-row .bar { height: 5px; }
.today-week-row .pace { margin-top: 11px; font-size: 13px; line-height: 1.45; }
.today-week-row .goal-line { margin-top: 9px; font-size: 13px; }

/* v68: one logged set resolves the main action. Further training is a
   conscious choice, not another identical task served immediately. */
.postset-hero {
  background: radial-gradient(ellipse at 5% 0%, rgba(196,244,58,.085), transparent 62%), var(--surface);
  border-color: rgba(196,244,58,.28);
}
.postset-hero .target { margin-top: 8px; }
.postset-note {
  color: var(--dim, #999);
  font-size: 14px;
  line-height: 1.5;
  margin: 12px 0 18px;
}
.postset-actions { display: flex; flex-direction: column; gap: 10px; }
.postset-actions .btn { min-height: 50px; width: 100%; }
.postset-secondary {
  appearance: none;
  display: block;
  width: 100%;
  min-height: 48px;
  padding: 12px 10px;
  border: 1px solid var(--border, #303137);
  border-radius: 14px;
  background: transparent;
  color: var(--text, #eee);
  font: inherit;
  font-weight: 650;
  text-align: center;
  cursor: pointer;
}
.postset-secondary:focus-visible { outline: 2px solid var(--accent, #c4f43a); outline-offset: 3px; }

/* v69: text that exists only for a screen reader. Clipped rather than
   display:none or visibility:hidden, both of which take the node out of
   the accessibility tree and would silence the very thing it is for. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The quiet way back out of a continuation somebody chose. Sits under the
   hero's own action, deliberately below it in weight: stopping is always
   available, and never the loudest thing on the screen. */
.postset-stop {
  appearance: none;
  display: block;
  width: 100%;
  margin-top: 12px;
  padding: 11px 10px;
  border: 0;
  background: transparent;
  color: var(--dim, #999);
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  text-align: center;
  cursor: pointer;
  min-height: 44px;
}
.postset-stop:hover { color: var(--text, #eee); }
