/* ============================================================
   Coach Simon Says – the application stylesheet
   ------------------------------------------------------------
   Hand-written CSS consuming wwwroot/css/tokens.css (CLAUDE.md
   §5.2, §0 #10) – no build step, no framework, no dependencies.

   EVERY colour, type and spacing value comes from tokens.css.
   There are no literal colours in this file: if a value is
   missing, add a token rather than a hex here (issue #73).

   MOBILE-FIRST – a deliberate departure from the marketing site,
   which is desktop-first with max-width breakpoints. Clients log
   sessions on a phone at a rack, so the base rules below ARE the
   phone, and the two min-width blocks at the end widen them.
   See DESIGN_PRINCIPLES.md (Brand).

   Structure follows docs/mockups/platform-mockup-v0.1.html;
   colour and corners follow DESIGN_PRINCIPLES.md, which wins –
   the mock-up's warm neutrals and rounded corners are stale.
   ============================================================ */

/* ---------- 1. reset and base ---------- */

*, *::before, *::after { box-sizing: border-box; }

/* The hidden attribute must win. Author rules like .btn { display: inline-block }
   beat the user-agent's [hidden] { display: none } regardless of specificity, so
   without this a button marked hidden in the markup is still painted – which is
   how the logger's Next buttons were visible before its script ran. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  min-height: 100vh;
  background: var(--ink);
  color: var(--text-body);
  font-family: var(--body);
  font-size: var(--fs-base);
  line-height: 1.6;
  display: flex;
  flex-direction: column;
}

img, video { max-width: 100%; height: auto; }

a {
  color: var(--accent);
  text-decoration: none;
  border-bottom: 1px solid transparent;
}
a:hover { color: var(--accent-hover); border-bottom-color: currentColor; }

/* Brand focus treatment: 2px accent outline, offset. On :focus-visible
   only, so a mouse click does not draw a ring. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition: none !important; animation: none !important; }
}

/* ---------- 2. typography ---------- */

h1, h2, h3, h4, h5, h6, .h3, .h5, .h6, .display-4 {
  color: var(--text-heading);
  margin: 0 0 var(--sp-3);
  line-height: var(--lh-heading);
}

h1, .display-4 {
  font-family: var(--disp);          /* Michroma – single 400 weight */
  font-weight: 400;
  font-size: var(--fs-h1);
  letter-spacing: var(--ls-heading);
  text-transform: uppercase;
}

h2 {
  font-family: var(--body);
  font-weight: 700;
  font-size: var(--fs-h2);
  letter-spacing: var(--ls-heading);
}

h3, .h3 {
  font-family: var(--body);
  font-weight: 700;
  font-size: var(--fs-h3);
  letter-spacing: var(--ls-kicker);
  text-transform: uppercase;
}

h4, h5, .h5, h6, .h6 {
  font-family: var(--body);
  font-weight: 700;
  font-size: var(--fs-small);
  letter-spacing: .04em;
}

p { margin: 0 0 var(--sp-3); }
p:last-child { margin-bottom: 0; }

/* Standfirst under an h1. Capped in ch so the measure stays readable
   when the wrap is at its full 1140px. */
.lede { color: var(--text-muted); max-width: 62ch; }

small, .form-text { font-size: var(--fs-small); }

/* The mono kicker – the site's label voice. Used above headings, on
   data elements, and on every chip in the system. */
.kicker {
  display: block;
  font-family: var(--mono);
  font-size: var(--fs-kicker);
  letter-spacing: var(--ls-kicker);
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: var(--sp-2);
}
.kicker.muted { color: var(--text-muted); }

/* Data elements are mono – loads, reps, dates, IDs. */
.data, .mono { font-family: var(--mono); }

/* ---------- 3. the shell: banner, header, wrap, footer ---------- */

/* The amber DEV banner (CLAUDE.md §7). Amber is a greenfield-guide
   requirement and deliberately outside the palette – but it is set in
   the system's own mono voice, so it reads as part of the chrome
   rather than a strip pasted on top of it. */
.dev-banner {
  background: var(--warn);
  color: var(--warn-ink);
  font-family: var(--mono);
  font-size: var(--fs-kicker);
  font-weight: 600;
  letter-spacing: var(--ls-kicker);
  text-transform: uppercase;
  text-align: center;
  padding: var(--sp-1) var(--wrap-pad);
}

.wrap {
  width: 100%;
  max-width: var(--wrap-max);
  margin: 0 auto;
  padding-left: var(--wrap-pad);
  padding-right: var(--wrap-pad);
}

.site-header {
  border-bottom: 1px solid var(--line-dark);
  background: var(--ink);
}
.site-header .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  min-height: 56px;
}

/* SIMON·SAYS – Michroma, wide tracking, accent separator (mock-up). */
.wordmark {
  font-family: var(--disp);
  font-size: var(--fs-kicker);
  letter-spacing: .28em;
  color: var(--text-heading);
  text-transform: uppercase;
  white-space: nowrap;
  border-bottom: 0;
}
.wordmark:hover { color: var(--text-heading); }
.wordmark em { color: var(--accent); font-style: normal; }

.nav-toggle {
  display: inline-flex;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 40px;
  height: 40px;
  padding: 0 8px;
  background: none;
  border: 1px solid var(--line-dark);
  border-radius: var(--radius);
  cursor: pointer;
}
.nav-toggle span {
  display: block;
  height: 1px;
  background: var(--text-body);
}
.nav-toggle:hover { border-color: var(--accent); }

/* Mobile: the nav is a stacked panel revealed by the burger. */
.site-nav {
  display: none;
  flex-direction: column;
  padding-bottom: var(--sp-3);
  border-bottom: 1px solid var(--line-dark);
}
.site-nav.open { display: flex; }

.site-nav a,
.site-nav .nav-user,
.site-nav button {
  font-family: var(--mono);
  font-size: var(--fs-kicker);
  letter-spacing: var(--ls-kicker);
  text-transform: uppercase;
  color: var(--text-muted);
  padding: var(--sp-2) 0;
  background: none;
  border: 0;
  text-align: left;
  cursor: pointer;
}
.site-nav a:hover,
.site-nav button:hover { color: var(--accent); }
.site-nav a.current { color: var(--text-heading); }
.site-nav .nav-user { color: var(--text-heading); cursor: default; }
.site-nav form { margin: 0; }

main { flex: 1 0 auto; padding: var(--section-y-narrow) 0; }

.site-footer {
  border-top: 1px solid var(--line-dark);
  padding: var(--sp-4) 0;
  color: var(--text-muted);
  font-size: var(--fs-small);
}

/* ---------- 4. layout utilities ----------
   The scaffold's Bootstrap was removed at the reskin (issue #73): it is
   a light, rounded, blue-accented framework and the brand is dark,
   square and orange, so every component was being overridden. These are
   hand-written stand-ins for the handful of utility class names the
   existing markup already uses, so the reskin did not need a rewrite of
   every view. Same names, same behaviour, our values. */

.container {
  width: 100%;
  max-width: var(--wrap-max);
  margin: 0 auto;
  padding-left: var(--wrap-pad);
  padding-right: var(--wrap-pad);
}

.row {
  display: flex;
  flex-wrap: wrap;
  margin-left: calc(var(--grid-gap) / -2);
  margin-right: calc(var(--grid-gap) / -2);
  row-gap: var(--grid-gap);
}
.row > * {
  flex: 0 0 100%;
  max-width: 100%;
  padding-left: calc(var(--grid-gap) / 2);
  padding-right: calc(var(--grid-gap) / 2);
}
.row.g-2, .row.gap-2 { row-gap: var(--sp-2); }

/* Unprefixed columns apply at every width – the ones the markup uses
   for things that must stay side by side on a phone. */
.col-3  { flex: 0 0 25%;      max-width: 25%; }
.col-4  { flex: 0 0 33.3333%; max-width: 33.3333%; }
.col-5  { flex: 0 0 41.6667%; max-width: 41.6667%; }
.col-8  { flex: 0 0 66.6667%; max-width: 66.6667%; }
.col-12 { flex: 0 0 100%;     max-width: 100%; }

.d-flex { display: flex; }
.d-inline { display: inline; }
.flex-wrap { flex-wrap: wrap; }
.flex-grow-1 { flex-grow: 1; }
.gap-2 { gap: var(--sp-2); }
.justify-content-between { justify-content: space-between; }
.justify-content-center { justify-content: center; }
.align-items-center { align-items: center; }
.align-items-end { align-items: flex-end; }
.text-center { text-align: center; }
.w-100 { width: 100%; }

.mb-0 { margin-bottom: 0 !important; }
.mb-1 { margin-bottom: var(--sp-1); }
.mb-2 { margin-bottom: var(--sp-2); }
.mb-3 { margin-bottom: var(--sp-3); }
.mb-4 { margin-bottom: var(--sp-4); }
.mt-1 { margin-top: var(--sp-1); }
.mt-2 { margin-top: var(--sp-2); }
.mt-3 { margin-top: var(--sp-3); }
.mt-4 { margin-top: var(--sp-4); }
.ms-1 { margin-left: var(--sp-1); }
.p-2 { padding: var(--sp-2); }
.p-3 { padding: var(--sp-3); }
.pb-3 { padding-bottom: var(--sp-3); }

.border { border: 1px solid var(--line-dark); }
.border-2 { border-width: 2px; }
.border-top { border-top: 1px solid var(--line-dark); }
.border-bottom { border-bottom: 1px solid var(--line-dark); }

.list-unstyled { list-style: none; padding-left: 0; margin: 0; }

.text-muted, .text-secondary { color: var(--text-muted); }
.text-danger { color: var(--accent); }   /* the accent is the alarm voice */
.text-dark { color: var(--text-body); }  /* scaffold leftover, neutralised on dark */

/* ---------- 5. surfaces ---------- */

/* Flat, square, 1px-bordered. No shadow, no radius – the site ships
   zero border-radius (DESIGN_PRINCIPLES.md Brand). */
.card, .panel {
  background: var(--ink-soft);
  border: 1px solid var(--line-dark);
  border-radius: var(--radius);
  padding: var(--card-pad-narrow);
}
.card + .card, .panel + .panel { margin-top: var(--sp-3); }

/* Hover shifts the border only – never a glow. Interactive cards opt in. */
a.card:hover, .card.is-actionable:hover { border-color: var(--accent); }

/* Dashed marks "nothing here yet" (mock-up's coming-soon treatment). */
.card.is-empty {
  border-style: dashed;
  color: var(--text-muted);
  text-align: center;
}

.card-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--sp-2);
  margin-bottom: var(--sp-2);
}
.card-title {
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .02em;
  color: var(--text-heading);
}

/* A page section: heading, optional sub, then content. */
.section + .section { margin-top: var(--section-y-narrow); }
.section-head { margin-bottom: var(--sp-3); }
.section-head .sub { color: var(--text-muted); font-size: var(--fs-small); }
.section-head .sub b { color: var(--text-heading); font-weight: 600; }

/* ---------- 6. forms ---------- */

.form-label {
  display: block;
  font-family: var(--mono);
  font-size: var(--fs-kicker);
  letter-spacing: var(--ls-kicker);
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--sp-1);
}

.form-control, .form-select {
  display: block;
  width: 100%;
  background: var(--ink);
  color: var(--text-heading);
  border: 1px solid var(--line-dark);
  border-radius: var(--radius);
  font-family: var(--body);
  font-size: var(--fs-base);
  padding: 11px 12px;
}
.form-control:hover, .form-select:hover { border-color: var(--text-muted); }
.form-control::placeholder { color: var(--text-muted); }
textarea.form-control { min-height: 96px; resize: vertical; }

/* Same floor as .btn-sm, and for the same reason: every field on the
   programme builder is a -sm, and the builder is a touch screen (§5.6). */
.form-control-sm, .form-select.form-control-sm {
  padding: 7px 9px;
  font-size: var(--fs-small);
  min-height: 44px;
}

/* Numeric entry is a data element – mono, so a rack-side glance is
   unambiguous about 0 vs O and 1 vs l. */
.form-control[type="number"] { font-family: var(--mono); }

.form-select { appearance: none; padding-right: 32px; }

.form-text { display: block; color: var(--text-muted); margin-top: var(--sp-1); }

.form-check { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-2); }
.form-check-input {
  width: 20px;
  height: 20px;
  flex: none;
  accent-color: var(--accent);
  border-radius: var(--radius);
}
.form-check-label { color: var(--text-body); }

fieldset { border: 0; margin: 0; padding: 0; }
legend { padding: 0; }

/* ---------- 7. buttons ---------- */

.btn {
  display: inline-block;
  background: var(--ink-soft);
  color: var(--text-heading);
  border: 1px solid var(--line-dark);
  border-radius: var(--radius);
  font-family: var(--body);
  font-weight: 700;
  font-size: .8rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  padding: 13px 26px;
  cursor: pointer;
  text-align: center;
  transition: background .12s, border-color .12s, transform .12s;
}
.btn:hover { border-color: var(--accent); color: var(--text-heading); }
.btn:disabled, .btn[aria-disabled="true"] { opacity: .5; cursor: not-allowed; }

/* The accent button – hover darkens and lifts 1px, never glows. */
.btn-accent {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--white);
}
.btn-accent:hover {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
  color: var(--white);
  transform: translateY(-1px);
}

.btn-outline-secondary { background: none; }
.btn-outline-danger { background: none; border-color: var(--accent); color: var(--accent); }
.btn-outline-danger:hover { background: var(--accent-soft); }

/* 44px is the floor, not the height (DESIGN_PRINCIPLES.md §5.6). "Small"
   means a smaller font and tighter horizontal padding - it does not mean a
   target too small to hit, because on a touch device there is no such thing as
   a 37px button. Simon builds programmes on an iPad and clients log sessions
   on phones; nothing here is driven by a mouse. */
.btn-sm {
  padding: 8px 14px;
  font-size: var(--fs-kicker);
  min-height: 44px;
}

/* Was the worst target in the app: padding 0 and no border made it about
   21px tall, and one of its uses was REMOVE on a prescription row - a
   destructive action in a dense table, on a touch screen (§5.6). It still
   reads as a link; it is now big enough to hit. */
.btn-link {
  background: none;
  border: 0;
  color: var(--accent);
  padding: 0 4px;
  letter-spacing: var(--ls-kicker);
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.btn-link:hover { color: var(--accent-hover); }

.btn-row { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-top: var(--sp-3); }

/* ---------- 8. tables ---------- */

.table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-small);
}
.table th {
  font-family: var(--mono);
  font-size: var(--fs-kicker);
  letter-spacing: var(--ls-kicker);
  text-transform: uppercase;
  color: var(--text-muted);
  font-weight: 500;
  text-align: left;
}
.table th, .table td {
  padding: var(--sp-2);
  border-bottom: 1px solid var(--line-dark);
  vertical-align: top;
}
.table td { color: var(--text-body); }
/* BANDING, not hover. A hover tint was the table's only row-tracking aid and
   it never fires on the device this screen is used on (§5.6) - dead code
   pretending to be an affordance. Banding does the same job and is always
   there, which matters most on the builder's tables: six rows a session
   across twenty-plus sessions. */
.table tbody tr:nth-child(even) td { background: var(--ink-soft); }
.table-sm th, .table-sm td { padding: var(--sp-1) var(--sp-2); }

/* Wide tables scroll inside their own box, never the page. */
.table-scroll { overflow-x: auto; }

/* The exercise library. Cue notes are long rendered lists, and with automatic
   column sizing they starved the name column - names like "Alternating Jump
   Lunges / Overhead Frog Jumps (snatch-grip bar)" wrapped to one word per
   line. Fixed layout gives every column a floor the content cannot take back;
   min-width makes the table scroll inside .table-scroll on a phone rather
   than crushing all four columns into 312px. */
.table-exercises { table-layout: fixed; min-width: 760px; }
.table-exercises .c-name     { width: 26%; }
.table-exercises .c-category { width: 12%; }
.table-exercises .c-notes    { width: 50%; }
.table-exercises .c-actions  { width: 12%; }
/* break-word, not anywhere: only split a token that genuinely cannot fit. */
.table-exercises th, .table-exercises td { overflow-wrap: break-word; }
.table-exercises .c-name { color: var(--text-heading); }

/* The client list. Same answer as the exercise library one rule up, reached
   the same way: automatic column sizing let the columns bid against each
   other, the actions column took what it asked for and the name column was
   squeezed to about 100px - "Kagiso Molefe (DEMO)" came out over three lines
   and the rows were 230px tall.

   EVERY WIDTH IS MEASURED. Each one is the widest real content that column
   holds, rendered in the font and size it ships in, plus the 8px cell padding
   on each side:

     c-name     170px  "Thandeka Nkosi (DEMO)", Montserrat 400 at .9rem
                       (set at 200 - the headroom is for a longer name, and
                       it is where the disabled chip wraps to)
     c-trained   61px  "125d ago"; the header wraps to LAST / TRAINED at 59px
                       (set at 84 rather than 77 - this one has headroom)
     c-session  120px  the "12 of 14 sets" chip, Plex Mono at .75rem +.08em
     c-actions  398px  the four controls plus their 2px gaps

   THERE IS NO c-status, AND THAT IS THE SECOND DELIBERATE COLUMN REMOVAL ON
   THIS TABLE (Mich, 2026-10-01). It cost 98px to print a word that reads
   "active" for every client but a disabled one. Status now renders in the
   name cell and only when it is not active - the empty video slot's rule
   applied to a column: an exception is worth showing, a constant is not.
   Email went the same way and for the same kind of reason, one PR earlier.

   GOALS IS DELIBERATELY UNSPECIFIED, WHICH IS HOW IT TAKES THE SLACK (Mich,
   2026-10-01). Under table-layout: fixed the one column with no width gets
   the whole remainder, so it is prose that grows with the viewport rather
   than a fifth claim competing with the others. Measured against the tablet
   band in DESIGN_PRINCIPLES.md section 5.6, with status gone:

     1024pt (the floor of the band)  140px
     1080pt (iPad 10th gen)          196px
     1140px (--wrap-max, desktop)    256px
     1180pt (iPad Air, the ceiling)  296px

   ClientListColumnBudgetTests reads these numbers back out of this file and
   checks that they add up, because a column plan that over-subscribes its
   table is exactly the fault that produced the 230px rows and nothing else
   in the suite can see it.

   min-width keeps a phone scrolling inside .table-scroll rather than crushing
   the columns into 312px, and is set below the 976px an iPad gets at the
   bottom of the band so that device does not scroll. */
.table-clients { table-layout: fixed; min-width: 960px; }
.table-clients .c-name    { width: 200px; color: var(--text-heading); }
.table-clients .c-trained { width: 84px; }
.table-clients .c-session { width: 138px; }
.table-clients .c-actions { width: 414px; }
/* c-goals carries no width on purpose - see above. */
/* The status chip only ever appears on an exception, so it wraps under the
   name rather than competing with it for the line. */
.table-clients .c-name .chip { margin-top: var(--sp-1); }
/* break-word, not anywhere: only split a token that genuinely cannot fit. */
.table-clients th, .table-clients td { overflow-wrap: break-word; }

/* Simon's cue notes, rendered from his Markdown by CueNotesMarkup. */
.cue-notes { margin: 0; padding-left: 1.1em; list-style: disc; }
.cue-notes li { margin-bottom: var(--sp-1); }
.cue-notes li:last-child { margin-bottom: 0; }
.cue-notes strong { color: var(--text-heading); font-weight: 600; }
.cue-notes em { color: var(--text-body); }

/* ---------- 9. messages ---------- */

.alert {
  border: 1px solid var(--line-dark);
  border-left-width: 3px;
  border-radius: var(--radius);
  background: var(--ink-soft);
  padding: var(--sp-3);
  margin-bottom: var(--sp-3);
  font-size: var(--fs-small);
}
.alert-danger { border-left-color: var(--accent); }
.alert-success { border-left-color: var(--good); }
.alert-warning { border-left-color: var(--warn); }

.badge {
  display: inline-block;
  font-family: var(--mono);
  font-size: var(--fs-kicker);
  letter-spacing: var(--ls-kicker);
  text-transform: uppercase;
  border: 1px solid var(--line-dark);
  border-radius: var(--radius);
  padding: 3px 8px;
  color: var(--text-muted);
}

/* ---------- 10. the design system's own components ----------
   Structure from the v0.1 mock-up; colour and corners corrected to the
   brand (square, cool neutrals) per DESIGN_PRINCIPLES.md. */

/* Chips – one shape, several voices. */
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  font-family: var(--mono);
  font-size: var(--fs-kicker);
  letter-spacing: .08em;
  text-transform: uppercase;
  border: 1px solid var(--line-dark);
  border-radius: var(--radius);
  padding: 3px 8px;
  color: var(--text-muted);
}
.chip.is-accent { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
.chip.is-good   { border-color: var(--good);   color: var(--good); }
/* An exception worth noticing rather than an alert: a disabled client is a
   deliberate coach action, not something going wrong. */
.chip.is-warn   { border-color: var(--warn);   color: var(--warn); }

/* Set chips – one per programmed set; the reps go inside. */
.sets { display: flex; flex-wrap: wrap; gap: var(--sp-1); margin-top: var(--sp-2); }
.set {
  min-width: 34px;
  height: 30px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--ink);
  border: 1px solid var(--line-dark);
  border-radius: var(--radius);
  font-family: var(--mono);
  font-size: var(--fs-small);
  color: var(--text-muted);
}
.set.done { background: var(--accent); border-color: var(--accent); color: var(--white); }
.set.miss { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }

/* Plate progress – a loaded bar reads as progress at a glance. */
/* Wraps: a five-week block has more sessions than the mock-up's four
   lifts, and the row must not push the card wider than the phone. */
.plates { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-1); margin-top: var(--sp-2); }
.plates .bar { height: 2px; width: 26px; background: var(--text-muted); }
.plate {
  width: 9px;
  height: 22px;
  border: 1px solid var(--line-dark);
  background: var(--ink);
}
.plate.full { background: var(--accent); border-color: var(--accent); }
.plates .lbl {
  font-family: var(--mono);
  font-size: var(--fs-kicker);
  letter-spacing: .08em;
  color: var(--text-muted);
  margin-left: var(--sp-1);
}

/* A prescribed lift inside a session card: name and prescription on one
   line, set chips under it. The mock-up's session-card shape, which is
   also why the programme view is not a four-column table – that table
   was unreadable on the phone this screen is actually used on. */
.lift-row { padding-top: var(--sp-3); margin-top: var(--sp-3); border-top: 1px solid var(--line-dark); }
.lift-row:first-child { padding-top: 0; margin-top: 0; border-top: 0; }
.lift-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--sp-2);
  flex-wrap: wrap;
}
.lift {
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .02em;
  color: var(--text-heading);
}
.rx { font-family: var(--mono); font-size: var(--fs-small); color: var(--text-muted); }
.rx b { color: var(--text-heading); font-weight: 600; }
.lift-note { font-size: var(--fs-small); color: var(--text-muted); margin: var(--sp-1) 0 0; }

/* The block overview: a week heading, then one row per session. */
.week-head {
  font-family: var(--mono);
  font-size: var(--fs-kicker);
  letter-spacing: var(--ls-kicker);
  text-transform: uppercase;
  color: var(--text-muted);
  margin: var(--sp-4) 0 var(--sp-2);
}
.session-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
.session-item {
  background: var(--ink-soft);
  border: 1px solid var(--line-dark);
  border-radius: var(--radius);
  padding: var(--sp-2) var(--sp-3);
}
.session-item .name { color: var(--text-heading); font-weight: 600; }
.session-item .detail { display: block; font-size: var(--fs-small); color: var(--text-muted); }
/* A logged session recedes rather than disappearing – it is still the
   record of what was done. */
.session-item.is-logged { opacity: .55; }

/* Set-logging rows. On a phone each set is a labelled stack; from 560px
   it becomes the grid a table would have given, without a table's
   horizontal scroll at the rack. */
.set-log { border-top: 1px solid var(--line-dark); padding-top: var(--sp-3); margin-top: var(--sp-3); }
.set-log:first-of-type { border-top: 0; padding-top: 0; margin-top: 0; }
.set-log-no {
  font-family: var(--mono);
  font-size: var(--fs-kicker);
  letter-spacing: var(--ls-kicker);
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--sp-1);
}
.set-fields { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-2); }
.set-fields .wide { grid-column: 1 / -1; }

/* ---- the parts of a complex, one row each (#372) ----------------------

   REPLACES `.set-parts`, which was one box per part INSIDE the set row:
   auto-fit columns in a track sized for one two-character number. Fine for a
   clean and jerk, unusable for a four-part bar complex – and there was one
   tick for the whole set, so "the clean went up and the jerk did not" had no
   control to say it with.

   A PART ROW IS THE SET ROW'S GRID WITH THE NAME GIVEN A REAL COLUMN, so the
   reps box, the load column's width and the tick line up with the set above
   rather than drifting out of step with it. The tick keeps its 44px for the
   same reason it does everywhere else: it is the minimum touch target
   (DESIGN_PRINCIPLES section 5) and Simon's primary device is an iPad.

   INDENTED AND RULED ON THE LEFT, which is what makes four rows read as one
   movement with its parts rather than as four exercises – the one-screen
   superset's problem (#269) one level down. */
.part-row {
  display: grid;
  /* THE TICKS LINE UP WITH THE SET ROW'S, and the name column is sized to
     make that true rather than left to 1fr. The set row above is
     22px + 8.5rem + 4rem + 44px with three 8px gaps = 290px; this row is
     indented by sp-3 + sp-2 = 24px, so its own tracks have to come to 266px
     for the two ticks to sit in the same place. 18 + 116 + 64 + 44 + 24 is
     266, which is where 7.25rem comes from.

     A COLUMN OF TICKS THAT DOES NOT LINE UP READS AS SEPARATE CONTROLS,
     which is the opposite of what the indent is for: these are the parts of
     ONE movement. A long part name wraps instead, which costs a row some
     height and costs the grouping nothing. */
  grid-template-columns: 18px minmax(0, 7.25rem) minmax(0, 4rem) 44px;
  justify-content: start;
  max-width: 22rem;
  align-items: center;
  gap: var(--sp-2);
  margin-left: var(--sp-3);
  padding: var(--sp-1) 0 var(--sp-1) var(--sp-2);
  border-left: 1px solid var(--line-dark);
}
.part-n {
  font-family: var(--mono);
  font-size: var(--fs-kicker);
  color: var(--text-muted);
}
.part-name { overflow-wrap: anywhere; }

/* The second signal, so a done part never rests on the filled box alone -
   the same pairing the set row makes, in the one colour that means "this
   happened". */
.part-row:has(.tick input:checked) { border-left-color: var(--accent); }

/* The missed-or-skipped question, on its own line under the PART it is about
   rather than under the whole set. Accented because it is the one thing on
   the page waiting for an answer. */
.set-row.shortfall,
.part-row.shortfall {
  grid-template-columns: 2ch 1fr auto;
  align-items: center;
  border-left: 2px solid var(--accent);
  padding-left: var(--sp-2);
}

/* Answered intake: question above, answer below. A dl rather than a
   table because the pairs are read, not compared column-wise. */
.answers { margin: 0; }
.answers dt {
  font-family: var(--mono);
  font-size: var(--fs-kicker);
  letter-spacing: var(--ls-kicker);
  text-transform: uppercase;
  color: var(--text-muted);
}
.answers dd { margin: 0 0 var(--sp-3); color: var(--text-heading); }
.answers dd:last-child { margin-bottom: 0; }

/* Coach queue rows. */
.qitem { display: flex; flex-direction: column; gap: var(--sp-2); }
.qtop { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.qname { font-weight: 700; color: var(--text-heading); }
.qline { font-size: var(--fs-small); color: var(--text-muted); margin: 0; }
.qline b { color: var(--text-heading); font-weight: 600; }

/* Severity, written by the rules engine (CLAUDE.md §8). The vocabulary is
   fixed - low / medium / high / celebrate - so the escalation is built from
   weight rather than from four unrelated colours: the accent is meant to be
   the only colour with a voice, and celebrate is the one exception. */
.sev-low { }                                                   /* muted default */
.sev-medium { border-color: var(--text-muted); color: var(--text-heading); }
.sev-high { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
.sev-celebrate { border-color: var(--good); color: var(--good); }

/* A flag's context snapshot - stored as JSON text (CLAUDE.md §2.2) and shown
   raw, because it is the evidence the rule acted on. Scrolls in its own box. */
.context-json {
  background: var(--ink);
  border: 1px solid var(--line-dark);
  border-radius: var(--radius);
  padding: var(--sp-3);
  overflow-x: auto;
  font-family: var(--mono);
  font-size: var(--fs-small);
  color: var(--text-body);
  margin: 0;
}

/* Label/value pairs on a detail screen. */
.detail-list { margin: 0; }
.detail-list dt {
  font-family: var(--mono);
  font-size: var(--fs-kicker);
  letter-spacing: var(--ls-kicker);
  text-transform: uppercase;
  color: var(--text-muted);
}
.detail-list dd { margin: 0 0 var(--sp-2); color: var(--text-heading); }

/* Progressive disclosure in the builder - a closed summary keeps the add
   forms out of the way until wanted ("simple is better, add later"). */
details > summary {
  font-family: var(--mono);
  font-size: var(--fs-kicker);
  letter-spacing: var(--ls-kicker);
  text-transform: uppercase;
  color: var(--accent);
  cursor: pointer;
  padding: var(--sp-2) 0;
}
details > summary:hover { color: var(--accent-hover); }

hr { border: 0; border-top: 1px solid var(--line-dark); margin: var(--sp-4) 0; }

/* Video review frame strip. */
.frames { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-1); }
.frame {
  aspect-ratio: 3 / 4;
  border: 1px solid var(--line-dark);
  background: var(--ink);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding-bottom: var(--sp-1);
  font-family: var(--mono);
  font-size: var(--fs-kicker);
  color: var(--text-muted);
}
.frame.mark { border-color: var(--accent); color: var(--accent); }

/* ---------- 10b. the v0.3 vocabulary ----------
   Structure from docs/mockups/platform-mockup-v0.3.html, which was drawn
   after the site audit, so its palette already matches tokens.css.

   These exist to serve DESIGN_PRINCIPLES.md §5: the action at the top, the
   app filling in what it knows, explanation available rather than
   compulsory, and the summary always keeping a path to the full record. */

/* The screen opens on its subject, not its name (§5.1). Still an <h1>
   semantically - a screen with no h1 is worse for a screen reader than one
   with a small h1 - but at card-heading size, not display size. */
.ctx {
  font-family: var(--mono);
  font-size: var(--fs-kicker);
  letter-spacing: .11em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 0 0 var(--sp-1);
}
.screen-title {
  font-family: var(--body);
  font-weight: 700;
  font-size: 1.125rem;
  letter-spacing: .01em;
  line-height: 1.25;
  text-transform: none;
  color: var(--text-heading);
  margin: 0;
}
.screen-head { margin-bottom: var(--sp-3); }
.screen-title:focus { outline: none; }

/* Present for a screen reader, absent on screen. Used for the one <h1> on a
   screen whose visible headings all belong to sections. */
.visually-hidden {
  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 logger's exercise sections. Spacing between the parts of one exercise,
   and the sections themselves are shown one at a time by site.js when it
   runs - hidden is a plain attribute, so no CSS is required for the
   no-JavaScript case. */
.logger .ex, .logger .ex-end { display: flex; flex-direction: column; gap: var(--sp-3); }
.logger .ex + .ex, .logger .ex-end { margin-top: var(--section-y-narrow); }
/* .ex/.ex-end used to need their own [hidden] rules for the same reason; the
   global one above covers them now. */

/* Week progress: one square per expected session. Squares, not circles -
   the brand ships zero border-radius. */
.dots { display: flex; align-items: center; gap: var(--sp-1); flex-wrap: wrap; }
.dot { width: 8px; height: 8px; background: var(--line-dark); flex: none; }
.dot.on { background: var(--accent); }
.dot.good { background: var(--good); }

/* A card that wants attention. The accent edge is the whole treatment -
   no glow, no fill (DESIGN_PRINCIPLES.md Brand). */
.card.hot { border-left: 2px solid var(--accent); }

/* A prescribed lift with its resolved load on the right. */
.lift-line {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--sp-3);
  padding: var(--sp-2) 0;
  border-bottom: 1px solid var(--line-dark);
}
.lift-line:last-child { border-bottom: 0; }
.lift-line .nm { font-weight: 600; color: var(--text-heading); }
.lift-line .rx { font-family: var(--mono); font-size: var(--fs-kicker); color: var(--text-muted); display: block; }
.lift-line .kg { font-family: var(--mono); font-weight: 600; color: var(--accent); white-space: nowrap; }

/* The primary action of a screen: full width, unmissable at a rack. */
.btn-block { display: block; width: 100%; }
.btn-lg { padding: 15px; font-size: 1rem; }

/* Explanation that is available rather than compulsory (§5.3). */
.hint { font-size: var(--fs-kicker); color: var(--text-muted); margin: 0; }

/* A fold (§5.4). <details>/<summary> so it works with no JavaScript, and so
   the full record is always one tap away rather than gone. */
.fold {
  border-top: 1px solid var(--line-dark);
}
.fold > summary {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-3) 0;
  font-family: var(--body);
  font-weight: 600;
  font-size: var(--fs-small);
  letter-spacing: 0;
  text-transform: none;
  color: var(--text-muted);
  cursor: pointer;
  list-style: none;
}
.fold > summary::-webkit-details-marker { display: none; }
.fold > summary::after { content: "▾"; color: var(--text-muted); }
.fold[open] > summary::after { content: "▴"; }
.fold > summary:hover { color: var(--accent); }
.fold-body { padding-bottom: var(--sp-3); }

/* A fold-shaped row that is really a link to another screen. Same promise as
   a fold - the full record is one tap away - but the record lives on its own
   page rather than under this one. */
.fold-link {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-3) 0;
  border-top: 1px solid var(--line-dark);
  font-weight: 600;
  font-size: var(--fs-small);
  color: var(--text-muted);
}
.fold-link:hover { color: var(--accent); border-bottom-color: transparent; }

/* Quoted client words - their note, shown back to the coach as they wrote it. */
.note-quote {
  border-left: 2px solid var(--line-dark);
  background: var(--ink-raised);
  padding: var(--sp-2) var(--sp-3);
  font-size: var(--fs-small);
  color: var(--text-body);
  margin: 0;
}

/* Coach dashboard rows: a pip, who, and how long ago. */
.grouphead {
  font-family: var(--mono);
  font-size: var(--fs-kicker);
  letter-spacing: var(--ls-kicker);
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 0 0 var(--sp-2);
}
.cl {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) 0;
  border-bottom: 1px solid var(--line-dark);
}
.cl:last-child { border-bottom: 0; }
/* The whole row is the link. Neutralise the accent-link default so the row
   reads as a row; the name brightening on hover is the affordance. */
a.cl { color: inherit; border-bottom: 1px solid var(--line-dark); text-decoration: none; }
a.cl:last-child { border-bottom: 0; }
a.cl:hover { color: inherit; }
a.cl:hover .nm { color: var(--accent); }
.cl .who { flex: 1; min-width: 0; }
.cl .nm { font-weight: 600; color: var(--text-heading); display: block; }
.cl .sub { font-family: var(--mono); font-size: var(--fs-kicker); color: var(--text-muted); }
.cl .when { font-family: var(--mono); font-size: var(--fs-kicker); color: var(--text-muted); flex: none; }
/* Pips carry a word beside them, never colour alone - the group heading and
   the .sub line both say it in English. */
.pip { width: 9px; height: 9px; flex: none; background: var(--text-muted); }
.pip.needs { background: var(--accent); }
.pip.well { background: var(--good); }
.pip.quiet { background: var(--text-muted); }
/* FALLING BEHIND (#232). Amber rather than the base grey, which is what it
   fell through to when this rule was missing – identical to .pip.quiet, so
   the one bucket added to tell a client apart from a quiet one looked the
   same as a quiet one. Caught in review. */
.pip.behind { background: var(--warn); }

/* ---------- 10c. the session logger ----------
   The screen the v0.3 brief calls the biggest win: five sets by four fields,
   every box empty, was twenty inputs for one exercise.

   Values arrive pre-filled from the prescription and the resolved load
   (§5.2), and each row carries an explicit "done" tick. The tick is a real
   checkbox, which is what makes this safe: SessionLogService records every
   set that has ANY data, so pre-filling alone would log five sets for a
   client who did three. Pre-fill answers "what do I type"; the tick answers
   "what did I actually do". They are different questions. */
.set-row {
  display: grid;
  /* Reps, load and the tick are ONE control, so they are sized as one. The
     first attempt only left-aligned the tracks and left each field 9rem wide
     for a two-character number - which put ~150px between reps and load and
     landed the tick 340px in, reading as "over there" even though it was
     technically left-aligned. Capping the group is what actually groups it. */
  /* REPS IS THE PRESCRIPTION AND IT WAS THE TRUNCATED ONE (#5, 2026-10-06).
     The two fields were the same width, which looks tidy and is wrong: a
     load is at most five characters ("72.5") while reps carries the
     prescription itself as its placeholder, and "10 each leg" and "12 each
     arm" were arriving as "10 each l..." and "12 each a:" on a phone. The
     client cannot do a set they cannot read.

     THE TICK STAYS AT 44px. It is the minimum touch target
     (DESIGN_PRINCIPLES section 5) and Simon's primary device is an iPad, so
     it is the one track here that is not free to shrink. The width taken
     back came from the load. */
  grid-template-columns: 22px minmax(0, 8.5rem) minmax(0, 4rem) 44px;
  justify-content: start;
  max-width: 22rem;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) 0;
  border-bottom: 1px solid var(--line-dark);
}

/* §7's prescription band, in the builder's edit row (#309). An aid
   rather than a value, so it reads as information: mono for the figures,
   muted, and no control anywhere in it.

   .band-aid has one owner and one view, which is what
   StylesheetClassOwnershipTests is about and what .row-actions cost on
   2026-10-01. */
.band-aid {
  margin-top: var(--sp-1);
  line-height: 1.5;
}

/* Why a set's weight moved (#305). It sits UNDER the row rather than in
   it, because .set-row is a four-track grid sized to group reps, load and
   the tick as one control - a fifth track would pull that group apart, and
   spanning one would squeeze the fields. The negative top margin pulls it
   back under the row it belongs to, inside that row's bottom padding.

   NAMED FOR THE COMPONENT, not for what it looks like: `.set-why` has one
   owner and one view, which is what StylesheetClassOwnershipTests is about
   and what `.row-actions` cost on 2026-10-01. */
.set-why {
  margin: calc(var(--sp-2) * -1) 0 var(--sp-2);
  max-width: 20rem;
  color: var(--accent);
}

/* The column headings. Said once at the top rather than repeated on every
   row - and the point of them is the third one: nothing previously told you
   the box was a thing you were meant to do something to. */
/* ---- one screen per block (2026-10-07) --------------------------------
   Simon and Mich both rejected the screen-per-round logger independently:
   "the rounds visible together, in one section". A block is now one screen,
   grouped BY ROUND, so a row's neighbours are different exercises and the
   row has to name its own lift.

   SO THE BLOCK ROW IS A WIDER GRID, not the same one with a label squeezed
   into a 22px track. The exercise name takes a real column and the group
   widens to match; reps, load and the tick keep their own widths, because
   the reason they were sized that way (#5) has not changed.

   THE TICK STAYS AT 44px on both shapes – the minimum touch target
   (DESIGN_PRINCIPLES section 5), and Simon's primary device is an iPad. */
.set-row.is-block {
  grid-template-columns: minmax(0, 1fr) minmax(0, 6rem) minmax(0, 4rem) 44px;
  max-width: 30rem;
}
.set-row.is-block .ex-name {
  font-family: var(--font-body);
  font-size: var(--fs-body);
  color: var(--text);
  overflow-wrap: anywhere;
}

/* The round is a heading over its own group rather than a column, because
   it is the thing a client counts off: round one, both lifts, then round
   two. */
.round-head {
  margin: var(--sp-3) 0 0;
  font-family: var(--mono);
  font-size: var(--fs-kicker);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent);
}
.round-head:first-of-type { margin-top: 0; }

/* ---- RPE and the note, inline on the set (2026-10-07) ------------------
   They were one fold at the bottom of the screen, and on 2026-10-06 that
   fold moved to the last round of a block, which made it worse. Simon:
   "too disjointed from the sets".

   THE STRIP IS NOT A CONTROL ITSELF. It carries a quiet toggle, the
   copy-down, and whatever the page has to say about this row – so an
   untouched set costs one small line and nothing else. */
.set-extra {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  max-width: 30rem;
  padding: 0 0 var(--sp-2);
}
.set-more {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  min-height: 44px;
  padding: 0 var(--sp-2) 0 0;
  background: none;
  border: 0;
  color: var(--text-muted);
  font-family: var(--font-body);
  font-size: var(--fs-kicker);
  cursor: pointer;
}
.set-more:hover, .set-more:focus-visible { color: var(--accent); }
.set-more-sign {
  display: inline-grid;
  place-items: center;
  width: 1.4rem;
  height: 1.4rem;
  border: 1px solid var(--line-dark);
  font-family: var(--mono);
  line-height: 1;
}
.set-more:hover .set-more-sign, .set-more:focus-visible .set-more-sign {
  border-color: var(--accent);
}
.set-extra-body {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  width: 100%;
}
.set-extra-field { display: flex; align-items: center; gap: var(--sp-1); min-width: 0; }
.set-extra-field input { min-width: 0; }
.set-extra-field.set-extra-grow { flex: 1 1 12rem; }
.set-extra-field.set-extra-grow input { width: 100%; }
/* What happened to this row's save (2026-10-07). Quiet until something
   happens, and LOUD only on the one state that needs an action – a failed
   save is the client's to retry, and anything else competing for that
   colour makes it ordinary. */
/* ONE STATUS PER ROW (#400). Was .set-saved beside a second live region, so
   a row could read "Copy down  Already the same.  Saved" as one run-on line.
   The states are the same ones; what changed is that the copy-down and nudge
   messages now arrive here too, under data-state="note". */
.set-status { font-size: var(--fs-kicker); color: var(--text-muted); }
.set-status[data-state="saving"] { color: var(--text-muted); }
.set-status[data-state="saved"]  { color: var(--text-muted); }
.set-status[data-state="stale"]  { color: var(--text-muted); font-style: italic; }
.set-status[data-state="failed"] { color: var(--accent); font-weight: 600; }

/* Why the boxes opened, inside the boxes (#400). Its own line at the top of
   the panel, so it is never read as part of the row's save state – which is
   what "Copy down  Already the same.  Saved" was. Empty until something
   happens, and `:empty` keeps it from taking a line when it has nothing. */
.set-extra-said {
  grid-column: 1 / -1;
  margin: 0 0 var(--sp-1);
  font-size: var(--fs-kicker);
  color: var(--text-muted);
}
.set-extra-said:empty { display: none; }

.set-copy-down {
  min-height: 44px;
  font-size: var(--fs-kicker);
}

/* ---- clearing the queue in bulk (2026-10-07) --------------------------
   The bar sits ABOVE the list, so the control is met before the work
   rather than after it – the same correction the logger's copy button
   needed on 2026-10-06, applied before it could bite twice. */
.qbulk {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: var(--sp-3);
}
.qselect {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  min-height: 44px;
  font-size: var(--fs-kicker);
  color: var(--text-muted);
  cursor: pointer;
}
.qselect input { width: 20px; height: 20px; accent-color: var(--accent); }

.set-head { padding-bottom: var(--sp-1); }
.set-head .col-label {
  font-family: var(--mono);
  font-size: var(--fs-kicker);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.set-head .col-label.for-tick { text-align: center; }
.set-row:last-of-type { border-bottom: 0; }
.set-row .n { font-family: var(--mono); font-size: var(--fs-kicker); color: var(--text-muted); }

/* ---------- video: compact preview, and the coach's feedback ----------
   #142: the player used to render full-width with preload="metadata", which on
   a phone is the whole screen before the coach has decided to watch. A
   <details> is the whole mechanism – no script, and it still opens with
   JavaScript off. The tile is a PLACEHOLDER, not a frame: there is no frame
   extraction in the build, and a browser cannot generate a poster (see the
   view's comment). */
.video-fold > summary {
  display: flex;
  gap: var(--sp-3);
  align-items: center;
  cursor: pointer;
  list-style: none;
}
.video-fold > summary::-webkit-details-marker { display: none; }

.video-tile {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 84px;
  height: 56px;
  background: var(--ink-raised);
  border: 1px solid var(--line-dark);
}
/* A play triangle in the accent, drawn in CSS rather than shipped as an
   asset – it is three lines here and a request there. */
.video-play {
  width: 0;
  height: 0;
  border-left: 14px solid var(--accent);
  border-top: 9px solid transparent;
  border-bottom: 9px solid transparent;
}
.video-fold > summary:hover .video-tile { border-color: var(--accent); }

.video-meta { min-width: 0; }
.video-name {
  display: block;
  color: var(--text-heading);
  font-weight: 700;
  overflow-wrap: anywhere;
}

/* The coach's feedback, on the client's own video card. Set apart from the
   metadata around it because it is the one thing on that card written BY a
   person TO the person reading it. */
.feedback {
  margin-top: var(--sp-3);
  padding: var(--sp-3);
  background: var(--ink-raised);
  border-left: 2px solid var(--accent);
}
.feedback-body {
  color: var(--text-body);
  white-space: pre-wrap;   /* his line breaks are his */
  margin-bottom: var(--sp-2);
}

/* "Same for every set" – copies set one down. A link rather than a button on
   purpose: it is a convenience beside the rows, not a second action competing
   with the submit (DESIGN_PRINCIPLES.md §5.1). It is rendered hidden and
   unhidden by script, so with JavaScript off nothing appears here at all. */
.set-copy {
  display: inline-block;
  margin-top: var(--sp-2);
  font-size: var(--fs-small);
}
.set-field { min-width: 0; }
.set-field input {
  width: 100%;
  background: none;
  border: 0;
  border-bottom: 1px dashed var(--line-dark);
  border-radius: var(--radius);
  color: var(--text-heading);
  font-family: var(--mono);
  font-size: 1rem;
  padding: 2px 0;
}
.set-field input:hover, .set-field input:focus { border-bottom-color: var(--accent); }
/* The tick: a checkbox drawn as a button. Keyboard reachable, works with no
   JavaScript, and its label is what a screen reader announces. */
.tick { display: block; position: relative; width: 44px; height: 44px; }
.tick input {
  position: absolute;
  opacity: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  cursor: pointer;
}
.tick .box {
  display: flex;
  align-items: center;
  justify-content: center;
  /* 44px square: the minimum comfortable touch target, and the box IS the
     target - see the note under the checked rule about why the whole row is
     not one. */
  width: 44px;
  height: 44px;
  /* An empty box has to read as a TARGET, not as a gap. A 1px --line-dark
     outline on an --ink-soft card is nearly the card itself; 2px in the muted
     text tone, over a fill one step lighter than the card, is a thing you can
     see and aim at. */
  border: 2px solid var(--text-muted);
  background: var(--ink-raised);
  border-radius: var(--radius);
  font-size: 1.25rem;
  line-height: 1;
  transition: background .12s, border-color .12s;
}
/* Unticked draws NO MARK AT ALL - the glyph is a pseudo-element with no
   content, so there is nothing in the box and nothing in the DOM to mistake
   for a tick. A checkbox that shows its mark in both states cannot be read. */
.tick .box::after { content: ""; }
/* Interactive before you click it: the box answers the pointer. */
.tick:hover .box { border-color: var(--accent); background: var(--accent-soft); }
.tick:active .box { border-color: var(--accent-hover); }

/* ~ not +. The paired hidden Performed=false input sits between the checkbox
   and the box, so the adjacent-sibling combinator never matched and the box
   never filled - the tick had no visible state at all. */

/* Ticked is a SOLID accent box with a dark mark in it. The accent is the
   brightest thing in the palette and the brand's designated voice; --good is
   a muted sage and was too quiet to read at a glance in a gym. Dark mark on
   accent, not white, because it is the higher contrast of the two. */
.tick input:checked ~ .box {
  background: var(--accent);
  border-color: var(--accent);
}
.tick input:checked ~ .box::after {
  /* The glyph itself, not a CSS escape - the sheet is UTF-8 and already
     carries ▾/▴ this way, and an escape is one transcription step that can
     silently become the wrong character. */
  content: "✓";
  color: var(--ink);
  font-weight: 700;
}
.tick input:focus-visible ~ .box { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The second signal, so the state never rests on colour alone: the whole row
   takes an edge in the same colour. Someone who cannot separate the fill from
   its surroundings still sees the row change shape. */
.set-row:has(.tick input:checked) {
  border-left: 3px solid var(--accent);
  padding-left: var(--sp-2);
  margin-left: calc(var(--sp-2) * -1 - 3px);
}

/* NOTHING DIMS, EVER. Fading a completed set reads as skipped, which is the
   opposite of what a tick means. There is deliberately no rule here that
   lowers the contrast of a ticked row. */

/* Deliberately NOT dimming a ticked row's numbers. Fading them reads as
   "skipped", which is the opposite of what a tick means. The filled box is
   the whole signal. */

/* ---------- 11. AI provenance (DESIGN_PRINCIPLES.md §3) ----------
   Fixed copy, reused verbatim, never improvised per screen. Phase one
   generates nothing (CLAUDE.md §0 #6), so these are styled and ready:
   a screen that later carries generated content already has the slot. */

.provenance {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  font-family: var(--mono);
  font-size: var(--fs-kicker);
  letter-spacing: .08em;
  color: var(--text-muted);
  border: 1px solid var(--line-dark);
  border-radius: var(--radius);
  padding: 3px 8px;
  margin-top: var(--sp-2);
}

/* "Reviewed and approved by Simon" – his sign-off is the strongest
   statement on the screen, so it carries the one non-accent colour. */
.provenance.approved { border-color: var(--good); color: var(--good); }

/* ---------- 12. widen ----------
   Mobile-first: everything above is the phone. These two blocks widen
   it. The site's own breakpoints (560px, 900px) are reused so the two
   properties stay in step – only the direction is reversed. */

@media (min-width: 560px) {
  .col-sm-3 { flex: 0 0 25%; max-width: 25%; }
  .col-sm-9 { flex: 0 0 75%; max-width: 75%; }
  .d-sm-inline-flex { display: inline-flex; }
  .frames { grid-template-columns: repeat(6, 1fr); }
  .card, .panel { padding: var(--card-pad); }

  /* Set number, then reps / load / RPE / note across one line. */
  .set-log { display: grid; grid-template-columns: 40px 1fr; gap: var(--sp-2); align-items: start; }
  .set-log-no { margin-bottom: 0; padding-top: 10px; }
  .set-fields { grid-template-columns: repeat(4, 1fr); }
  .set-fields .wide { grid-column: auto; }
}

@media (min-width: 900px) {
  /* v0.3 screens are single-column by design - phone-shaped, one thing after
     another. Left at the full 1140px wrap they stretch: a two-ended row puts
     its halves a third of a viewport apart and nothing reads as a column.
     Pages opt in with ViewData["Narrow"]; the wrap itself is unchanged. */
  .wrap-narrow { max-width: 46rem; }

  main { padding: var(--section-y) 0; }
  .section + .section { margin-top: var(--section-y); }

  /* The nav is always visible; the burger goes away. */
  .nav-toggle { display: none; }
  .site-nav {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: var(--sp-4);
    padding-bottom: 0;
    border-bottom: 0;
  }
  .site-nav a, .site-nav .nav-user, .site-nav button { padding: 0; }
  .site-header .wrap { min-height: 68px; }

  .col-md-1 { flex: 0 0 8.3333%;  max-width: 8.3333%; }
  .col-md-2 { flex: 0 0 16.6667%; max-width: 16.6667%; }
  .col-md-3 { flex: 0 0 25%;      max-width: 25%; }
  .col-md-4 { flex: 0 0 33.3333%; max-width: 33.3333%; }
  .col-md-5 { flex: 0 0 41.6667%; max-width: 41.6667%; }
  .col-md-6 { flex: 0 0 50%;      max-width: 50%; }
  .col-md-7 { flex: 0 0 58.3333%; max-width: 58.3333%; }
}

/* ---------- programme builder (#130) ---------- */

/* The copy-week control. Visible and persistent - never a hover affordance,
   because it would not exist on Simon's iPad (§5.6). */
.week-copy { margin: 0 0 var(--sp-3); }

/* Edit and Remove side by side, both real buttons at the 44px floor. The
   column is allowed to wrap rather than forcing the table wider: the tablet
   budget is roughly 1024-1180px and the table already spends most of it.

   THIS CLASS BELONGS TO THE BUILDER AND NOTHING ELSE USES IT. The name reads
   generic, which is how the client list came to declare it a second time in
   2026-10-01 and inherit the flex-wrap above onto a component that needed
   nowrap. A second block with this selector merges with this one rather than
   replacing it. StylesheetClassOwnershipTests enforces the rule; a new
   component with row actions gets its own prefix, as .client-action* did. */
.row-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1);
  align-items: center;
}

/* The edit form sits in its own row under the prescription it edits, and it
   STACKS. §5.6: the table may stay a table, the form may not stay a row of
   seven fields. Its own background so the banding above cannot tint it. */
.edit-row td {
  background: var(--ink-soft) !important;
  padding: var(--sp-3);
}

.edit-form .grouphead { margin-top: 0; }

/* auto-fit rather than fixed columns: four fields side by side on a landscape
   tablet, folding to two and then one as the room runs out. */
.edit-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
  gap: var(--sp-2);
  margin-bottom: var(--sp-2);
}

.edit-grid label, .edit-notes { display: block; }

.edit-notes { margin-bottom: var(--sp-2); }

/* ---------- PB celebration (#35) ---------- */

.pb-party {
  position: relative;
  overflow: hidden;
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  background: var(--accent-soft);
  padding: var(--sp-4) var(--sp-3);
  margin-bottom: var(--sp-3);
  text-align: center;
}

.pb-shout {
  font-family: var(--display);
  color: var(--accent);
  font-size: 1.6rem;
  margin: 0;
  letter-spacing: .04em;
}

.pb-lift { margin: var(--sp-1) 0 0; color: var(--text-heading); font-size: 1.1rem; }
.pb-since { margin: var(--sp-1) 0 0; color: var(--text-muted); font-size: var(--fs-small); }
.pb-ask { margin: var(--sp-3) 0 var(--sp-2); color: var(--text-heading); }

/* The client's own answer, read back on an acknowledged card. Quoted, in the
   data face, because it is their words rather than the app's. */
.pb-said { margin: 0; color: var(--text-heading); font-style: italic; }

.pb-form { display: flex; gap: var(--sp-2); flex-wrap: wrap; justify-content: center; }
.pb-form .form-control { flex: 1 1 14rem; }

/* Tappable answers to "how did it feel" (#393). THEIR OWN PREFIX RATHER THAN
   .chip, which is a shared namespace with several owners – the lesson from
   #288, where two components took .row-actions and each inherited the other's
   declarations. These are also CONTROLS rather than labels, so they are 44px
   tall where a chip is 20, which is CLAUDE.md section 5.2's target size. */
.pb-suggestions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  justify-content: center;
  margin-bottom: var(--sp-2);
}

.pb-suggestion {
  min-height: 44px;
  padding: 0 var(--sp-3);
  font-family: var(--mono);
  font-size: var(--fs-kicker);
  letter-spacing: .06em;
  background: transparent;
  border: 1px solid var(--line-dark);
  border-radius: var(--radius);
  color: var(--text);
  cursor: pointer;
}

.pb-suggestion:hover,
.pb-suggestion:focus-visible { border-color: var(--accent); color: var(--accent); }

/* Confetti in CSS, because the app has no build step and no animation library
   (§0 #10) - 24 spans on staggered delays. */
.pb-confetti { position: absolute; inset: 0; pointer-events: none; }

.pb-bit {
  position: absolute;
  top: -12px;
  left: calc(var(--i) * 4.1%);
  width: 7px;
  height: 12px;
  opacity: 0;
  background: var(--accent);
  animation: pb-fall 2.4s linear calc(var(--i) * 70ms) 2;
}

.pb-bit:nth-child(3n)   { background: var(--text-heading); }
.pb-bit:nth-child(3n+1) { background: var(--accent-hover); }

@keyframes pb-fall {
  0%   { opacity: 1; transform: translateY(0) rotate(0deg); }
  100% { opacity: 0; transform: translateY(260px) rotate(420deg); }
}

/* A celebration is not worth a headache. Anyone who has asked the OS for less
   motion gets the words and none of the movement. */
@media (prefers-reduced-motion: reduce) {
  .pb-confetti { display: none; }
}

/* ---------- 13. The check-in scales (#188, bible §16) ----------
   "Quick-tap, 1-5 scale - scales beat smileys" is §16's own instruction, so
   these are numbered dots rather than faces. The same component renders the
   post-session difficulty at 1 to 10, which is why the width comes from the
   grid rather than from a fixed dot size.

   RADIOS, NOT BUTTONS, and that is load-bearing rather than a styling choice:
   nothing is pre-selected, so "not answered" posts nothing and stores NULL -
   the distinction migration 020 exists to protect. A control that always has
   a value could not express it. */
.scale { border: 0; margin: 0 0 var(--sp-3); }
.scale legend { padding: 0; }
.scale-row {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: var(--sp-1);
  margin-top: var(--sp-2);
}
/* Ten across still has to be tappable on a phone, so the dots go rectangular
   rather than shrinking below the touch target's height. */
.scale-row.is-ten { grid-template-columns: repeat(10, 1fr); gap: 4px; }
.scale-input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  margin: 0;
  pointer-events: none;
}
.scale-dot {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  border: 2px solid var(--text-muted);
  background: var(--ink-raised);
  border-radius: var(--radius);
  font-family: var(--mono);
  font-size: var(--fs-body);
  color: var(--text-body);
  cursor: pointer;
  transition: background .12s, border-color .12s, color .12s;
}
.scale-dot:hover { border-color: var(--accent); background: var(--accent-soft); }
/* + rather than ~: each input sits immediately before its own label, so the
   adjacent-sibling combinator is the one that matches. The tick next door
   needs ~ because a paired hidden input sits between the two - a difference
   worth stating, since copying the wrong one there cost a release. */
.scale-input:checked + .scale-dot {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--ink);
  font-weight: 700;
}
.scale-input:focus-visible + .scale-dot { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The anchors, so a number means something without a legend. */
.scale-ends {
  display: flex;
  justify-content: space-between;
  margin: var(--sp-1) 0 0;
}

/* The coach's read of a check-in that was never answered. Not a zero, not a
   dash pretending to be a score: the words say which question was not asked. */
.checkin-scores { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-3); margin: 0; }
.checkin-scores div { display: flex; gap: var(--sp-1); align-items: baseline; }
.checkin-scores .v { font-family: var(--mono); color: var(--text-heading); }
.checkin-scores .v.is-low { color: var(--accent); }

/* ---------- 14. The programme builder's head and week folds (#90) ----------
   §5.4 on the densest screen in the app: a summary on top, and the weeks as
   folds underneath with nothing truncated. §5.6 governs its width - this is a
   tablet screen at about 1180px, not a desk tool, so every target here stays
   at the 44px minimum and nothing depends on hover. */

/* The draft/approved state, in the head rather than in a chip. Approval is
   irreversible (CLAUDE.md §2.3), so it takes an edge and not a word: someone
   who cannot separate the two chip colours still sees a different screen.
   The accent is the working state and --good the locked one, matching the
   chips that already mean those things elsewhere in this sheet. */
.screen-head.is-draft,
.screen-head.is-locked {
  border-left: 3px solid var(--accent);
  padding-left: var(--sp-3);
}
.screen-head.is-locked { border-left-color: var(--good); }

.build-summary { margin-bottom: var(--sp-3); }

/* A week fold's summary is a row, not a line of prose: the name, then the
   counts, then a marker when the week holds a prescription whose load cannot
   resolve. The marker is what keeps a CLOSED fold honest. */
.week-fold > summary {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
  /* 44px, because this is the primary navigation on a touch screen (§5.6) and
     a fold summary defaults to a line of text height. */
  min-height: 44px;
}
.week-fold > summary .week-name {
  font-family: var(--mono);
  font-size: var(--fs-kicker);
  letter-spacing: var(--ls-kicker);
  text-transform: uppercase;
  color: var(--text-heading);
}
.week-fold > summary .week-count { font-size: var(--fs-small); color: var(--text-muted); }
/* The arrow the .fold rule appends sits after the flexed children, so it is
   pushed to the far edge rather than trailing the last chip. */
.week-fold > summary::after { margin-left: auto; }

/* ---------- 15. The screenshot view (#174) ----------
   A client screenshots their day and trains from the image. A screenshot
   captures the VIEWPORT, so the whole block answers to one number: about
   700-730px of content on an iPhone 14 once browser chrome is off.

   ONE ROW PER EXERCISE IS WHAT BUYS THE TYPE SIZE. At the logger's per-set
   density a four-exercise day is ~1,320px - nearly two viewports. At ~40px a
   row it is ~160px, and the headroom goes into the load, which is the number
   read across a bench. Nothing here is a control: no 44px targets, because
   that target is what broke the budget. */
.shot {
  background: var(--ink-soft);
  border: 1px solid var(--line-dark);
  border-radius: var(--radius);
  padding: var(--sp-3);
  margin-bottom: var(--sp-2);
}
.shot-ctx {
  font-family: var(--mono);
  font-size: var(--fs-kicker);
  letter-spacing: var(--ls-kicker);
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 0;
}
.shot-title {
  font-family: var(--display);
  font-size: 1.25rem;
  line-height: 1.15;
  color: var(--text-heading);
  margin: var(--sp-1) 0 var(--sp-3);
}
.shot-empty { color: var(--text-muted); margin: 0; }

.shot-lifts { list-style: none; margin: 0; padding: 0; }
/* The row: name and volume on the left, the load hard right. Baseline-aligned
   so the big number does not push the name off centre. */
.shot-lift {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: baseline;
  column-gap: var(--sp-2);
  padding: var(--sp-2) 0;
  border-bottom: 1px solid var(--line-dark);
}
.shot-lift:last-child { border-bottom: 0; }
.shot-nm {
  grid-column: 1;
  color: var(--text-heading);
  font-weight: 600;
}
.shot-vol {
  grid-column: 1;
  grid-row: 2;
  font-size: var(--fs-small);
  color: var(--text-muted);
}
/* THE ONE THING SET LARGER THAN BODY SIZE, and the reason the rest is dense.
   Tabular figures so a column of loads lines up on the decimal. */
.shot-kg {
  grid-column: 2;
  grid-row: 1 / span 2;
  align-self: center;
  font-size: 1.6rem;
  line-height: 1;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}
.shot-unit {
  font-size: var(--fs-small);
  color: var(--text-muted);
  margin-left: 2px;
}
/* No training max, so no number (CLAUDE.md §8). Muted rather than alarming:
   on an image the client cannot act on an error, so it reads as an
   instruction. */
.shot-ask {
  grid-column: 2;
  grid-row: 1 / span 2;
  align-self: center;
  font-size: var(--fs-small);
  color: var(--text-muted);
}
/* The reflection line. PROSE, NOT A LABEL - a line that reads like a field on
   a screenshot is a picture of a control, which is the defect this whole view
   avoids. No border, no box, no colon. */
.shot-ask-line {
  margin: var(--sp-3) 0 0;
  color: var(--text-muted);
  font-style: italic;
}
/* Everything for the screen rather than the image sits below the card, so a
   client framing the shot can crop it out. */
.shot-after { margin: 0 0 var(--sp-3); }

/* ---------------------------------------------------------------------------
   Client-list row actions (Mich, 2026-10-01; the principle is
   DESIGN_PRINCIPLES section 7, "the data is already there and the work is
   routes to it")
   ---------------------------------------------------------------------------
   Four actions per client row: log a session on their behalf, their last
   session, their video, their programme. Log is the only filled control
   because it is the one Simon reaches for; the rest are quiet.

   THE PREFIX IS client-action RATHER THAN row-action, AND THAT IS A FIX
   RATHER THAN A PREFERENCE. The first cut of this component called itself
   .row-actions, which the programme builder had already been using since
   #130 for its Edit/Remove pair. A second block with the same selector
   MERGES with the first instead of replacing it, so this component silently
   inherited the builder's flex-wrap: wrap - written there deliberately, to
   let that table's action column wrap rather than force the table wider -
   and the client list's four 112px slots wrapped to one line each. Rows
   came out 230px tall on the deployed site. Renaming is the fix; overriding
   flex-wrap would have left the coupling in place for the next person.
   StylesheetClassOwnershipTests stops it happening again, and CLAUDE.md
   section 10 carries the general shape.

   THE SLOTS CARRY A FIXED FLOOR AND AN ABSENT ACTION LEAVES ITS SLOT EMPTY,
   which
   is a decision rather than a layout accident. An empty video slot says
   "nothing waiting" in the same glance the full one says the opposite, so the
   column is scannable top to bottom - collapsing the gaps would make the
   icons jog from row to row and destroy exactly that reading.

   The floors are per-slot rather than one width for all four, because what
   makes the column scan is that every row's Session control sits at the same
   x - not that Log is as wide as Programme. Four slots at the widest label
   would spend 523px of the actions column where four honest ones spend 398,
   and the 125px difference comes straight out of the goals column.

   44px minimum on every target: Simon's primary device is an iPad, and a
   control smaller than a fingertip is a control that gets mis-tapped. */
.client-actions {
  display: flex;
  flex-wrap: nowrap;        /* one line. See the note above - this used to be
                               inherited as wrap from another component. */
  gap: 2px;                 /* --sp-1 is 4px and the column cannot spare 6 of
                               them; the slot floors already separate these. */
  align-items: center;
  white-space: nowrap;
}

.client-action-slot {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: flex-start;
  min-height: 44px;
}

/* EVERY NUMBER BELOW IS MEASURED, NOT ESTIMATED. They were taken by rendering
   each label in Montserrat 600 at .8rem - the font and size this component
   actually ships - and adding the icon, the gap, the padding and the borders.
   The first cut of this file guessed, and guessed "Programme" at 67px when it
   is 80px; CLAUDE.md section 10's lesson about taking the expected value from
   the domain rather than from the code applies to widths as much as to loads.

   Named rather than nth-child so each width reads as what it is.

   MIN-WIDTH RATHER THAN WIDTH, and the difference matters on one row in a
   hundred: the video label gains a count from two videos up ("Video (3)" is
   113px against "Video" at 85px). A fixed width would let that control
   overflow its slot and sit on top of the next one. A floor lets it push the
   programme slot right on that row alone - a visible shift rather than an
   overlap, and the only row where the column stops aligning. */
.client-action-slot.is-log       { min-width: 74px; }   /* control  72.2px */
.client-action-slot.is-session   { min-width: 100px; }  /* control  98.2px */
.client-action-slot.is-video     { min-width: 88px; }   /* control  85.2px */
.client-action-slot.is-programme { min-width: 130px; }  /* control 127.2px */

/* ONE FAMILY WITH AN EMPHASIS, not three unrelated controls. All three
   actions are the same pill - same height, same padding, same type, same
   border box - and the only thing that marks the primary is the fill. The
   first cut gave Log uppercase, letter-spacing and a heavier weight on top of
   the fill, and gave the other two no border at rest, so a row read as a
   button next to two links (Mich, 2026-10-01). */
.client-action {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  min-height: 44px;
  padding: 0 10px;          /* 10 rather than 12: four controls in a column
                               that has about 410px, so 16px matters. */
  border: 1px solid var(--line-dark);
  border-radius: var(--radius);
  color: var(--text-body);
  font-size: .8rem;
  font-weight: 600;
  text-decoration: none;
}

/* 18px inside a control rather than the set's usual 20, for the same 410px
   reason. The .ico default is unchanged - this is the component tuning its
   own copy, not a change to the convention in _Icons.cshtml. */
.client-action .ico { width: 18px; height: 18px; }

/* Specificity beats the global a:hover, which would otherwise put the link
   underline back along the bottom edge. */
.client-action:hover {
  color: var(--accent);
  border-color: var(--accent);
}

.client-action.is-primary {
  background: var(--accent);
  color: var(--white);
  border-color: var(--accent);
}

.client-action.is-primary:hover { background: var(--accent-hover); color: var(--white); }

/* currentColor is the whole point: one asset, both themes, and the icon takes
   the colour of the label beside it - including the white of the primary and
   the accent of a hover. */
.ico {
  width: 20px;
  height: 20px;
  flex: 0 0 auto;
  color: currentColor;
}

/* The symbol sheet itself renders nothing. display:none would stop <use>
   resolving in some engines, so it is removed from flow instead. */
.icon-sheet {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}
