/* A.N.I.M.A. — shared design tokens and base components.
   Source of truth: STYLE.md. Reference implementation:
   mockup-coach-dashboard.html. When adding a screen, reuse what's
   here rather than inventing new tokens or component patterns. */

:root {
  --base:        #0E0E0F;
  --surface:     #1B1B1D;
  --surface-up:  #202022;
  --rule:        #3A3A3E;

  --text:        #F0F0F2;
  --text-body:   #8C8C90;
  --text-dim:    #6E6E73;

  --alarm:       #FF3B21;
  --alarm-dim:   #7A2412;
  --alarm-wash:  #24100C;

  --data:        #5F8199;

  --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:24px; --s6:32px; --s7:48px;

  --fd: 'Chakra Petch', sans-serif;
  --fb: 'IBM Plex Sans', system-ui, sans-serif;
  --fm: 'IBM Plex Mono', ui-monospace, monospace;

  --t-hero:  2.5rem;
  --t-xl:    2.125rem;
  --t-lg:    1.625rem;
  --t-md:    1.0625rem;
  --t-body:  0.9375rem;
  --t-sm:    0.8125rem;
  --t-label: 0.6875rem;

}

* { box-sizing: border-box; margin: 0; padding: 0; }

body {
  background: var(--base);
  color: var(--text-body);
  font-family: var(--fb);
  font-size: var(--t-body);
  line-height: 1.55;
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
}

/* Report-style layout: coach screens and dense athlete screens. */
.wrap { max-width: 1180px; margin: 0 auto; padding: var(--s5) var(--s4) var(--s7); }

/* Single-focus layout: auth, and anything else showing one thing at a time. */
.shell {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--s6);
  padding: var(--s5) var(--s4);
}
/* Under the nav header the centering is wrong: it left a screen's
   worth of empty band between the menu and the card. A shell that
   follows the header starts at the top; the signed out pages, which
   have no header, stay centered. */
.nav ~ .shell { min-height: 0; justify-content: flex-start; }

.lbl {
  font-family: var(--fm);
  font-weight: 500;
  font-size: var(--t-label);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.brand {
  font-family: var(--fd);
  font-weight: 700;
  font-size: .8125rem;
  letter-spacing: .34em;
  color: var(--text-dim);
  text-decoration: none;
}

/* Body face, not display: the wordmark above already carries the
   display weight, and a second display line beside it would compete
   rather than support. text-body, not text-dim — this is the one
   line on the page meant to be read before signing in, and text-dim
   is furniture only. Capped at the same width as .card--narrow below
   it, so the two lines share a column instead of the tagline running
   wide. */
.tagline {
  font-family: var(--fb);
  font-size: var(--t-sm);
  color: var(--text-body);
  text-align: center;
  max-width: 360px;
  margin: var(--s2) auto 0;
}

/* Shared page header, mounted by nav.js. Furniture: monochrome, no
   accent color — text-dim/text-body/text only, same as any other
   label or divider. */
.nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  padding: var(--s2) var(--s4);
  border-bottom: 1px solid var(--rule);
}
/* The menu toggle: a 44px target (STYLE.md's quality floor) holding
   three bars drawn in currentColor, so hover and the current-page
   greys come from the same tokens as a link. The negative margin keeps
   the bars flush with the header's right edge, where the last link's
   label used to sit. */
.nav-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin-right: calc(var(--s3) * -1);
  color: var(--text-dim);
  background: none;
  border: none;
  cursor: pointer;
}
.nav-toggle:hover { color: var(--text-body); }
.nav-toggle[aria-expanded="true"] { color: var(--text); }
.nav-toggle:focus-visible { outline: 2px solid var(--alarm); outline-offset: 2px; }
.nav-bars, .nav-bars::before, .nav-bars::after {
  display: block;
  width: 20px;
  height: 2px;
  background: currentColor;
}
.nav-bars { position: relative; }
.nav-bars::before, .nav-bars::after { content: ''; position: absolute; left: 0; }
.nav-bars::before { top: -6px; }
.nav-bars::after { top: 6px; }
/* Each link carries its own 44px tap target with side padding for a
   hit area wider than its label. */
/* .viewlink and .viewlinks are the same furniture inside a flow view:
   the way back ("All people", "Back to conversation") and the links to
   a person's context ("Notes", "Exercises") on coach.html. Buttons,
   not anchors, since a view change is a state change on the page
   rather than a navigation; same type, same 44px target. */
.viewlinks { display: flex; align-items: center; gap: 0; flex-wrap: wrap; margin: 0 calc(var(--s3) * -1); }
.nav-link, .viewlink {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-family: var(--fm);
  font-weight: 500;
  font-size: var(--t-label);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-dim);
  background: none;
  border: none;
  padding: 0 var(--s3);
  cursor: pointer;
  text-decoration: none;
}
.nav-link:hover, .viewlink:hover { color: var(--text-body); }
.nav-link:focus-visible, .viewlink:focus-visible { outline: 2px solid var(--alarm); outline-offset: 2px; }
.nav-link-current { color: var(--text); cursor: default; }
/* The panel the toggle opens: anchored to the right edge, full height,
   over the page, with the page dimmed behind it. Both hide with the
   hidden attribute, and the body locks its scroll while the panel is
   up. Links stack in the panel with the same type and target as
   before; only the direction changed. Sign out sits under a rule. */
.nav-backdrop {
  position: fixed;
  inset: 0;
  background: rgb(14 14 15 / .7);
  z-index: 20;
}
.nav-panel {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(320px, 85vw);
  display: flex;
  flex-direction: column;
  padding: var(--s2) var(--s4) var(--s5);
  background: var(--surface);
  border-left: 1px solid var(--rule);
  overflow-y: auto;
  z-index: 21;
}
.nav-backdrop[hidden], .nav-panel[hidden] { display: none; }
.nav-close { align-self: flex-end; margin: 0 calc(var(--s3) * -1) var(--s2) 0; }
.nav-panel-links { display: flex; flex-direction: column; margin: 0 calc(var(--s3) * -1); }
.nav-panel-signout { margin: var(--s2) calc(var(--s3) * -1) 0; padding-top: var(--s2); border-top: 1px solid var(--rule); }
body.nav-open { overflow: hidden; }
.viewlink:disabled { opacity: .5; cursor: default; }

/* Person list: a flow's Who view, one row per person and the whole row
   the target (STYLE.md's row list shape as a button). The name in the
   display face, one line of context beneath in mono. */
.person-list { border-top: 1px solid var(--rule); }
.person-row {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--s1);
  width: 100%;
  min-height: 64px;
  padding: var(--s4) 0;
  background: none;
  border: none;
  border-bottom: 1px solid var(--rule);
  text-align: left;
  cursor: pointer;
}
.person-row:hover .lbl { color: var(--text-body); }
.person-row:focus-visible { outline: 2px solid var(--alarm); outline-offset: -2px; }
.person-row:disabled { opacity: .5; cursor: default; }

/* The exercise match list on log.html: a list sitting over the form
   rather than more card. Its own --base ground and a 1px --text-dim
   border on all four sides, square, flush against the field above it
   so it reads as that field's. The border is what separates it, not
   the fill: --rule against a --surface card measures 1.52 to 1, under
   the 3 to 1 floor for a control boundary, which is why the list
   disappeared into the card; --text-dim measures 3.39 to 1. A fill
   alone would not have done it, --base on --surface being 1.12 to 1
   and --surface-up 1.06 to 1. The same finding moved the secondary
   button's border, and the check in's saved view already uses a
   --base block with a border for the same reason. Rows keep their
   hairlines; the last row's would double the panel's own edge. */
.exercise-matches {
  background: var(--base);
  border: 1px solid var(--text-dim);
  margin-top: calc(var(--s2) * -1);
}
.exercise-matches .person-row:last-child { border-bottom: none; }
/* Empty while an exercise is chosen: no panel, not an empty one. */
.exercise-matches:empty { display: none; }

.title {
  font-family: var(--fd);
  font-weight: 700;
  font-size: var(--t-lg);
  color: var(--text);
  letter-spacing: .03em;
  text-transform: uppercase;
}

.card {
  background: var(--surface);
  border: 1px solid var(--rule);
  padding: var(--s5);
}
.card > .lbl { display: block; margin-bottom: var(--s1); }
.card h1, .card h2 {
  font-family: var(--fd);
  font-weight: 600;
  margin-bottom: var(--s4);
  letter-spacing: .06em;
  text-transform: uppercase;
}
.card h1 { font-size: var(--t-md); color: var(--text); }
.card h2 { font-size: var(--t-body); color: var(--text-body); }
/* Landing: the confirmation a person cannot miss after a step lands
   them somewhere (plan-review.html's "Draft saved"). The raised surface
   and a --text border set it apart from every other card; never
   --alarm, since nothing here needs acting on to proceed. */
.card--landing { background: var(--surface-up); border-color: var(--text); }
.card--narrow { width: 100%; max-width: 360px; }
.card--form { width: 100%; max-width: 520px; }
.card--panel { width: 100%; max-width: 760px; }

.field { display: flex; flex-direction: column; gap: var(--s2); margin-bottom: var(--s4); }
/* Every label inside a field is the mono uppercase form label,
   except one that is a button: import.html's file picker is a
   <label class="btn"> for its hidden input, and it was coming out
   uppercase, letterspaced, and in --text-dim on the red fill. The
   exception is on this general rule, not on that button, since any
   label acting as a button anywhere hits the same thing. It sits
   inside :where() so the selector weighs exactly what `.field label`
   always has: a bare :not(.btn) would add a class to its weight, and
   the rule would then beat `.choice-list .choice` and
   `.field .muscle-row`, which exist to override it, and turn
   onboarding's training goal choices into form labels. */
.field label:where(:not(.btn)) {
  font-family: var(--fm);
  font-weight: 500;
  font-size: var(--t-label);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.input {
  background: var(--surface-up);
  border: 1px solid var(--rule);
  color: var(--text);
  font-family: var(--fb);
  font-size: var(--t-body);
  min-height: 44px;
  padding: 0 var(--s4);
  width: 100%;
}
.input:focus-visible {
  outline: 2px solid var(--alarm);
  outline-offset: 2px;
}

.textarea {
  min-height: 88px;
  padding: var(--s3) var(--s4);
  resize: vertical;
}

.hint {
  font-family: var(--fb);
  font-size: var(--t-sm);
  color: var(--text-body);
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--fd);
  font-weight: 600;
  font-size: .9375rem;
  text-decoration: none;
  min-height: 44px;
  padding: 0 var(--s5);
  cursor: pointer;
  border: 1px solid transparent;
  transition: border-color 120ms ease-out, color 120ms ease-out;
  width: 100%;
}
.btn-primary { background: var(--alarm); color: var(--base); border-color: var(--alarm); }
.btn-secondary { background: transparent; color: var(--text); border-color: var(--text-dim); }
.btn-inline { width: auto; }
.btn-secondary:hover { border-color: var(--text); }
.btn:disabled { opacity: .5; cursor: default; }
.btn:focus-visible { outline: 2px solid var(--alarm); outline-offset: 2px; }
/* A file input hidden with the clip-rect pattern, so it stays focusable
   and reachable by a screen reader, with a label styled as a button in
   its place instead of the browser's own control. The wrapper is
   positioned so the offscreen input sits beside its label rather than
   at the top of the page, and the label carries the focus ring, since
   the input itself is a clipped pixel. */
.file-pick { position: relative; display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; }
.file-input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}
.file-input:focus-visible + .btn { outline: 2px solid var(--alarm); outline-offset: 2px; }

/* Segmented rating, mounted by segmented.js: five cells, 1 to 5, one
   selected or none. None is not reported, and pressing the selected
   cell gets back there. Monochrome per STYLE.md, since a rating is a
   fact rather than something to act on: --text-dim on transparent,
   the selected cell lifted to --text on --surface-up with a --text
   border, --alarm only in the focus ring. Each cell its own 44px
   target; numbers in --fd with tabular numerals like every other
   numeric treatment. */
.segmented { display: inline-flex; gap: var(--s1); }
.segmented-cell {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  font-family: var(--fd);
  font-weight: 600;
  font-size: var(--t-body);
  font-variant-numeric: tabular-nums;
  color: var(--text-dim);
  background: transparent;
  border: 1px solid var(--rule);
  cursor: pointer;
  transition: border-color 120ms ease-out, color 120ms ease-out;
}
.segmented-cell:hover { color: var(--text-body); }
.segmented-cell.is-selected { color: var(--text); background: var(--surface-up); border-color: var(--text); }
.segmented-cell:focus-visible { outline: 2px solid var(--alarm); outline-offset: 2px; }

/* The pinned action bar, STYLE.md's Pinned action bar: one primary
   action fixed to the bottom of the screen while an edit or a review
   is open on history.html, and on no other surface. The page gives
   its .wrap bottom padding to match the bar's measured height, so the
   last row is never covered, and hides the bar entirely while an
   input, select, or textarea has focus, so it never sits over the
   phone keyboard. The safe area inset keeps the button clear of a
   phone's home indicator. Under the nav panel and its backdrop. */
.pinned-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 10;
  background: var(--surface);
  border-top: 1px solid var(--rule);
  padding: var(--s3) var(--s4) calc(var(--s3) + env(safe-area-inset-bottom));
}
.pinned-bar .btn { display: flex; max-width: 760px; margin: 0 auto; }

/* The month calendar, calendar.js, STYLE.md's Month calendar:
   History's way of choosing a week. Each week is one button the full
   width of the card, seven cells across, so the row is the target and
   never a single day. Monochrome like the segmented rating, and for
   the same reason, a date is a fact rather than something to act on:
   a day with a workout lifts to --text, a day that was only planned
   stays dim with a line under its number, the selected week takes the
   segmented cell's selected treatment, and --alarm appears only in
   the focus outline. Day numbers in --fd with tabular numerals like
   every other numeric treatment. The weekday row shares the week's
   grid and its 1px side borders' width, so initials sit over their
   columns. */
.calendar { display: flex; flex-direction: column; gap: var(--s1); margin-bottom: var(--s5); }
/* The head: an arrow, the Month and Year dropdowns, an arrow. The
   dropdowns share what the arrows leave and may shrink, min-width 0
   and tighter side padding than a form's input, so the row still fits
   a 320px screen; the arrows are 44px square and never shrink. */
.calendar-head {
  display: flex;
  align-items: center;
  gap: var(--s1);
  margin-bottom: var(--s2);
}
.calendar-head .input { flex: 1; min-width: 0; width: auto; padding: 0 0 0 var(--s2); }
.calendar-head .input[aria-label="Month"] { flex: 0.85; }
.calendar-head .input[aria-label="Year"] { flex: 1.15; }
.calendar-arrow { flex: none; width: 44px; padding: 0; font-size: var(--t-md); line-height: 1; }
/* The way home, drawn only when another week is selected. */
.calendar-thisweek { display: flex; justify-content: flex-start; margin-bottom: var(--s2); }
.calendar-thisweek .btn { min-height: 44px; font-size: var(--t-sm); padding: 0 var(--s4); }
/* The month said aloud for a screen reader, since the dropdowns
   replaced the printed month name. Hidden from sight, not from the
   accessibility tree. */
.calendar-live {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}
.calendar-weekdays {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  padding: 0 1px;
  text-align: center;
}
.calendar-week {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  align-items: center;
  width: 100%;
  min-height: 44px;
  padding: 0;
  color: var(--text-dim);
  background: transparent;
  border: 1px solid var(--rule);
  cursor: pointer;
  transition: border-color 120ms ease-out, background-color 120ms ease-out;
}
.calendar-week:hover { border-color: var(--text-dim); }
.calendar-week.is-selected { background: var(--surface-up); border-color: var(--text); }
.calendar-week:focus-visible { outline: 2px solid var(--alarm); outline-offset: 2px; }
.calendar-day {
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--fd);
  font-weight: 600;
  font-size: var(--t-body);
  font-variant-numeric: tabular-nums;
  color: var(--text-dim);
}
.calendar-day-number { padding: 0 2px 1px; border-bottom: 1px solid transparent; }
.calendar-day.is-trained { color: var(--text); }
.calendar-day.is-planned .calendar-day-number { border-bottom-color: var(--text-dim); }
/* The legend: a sample number in each treatment beside its words,
   built from the day cells' own classes so it cannot drift from them. */
.calendar-legend {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-start;
  gap: var(--s1) var(--s4);
  margin: var(--s2) 0 0;
}
.calendar-legend-item { display: inline-flex; align-items: baseline; gap: var(--s2); }
.calendar-legend .calendar-day { display: inline-flex; font-size: var(--t-sm); }

/* The check in's rows, checkin.html: a rated thing's name and status
   on one line with the segmented control beneath, so the row works at
   phone width without the control wrapping away from its name. A
   rated row's name lifts to --text, the way a checked choice does.
   Soreness groups are separated by a rule; each carries a breakdown
   disclosure whose granular rows indent under it. */
.rating-row { padding: var(--s2) 0; }
.rating-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s3); margin-bottom: var(--s2); }
.rating-side { display: inline-flex; align-items: baseline; gap: var(--s3); }
.rating-name { font-family: var(--fd); font-weight: 600; font-size: var(--t-body); color: var(--text-body); letter-spacing: .02em; }
.rating-row.is-rated .rating-name { color: var(--text); }
/* The state sits beside the name, "Shoulders · Not reported", then
   "Shoulders · 3" with the value lifted to --text, rather than out in
   the right corner, where on a phone it read as belonging to nothing.
   The corner keeps only the Unsaved marker. */
.rating-name-line { display: inline-flex; align-items: baseline; flex-wrap: wrap; gap: 0 var(--s2); min-width: 0; }
.rating-sep { color: var(--text-dim); }
.rating-status {
  font-family: var(--fm);
  font-size: var(--t-label);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-dim);
  white-space: nowrap;
}
.rating-row.is-rated .rating-status { color: var(--text); }
.soreness-group { border-top: 1px solid var(--rule); }
/* A disclosure's summary is a view link, per STYLE.md's button rule
   since 17 Sep 2026: expanding a list changes what is shown and
   writes nothing, so it is never a bordered box. A left aligned row in
   the view link's type with a small chevron before it, turning down
   when open, and the 44px target kept. It was a bordered, centered
   button until then, which on the check in drew eight full width
   boxes down the screen. Every disclosure that carries this class
   moves with it: the check in's breakdowns, Bands or chains in the
   set form, Show figure on the report, Past limits, and the review's
   Show the whole workout. Monochrome, --alarm only in the focus ring. */
.checkin-breakdown summary {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: var(--s2);
  min-height: 44px;
  margin: 0;
  padding: 0;
  font-family: var(--fm);
  font-weight: 500;
  font-size: var(--t-label);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-dim);
  background: transparent;
  border: 0;
  cursor: pointer;
  list-style: none;
}
.checkin-breakdown summary::-webkit-details-marker { display: none; }
.checkin-breakdown summary::before {
  content: '';
  flex: none;
  width: 6px;
  height: 6px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(-45deg);
  transition: transform 120ms ease-out;
}
.checkin-breakdown[open] summary::before { transform: rotate(45deg); }
.checkin-breakdown summary:hover { color: var(--text-body); }
.checkin-breakdown[open] summary { color: var(--text-body); }
.checkin-breakdown summary:focus-visible { outline: 2px solid var(--alarm); outline-offset: 2px; }
.checkin-breakdown .rating-row { padding-left: var(--s4); }
/* The guardrail muscle rows, guardrails.js and onboarding.html: one
   checkbox row per muscle, the whole row the 44px target, behind the
   check in's disclosure treatment above, which they share unchanged.
   .field .muscle-row outranks .field label's mono uppercase form
   label treatment, the way .choice-list .choice does. */
.muscle-rows { border-top: 1px solid var(--rule); margin-top: var(--s2); }
.field .muscle-row {
  display: flex;
  align-items: center;
  gap: var(--s3);
  min-height: 44px;
  padding: 0 var(--s4);
  border-bottom: 1px solid var(--rule);
  cursor: pointer;
  font-family: var(--fd);
  font-weight: 600;
  font-size: var(--t-body);
  letter-spacing: .02em;
  text-transform: none;
  color: var(--text-body);
}
.field .muscle-row.is-chosen { color: var(--text); }
.field .muscle-row input { width: 20px; height: 20px; margin: 0; flex-shrink: 0; accent-color: var(--text); cursor: pointer; }
.field .muscle-row:focus-within { outline: 2px solid var(--alarm); outline-offset: -2px; }
/* The saved view, a report on the landing card: eyebrow and title,
   the fill legend, the figure, then the record as STYLE.md's row list
   with the value in mono on the right, --text when it is something,
   --text-dim when it is ready and unremarkable. The legend's swatches
   are the figure's own fills in miniature. */
.checkin-saved { margin-top: var(--s3); }
/* The figure's ground. The ready fill is --surface-up, and so is the
   landing card, so on the card itself a muscle reported as 1 or 2
   vanished and read as never touched. The legend and the figure share
   one --base block with a --rule border, the legend as its top strip,
   so the ready fill and the Ready swatch have an edge to sit against.
   The fills themselves are STYLE.md's and untouched. */
.checkin-saved .fill-legend { margin: var(--s3) 0 0; padding: var(--s3) var(--s4) 0; background: var(--base); border: 1px solid var(--rule); border-bottom: 0; }
.checkin-saved .body-figure { margin: 0 0 var(--s4); padding: var(--s3) var(--s4) var(--s4); background: var(--base); border: 1px solid var(--rule); border-top: 0; }
.fill-legend { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s4); margin-top: var(--s3); }
.fill-legend-item { display: inline-flex; align-items: center; gap: var(--s2); }
.fill-swatch { display: inline-block; width: 12px; height: 12px; border: 1px solid var(--rule); }
.fill-swatch.is-low { background: var(--surface-up); }
.fill-swatch.is-mid { background: var(--alarm-dim); }
.fill-swatch.is-high { background: var(--alarm); }
.record-list { border-top: 1px solid var(--rule); }
.record-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3);
  padding: var(--s3) 0;
  border-bottom: 1px solid var(--rule);
}
.record-name { font-family: var(--fd); font-weight: 600; font-size: var(--t-body); color: var(--text); letter-spacing: .02em; }
.record-context { font-family: var(--fm); font-size: var(--t-label); letter-spacing: .06em; color: var(--text-dim); margin-top: var(--s1); }
.record-value { font-family: var(--fm); font-size: var(--t-sm); letter-spacing: .04em; color: var(--text); text-align: right; white-space: nowrap; flex-shrink: 0; }
.record-value.is-quiet { color: var(--text-dim); }
.checkin-actions { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; margin-top: var(--s4); }

/* Body figure, bodyfigure.js: STYLE.md's front and back silhouettes,
   every muscle its own shape in a --rule outline at 0.8px. Fills from
   the rating: none while not reported, --surface-up for 1 or 2,
   --alarm-dim for 3, --alarm for 4 or 5. Head, torso, and arms are
   outline only. A picture, never a control. Scales with its container
   up to a width that keeps each silhouette readable on a phone. */
.body-figure { display: flex; justify-content: center; align-items: flex-start; gap: var(--s4); margin: var(--s3) 0; }
.body-figure-view { flex: 1 1 0; max-width: 240px; }
.body-figure-view svg { display: block; width: 100%; height: auto; }
.body-figure-caption { display: block; text-align: center; margin-top: var(--s2); }
/* On a phone the two views stack, each taking the card's full width,
   so every region clears STYLE.md's 44px floor with room to spare. */
@media (max-width: 599px) {
  .body-figure { flex-direction: column; align-items: center; }
  .body-figure-view { width: 100%; max-width: 340px; }
}
.body-figure-furniture, .body-figure-shape { fill: none; stroke: var(--rule); stroke-width: .8; }
.body-figure-shape.is-low { fill: var(--surface-up); }
.body-figure-shape.is-mid { fill: var(--alarm-dim); }
.body-figure-shape.is-high { fill: var(--alarm); }
/* The small figure, STYLE.md's size cap, for the summary card: the two
   views stay side by side at every width and never stack. The phone
   stacking rule above does not apply here, because the figure stacked
   for a reason the card does not have: on the check in screen it was
   an input that had to clear the 44px floor, and on the summary card
   it is a picture with role img, so it can be small. The geometry is
   unchanged; only the box it is drawn into. */
.body-figure--small { gap: var(--s3); }
/* min-width 0 so a view can shrink under 160px on a narrow phone and
   the two stay side by side; a flex item otherwise refuses to go
   under its content width. */
.body-figure--small .body-figure-view { flex: 1 1 0; min-width: 0; max-width: 160px; }
@media (max-width: 599px) {
  .body-figure--small { flex-direction: row; align-items: flex-start; }
  .body-figure--small .body-figure-view { width: auto; max-width: 160px; }
}

/* Summary card, summarycard.js: STYLE.md's Summary card, the last
   seven days at a glance, at the head of report.html and, in pass 2,
   on coach.html's Talk view. The legend and the small figure share
   one --base block with a --rule border, the ground .checkin-saved
   gives its figure and for the same reason: the ready fill is
   --surface-up, so on a --surface card a muscle reported as 1 or 2
   vanished. Those rules are repeated here rather than widened, so the
   saved view is untouched. The severity columns are one column on a
   phone and two side by side from 768px, STYLE.md's grid breakpoint.
   The muscle rows are the record row list from checkin.html's saved
   view, .record-row and its parts, so a change there moves both; the
   card adds only the dot before the name, in its band's fill, never
   the only carrier since the rating and the column's word sit beside
   it. */
.summary-card { display: flex; flex-direction: column; gap: var(--s3); padding-bottom: var(--s4); margin-bottom: var(--s2); border-bottom: 1px solid var(--rule); }
.summary-card > .lbl { display: block; }
/* The one place a tag wraps: the sleep, mood, and last logged tags
   carry a date and an average each, and on a phone a tag that will
   not wrap forced the card wider than the screen. */
.summary-card .exercise-tag { white-space: normal; }
.summary-card .fill-legend { margin: 0; padding: var(--s3) var(--s4) 0; background: var(--base); border: 1px solid var(--rule); border-bottom: 0; }
.summary-figure { margin: 0; padding: var(--s3) var(--s4) var(--s4); background: var(--base); border: 1px solid var(--rule); border-top: 0; }
.summary-figure-caption { display: block; text-align: center; }
.summary-figure .body-figure { margin: var(--s2) 0 0; }
.summary-empty { display: flex; flex-direction: column; align-items: flex-start; gap: var(--s3); padding: var(--s2) 0; }
.summary-columns { display: grid; grid-template-columns: 1fr; gap: var(--s4); }
@media (min-width: 768px) { .summary-columns { grid-template-columns: 1fr 1fr; } }
.summary-column { display: flex; flex-direction: column; }
.summary-column > .lbl { display: block; padding-bottom: var(--s2); border-bottom: 1px solid var(--rule); }
.summary-dot { display: inline-block; width: 8px; height: 8px; margin-right: var(--s2); vertical-align: middle; border: 1px solid var(--rule); flex-shrink: 0; }
.summary-dot.is-low { background: var(--surface-up); }
.summary-dot.is-mid { background: var(--alarm-dim); }
.summary-dot.is-high { background: var(--alarm); }
.summary-dot.is-none { background: transparent; }
.summary-none { font-family: var(--fm); font-size: var(--t-label); letter-spacing: .06em; text-transform: uppercase; color: var(--text-dim); padding: var(--s2) 0; }
.summary-collapsed .record-row { padding-left: var(--s4); }

/* Tabs: two modes of one card, index.html's sign in and create
   account. Monochrome per the two tier rule: the active tab is --text
   with a --text underline, the rest --text-dim. 44px tall. */
.tabs { display: flex; border-bottom: 1px solid var(--rule); margin-bottom: var(--s5); }
.tab {
  flex: 1;
  min-height: 44px;
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  font-family: var(--fd);
  font-weight: 600;
  font-size: .875rem;
  text-transform: uppercase;
  letter-spacing: .1em;
  color: var(--text-dim);
  cursor: pointer;
  transition: color 120ms ease-out;
}
.tab:hover { color: var(--text-body); }
.tab[aria-selected="true"] { color: var(--text); border-bottom-color: var(--text); }
.tab:focus-visible { outline: 2px solid var(--alarm); outline-offset: -2px; }

/* Choice list: STYLE.md's row list with a radio in each row, for a
   short single choice where every option needs a line of explanation
   a <select> cannot carry (onboarding's training goal). Name in the
   display face, description beneath in prose; the checked row's name
   lifts to --text. Overrides .field label's mono treatment, since the
   row is a <label> but reads as content, not a form label. */
.choice-list { border-top: 1px solid var(--rule); }
.choice-list .choice {
  display: flex;
  align-items: flex-start;
  gap: var(--s3);
  min-height: 44px;
  padding: var(--s3) 0;
  border-bottom: 1px solid var(--rule);
  cursor: pointer;
  font-family: var(--fb);
  font-size: var(--t-sm);
  letter-spacing: normal;
  text-transform: none;
  color: var(--text-body);
}
.choice input { margin-top: .35em; flex-shrink: 0; accent-color: var(--text); }
.choice input:focus-visible { outline: 2px solid var(--alarm); outline-offset: 2px; }
.choice-name {
  display: block;
  font-family: var(--fd);
  font-weight: 600;
  font-size: var(--t-body);
  color: var(--text-body);
  letter-spacing: .02em;
}
.choice:has(input:checked) .choice-name { color: var(--text); }

.toggle {
  background: none;
  border: none;
  color: var(--text-dim);
  font-family: var(--fb);
  font-size: var(--t-sm);
  cursor: pointer;
  text-decoration: underline;
  padding: var(--s2) 0;
}
.toggle:hover { color: var(--text-body); }
.toggle:focus-visible { outline: 2px solid var(--alarm); outline-offset: 2px; }

.dismiss {
  background: none;
  border: 1px solid var(--rule);
  color: var(--text-dim);
  font-family: var(--fm);
  font-size: .625rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  padding: 7px 11px;
  cursor: pointer;
  white-space: nowrap;
  min-height: 44px;
  transition: color 120ms ease-out, border-color 120ms ease-out;
}
.dismiss:hover { color: var(--text); border-color: var(--text-dim); }
.dismiss:focus-visible { outline: 2px solid var(--alarm); outline-offset: 2px; }

.actions {
  display: flex;
  gap: var(--s3);
  margin-top: var(--s5);
  padding-top: var(--s5);
  border-top: 1px solid var(--rule);
  flex-wrap: wrap;
}
.actions .btn { width: auto; flex: 1; }

/* onboarding.html's steps are fieldsets, for the grouping, and the
   browser draws a fieldset a border, padding, and a minimum width of
   its content; none of that is the design's. The only fieldsets in
   the app. */
fieldset { border: 0; padding: 0; margin: 0; min-width: 0; }
.step[hidden] { display: none; }
/* .btn sets display: inline-flex, which beats the hidden attribute's
   own display: none — without this, setting .hidden on a button does
   nothing at all. Same reason .step and .notice need theirs. */
.btn[hidden] { display: none; }
.viewlink[hidden] { display: none; }
/* Talk's last seven days line, coach.html: a hint in --text-body, and
   when a muscle is at 4 or 5 the notable case per STYLE.md's two tier
   rule, brighter, never a second colour. */
#summary-line.is-notable { color: var(--text); }

.guardrail-block {
  border: 1px solid var(--rule);
  padding: var(--s4);
  margin-bottom: var(--s4);
}
.guardrail-block .dismiss { margin-top: var(--s2); }

/* Tier one by default: something the reader needs to act on before
   they can continue. .notice--info downgrades to tier two for
   messages that are just informational (STYLE.md's two tier rule). */
.notice {
  font-family: var(--fb);
  font-size: var(--t-sm);
  color: var(--alarm);
  margin-bottom: var(--s4);
}
.notice[hidden] { display: none; }
.notice--info { color: var(--text-body); }

/* Report-style page header: brand + title, hairline beneath. */
.pagehead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s4);
  flex-wrap: wrap;
  margin-bottom: var(--s5);
  padding-bottom: var(--s4);
  border-bottom: 1px solid var(--rule);
}

.filters { display: flex; gap: var(--s3); flex-wrap: wrap; margin-bottom: var(--s4); }
.filters .field { flex: 1; min-width: 200px; margin-bottom: 0; }

/* Exercise library: a scrollable row list, each row a native
   <details> so the baseline form expands in place with no JS needed
   for the toggle itself. */
.exercise-list { border-top: 1px solid var(--rule); max-height: 60vh; overflow-y: auto; }

.exercise-row summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  padding: var(--s3) 0;
  border-bottom: 1px solid var(--rule);
  cursor: pointer;
  list-style: none;
}
.exercise-row summary::-webkit-details-marker { display: none; }
.exercise-row summary:hover .exercise-name { color: var(--text); }
.exercise-row summary:focus-visible { outline: 2px solid var(--alarm); outline-offset: -2px; }

.exercise-name { font-family: var(--fd); font-weight: 600; font-size: var(--t-body); color: var(--text-body); letter-spacing: .02em; }

/* Confirm baselines' row heading: the loud counterpart to
   .exercise-name above, for a row where the exercise itself is one of
   the two things the eye should land on, not a label beside a form. */
.row-name {
  font-family: var(--fd);
  font-weight: 600;
  font-size: var(--t-md);
  color: var(--text);
  letter-spacing: .06em;
  text-transform: uppercase;
}
.exercise-meta { display: flex; align-items: center; gap: var(--s3); flex-shrink: 0; }
.exercise-tag {
  font-family: var(--fm);
  font-size: var(--t-label);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-dim);
  white-space: nowrap;
}
.exercise-tag.baselined { color: var(--text); }

/* Import's exercise match step: a plain two column row per name, and
   a disclosure styled like the library's above so its <details> don't
   render the browser's default triangle. */
.match-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  padding: var(--s3) 0;
  border-bottom: 1px solid var(--rule);
}

.match-details summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  padding: var(--s3) 0;
  border-bottom: 1px solid var(--rule);
  cursor: pointer;
  list-style: none;
}
.match-details summary::-webkit-details-marker { display: none; }
.match-details summary:focus-visible { outline: 2px solid var(--alarm); outline-offset: -2px; }

.baseline-form { padding: var(--s4) 0 var(--s5); border-bottom: 1px solid var(--rule); }
.baseline-fields { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s3); margin-bottom: var(--s4); }
@media (max-width: 480px) { .baseline-fields { grid-template-columns: 1fr; } }

/* The logged set treatment, per STYLE.md: three numbers, not a
   formula. Values loud in the display face; separators, units, and
   the RPE marker recede to mono punctuation, raised half a line, so
   the eye reads load, reps, RPE as distinct values instead of a
   sentence. The most repeated element in the app, so it lives here
   once rather than copied per page. .set--md is the one size down
   variant for a set shown as evidence under a bigger figure. */
.set {
  display: flex;
  align-items: baseline;
  gap: var(--s2);
  font-family: var(--fd);
  font-weight: 700;
  font-size: var(--t-xl);
  line-height: 1;
  color: var(--text);
  letter-spacing: .02em;
  flex-wrap: wrap;
}
.set .x, .set .u {
  font-family: var(--fm);
  font-weight: 400;
  font-size: var(--t-label);
  color: var(--text-dim);
  letter-spacing: .08em;
  transform: translateY(-.4em);
}
.set--md { font-size: var(--t-md); }

/* A value and the marker naming it are one unit and never split across
   a wrap: in a narrow column the line was putting RPE at the end of
   one line and its number at the start of the next. Which side the
   marker sits on is not uniform — LB, SETS, and REPS follow their
   number, RPE precedes its own — so which nodes belong together is
   decided in the markup and this keeps whichever pair it wraps on one
   line. Same gap and baseline as .set itself, so a grouped pair looks
   exactly like the ungrouped one it replaces. */
.set-part { display: flex; align-items: baseline; gap: var(--s2); flex-wrap: nowrap; }

/* A missing value is a fact, not a failure, so it never reads as an
   error: mono, dim, bracketed. */
.missing { font-family: var(--fm); font-size: var(--t-sm); color: var(--text-dim); letter-spacing: .04em; }

/* Client roster: a row list, same shape as the exercise library above. */
.client-list { border-top: 1px solid var(--rule); }

.client-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  padding: var(--s3) 0;
  border-bottom: 1px solid var(--rule);
}
.client-row-inactive { color: var(--text-dim); }

.client-row-actions { display: flex; align-items: center; gap: var(--s3); flex-shrink: 0; }

.client-name { font-family: var(--fd); font-weight: 600; font-size: var(--t-body); color: var(--text-body); letter-spacing: .02em; }
.client-row-inactive .client-name { color: var(--text-dim); }
.client-id { font-family: var(--fm); font-size: var(--t-sm); letter-spacing: .06em; color: var(--text-body); }

.client-status {
  font-family: var(--fm);
  font-size: var(--t-label);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-dim);
  white-space: nowrap;
}

/* CSV import preview: cell text is data, so mono. Scrolls inside its
   own container so a wide export never makes the page scroll sideways. */
.csv-preview-scroll { overflow-x: auto; border: 1px solid var(--rule); margin-bottom: var(--s4); }
.csv-preview { border-collapse: collapse; font-family: var(--fm); font-size: var(--t-sm); white-space: nowrap; }
.csv-preview th, .csv-preview td { padding: var(--s2) var(--s4); border-bottom: 1px solid var(--rule); text-align: left; }
.csv-preview th { color: var(--text); text-transform: uppercase; letter-spacing: .06em; }
.csv-preview tr:last-child td { border-bottom: none; }

/* A two way toggle (import's pounds/kilograms question) needs to show
   which side is picked without a second hue — brightness carries it,
   same as the two tier signal rule, no alarm colour involved. */
.unit-toggle-active { background: var(--surface-up); border-color: var(--text); color: var(--text); }

.session-email {
  font-family: var(--fd);
  font-weight: 700;
  font-size: var(--t-md);
  color: var(--text);
  letter-spacing: .02em;
  margin-bottom: var(--s5);
  word-break: break-all;
}

/* Working indicator: three dots that pulse in sequence after the
   element's own text, for anything waiting on the network (STYLE.md's
   Motion section). The dots take the element's text color by
   inheritance, so they are monochrome wherever they sit and never
   --alarm. One bullet glyph with its fill hidden; the three dots are
   its text shadows, which is what lets a single pseudo element pulse
   them one at a time. A label that carries the class drops its
   trailing ellipsis, since the dots say the same thing. */
.working::after {
  content: '\2022';
  display: inline-block;
  width: 1.4em;
  margin-left: .45em;
  -webkit-text-fill-color: transparent;
  --dot-dim: color-mix(in srgb, currentColor 30%, transparent);
  text-shadow: 0 0 0 currentColor, .5em 0 0 var(--dot-dim), 1em 0 0 var(--dot-dim);
  animation: working-pulse 1.2s ease-in-out infinite;
}
@keyframes working-pulse {
  0%, 100% { text-shadow: 0 0 0 currentColor, .5em 0 0 var(--dot-dim), 1em 0 0 var(--dot-dim); }
  33%      { text-shadow: 0 0 0 var(--dot-dim), .5em 0 0 currentColor, 1em 0 0 var(--dot-dim); }
  66%      { text-shadow: 0 0 0 var(--dot-dim), .5em 0 0 var(--dot-dim), 1em 0 0 currentColor; }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
  /* The dots hold still at full opacity rather than pulsing. */
  .working::after { text-shadow: 0 0 0 currentColor, .5em 0 0 currentColor, 1em 0 0 currentColor; }
}
