/* body.css — the Body cards (js/tab_body.js), hosted by Progress since round
 * 12. Every class is prefixed .body-; the palette, the card, the button and
 * the chart all come from base.css untouched.
 */

/* ------------------------------------------- screens, not scrolls (BS10) -- */

/* A breaker open is "past a limit", which is the one thing --red means here.
 * The row's value is the span before the chevron; this sheet may not name
 * the base sheet's class, so it is reached by position. */
.body-row-alert > span:nth-last-child(2) { color: var(--red); }

/* The host a screen shows. The base sheet strips a `.card` pushed directly;
 * a card inside a host is stripped the same way — the screen is the box and
 * the screen title has already said the card's title. The recovery banner
 * keeps a bordered box of its own: it is an alert, not a section. */
.body-screen-host { display: grid; gap: 12px; }
.body-screen-host > .card { background: none; padding: 0; border-radius: 0; }
.body-screen-host > .card > .card-title { display: none; }
.body-screen-host > .body-recovery { padding: 12px 14px; border-radius: var(--radius-sm); }

/* ------------------------------------------------------- recovery banner -- */

.body-recovery {
  display: grid;
  gap: 10px;
  border: 1px solid var(--line);
}
.body-recovery.is-amber { border-color: var(--ink-3); }
.body-recovery.is-red { border-color: var(--red-dim); }

.body-recovery-head {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.body-recovery-title {
  font: 600 var(--fs-5)/1.3 var(--font);
  color: var(--ink);
}
.body-recovery-reasons {
  margin: 0;
  padding-left: 18px;
  display: grid;
  gap: 4px;
  color: var(--ink-2);
  font: 400 var(--fs-3)/1.45 var(--font);
}
.body-recovery-advice {
  color: var(--ink-2);
  font: 400 var(--fs-3)/1.5 var(--font);
}

/* ---------------------------------------------------------------- charts -- */

.body-chart { display: grid; gap: 6px; }

/* --------------------------------------- what the EMA did not judge (§A4) -- */


.body-excl-list { gap: 10px; }
.body-excl {
  display: grid;
  gap: 5px;
  padding: 11px 12px;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.body-excl-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
}
.body-excl-when { font: 600 var(--fs-4)/1.3 var(--font); color: var(--ink); }
.body-excl-lb { font: 600 var(--fs-4)/1.3 var(--font); color: var(--ink-2); }
.body-excl-src {
  margin-left: auto;
  font: 500 var(--fs-1)/1.4 var(--font);
  letter-spacing: -.01em;
  color: var(--ink-3);
}
.body-excl-why { font: 400 var(--fs-3)/1.45 var(--font); color: var(--ink-2); }
.body-excl-note { font: 400 var(--fs-2)/1.45 var(--font); color: var(--ink-3); }

/* QA 2026-09-01 §3 — the rule that fired, beside the reading it dropped, so a
 * sodium print and an unfasted one are told apart without reading a paragraph.
 * It sits after the source, which is why the source keeps its `margin-left`. */
.body-excl-rule { flex: 0 0 auto; }
/* What the filter does NOT exclude. It reads as a rule, not as a caption, so
 * it is fenced off from the list rather than tucked under the law above it. */
.body-excl-context {
  padding: 10px 12px;
  border-left: 2px solid var(--line);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  background: var(--surface-2);
}

/* ------------------------------------------------------- the daily stack -- */

.body-supplements { display: grid; gap: 12px; }
.body-supplements .card-title { margin-bottom: 0; }

/* ui.dateNav grew a week strip under the day row, so the control is two rows
 * tall and lands hard against the count below it. It gets its own air. */
.body-supp-nav { margin-bottom: 4px; }

.body-supp-head {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
.body-supp-when {
  margin-left: auto;
  font: 500 var(--fs-2)/1.4 var(--font);
  color: var(--ink-3);
}

/* "2 of 5" as a ring, with what is still to take inside it. Boxless for the
 * same reason the hero's goal ring is: the card is the box. */
.body-supp-ring {
  flex: none;
  background: none;
  padding: 0;
}

.body-supp-list { display: grid; gap: 8px; }

.body-supp-item {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: var(--tap);
  padding: 10px 12px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--ink-2);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color .15s var(--ease), color .15s var(--ease);
}
.body-supp-item:hover { border-color: var(--line); }
/* Taken is a selection, and this app draws a selection in white ink — red is
 * kept for the primary action, the active tab and going over. */
.body-supp-item.is-on { color: var(--ink); border-color: var(--ink); }

.body-supp-box {
  flex: none;
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border: 1.5px solid var(--ink-3);
  border-radius: var(--radius-xs);
  font: 700 var(--fs-3)/1 var(--font);
  color: var(--bg);
}
.body-supp-item.is-on .body-supp-box {
  background: var(--ink);
  border-color: var(--ink);
}

.body-supp-label {
  font: 500 var(--fs-3)/1.4 var(--font);
  min-width: 0;
  word-break: break-word;
}

.body-supp-chelation { margin-top: 2px; }

/* fb_278a63d7 — the amount beside a dose the pack gives a unit. One line per
 * dose: the name, a narrow number field, the unit. */
.body-supp-amounts { display: grid; gap: 8px; }
.body-supp-amount {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 96px auto;
  align-items: center;
  gap: 8px;
}
.body-supp-amount-name { font: 500 var(--fs-3)/1.4 var(--font); min-width: 0; }
.body-supp-amount-unit { font: 500 var(--fs-2)/1.4 var(--font); color: var(--ink-3); }
.body-supp-amount-unsaid { grid-column: 1 / -1; }

/* ----------------------------------------------- the daily log (fb_a9eddd98) */

.body-daily { display: grid; gap: 12px; }
.body-daily .card-title { margin-bottom: 0; }
.body-bm-quick {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
}
.body-daily-head {
  margin: 4px 0 0;
  font: 600 var(--fs-2)/1.4 var(--font);
  color: var(--ink-2);
}
.body-daily-list {
  display: grid;
  gap: 6px;
  margin: 0;
  padding: 0;
  list-style: none;
  font: 500 var(--fs-3)/1.4 var(--font);
}
.body-weigh-row[data-in-series="false"] { color: var(--ink-2); }
.body-daily-night { margin: 0; }
.body-provenance { margin: 4px 0 0; }
.body-kpis { display: grid; gap: 6px; }

/* ------------------------------------------------- body fat, five ways --- */

.body-methods { display: grid; gap: 12px; }
.body-methods .card-title { margin-bottom: 0; }

.body-methods-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(122px, 1fr));
  gap: 12px;
}


.body-methods-list { display: grid; border-top: 1px solid var(--line); }

/* Flat rows on hairlines, like every list on Progress (no boxed cards). */
.body-method {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 2px 12px;
  padding: 10px 0;
  border-bottom: 1px solid var(--line);
}
.body-method-name {
  font: 600 var(--fs-4)/1.35 var(--font);
  color: var(--ink);
  min-width: 0;
}
.body-method-pct {
  font: 600 var(--fs-4)/1.3 var(--font);
  color: var(--ink);
  text-align: right;
  white-space: nowrap;
}
.body-method-when {
  grid-column: 1 / -1;
  font: 400 var(--fs-2)/1.4 var(--font);
  color: var(--ink-2);
}

/* ------------------------------------------------------------------ steps -- */

.body-steps { display: grid; gap: 12px; }
.body-steps .card-title { margin-bottom: 0; }

.body-steps-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(104px, 1fr));
  gap: 12px;
}

.body-health {
  display: grid;
  gap: 8px;
  padding: 12px;
  background: var(--surface-2);
  border-radius: var(--radius-sm);
}
.body-health-title {
  margin: 0;
  font: 600 var(--fs-4)/1.3 var(--font);
  color: var(--ink);
}
.body-health-say {
  margin: 0;
  font: 400 var(--fs-3)/1.5 var(--font);
  color: var(--ink-2);
}

.body-health-url-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.body-health-url {
  flex: 1 1 220px;
  min-width: 0;
  overflow-x: auto;
  padding: 9px 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius-xs);
  background: var(--surface);
  color: var(--ink);
  font: 500 var(--fs-2)/1.45 ui-monospace, "SF Mono", "Cascadia Mono", Menlo, monospace;
  white-space: nowrap;
  -webkit-user-select: all;
  user-select: all;
}
.body-health-copy { flex: none; }

/* The envelope a Shortcut has to send, quoted verbatim inside the sentence
 * that explains it. Build Spec 7 §F forbids a raw identifier as DISPLAY TEXT;
 * `step_count` here is not a name the app chose for something, it is the exact
 * JSON key the phone must post. So it is set as code — mono, on its own tint,
 * wrapping rather than pushing the card sideways — and reads as quoted source
 * instead of as the app speaking in keys. */
.body-health-code {
  font: 500 var(--fs-2)/1.5 var(--font);
  color: var(--ink-2);
  overflow-wrap: anywhere;
  word-break: break-word;
}

/* ------------------------------------------------------------ composition -- */

.body-composition { display: grid; gap: 12px; }
.body-composition .card-title { margin-bottom: 0; }

.body-comp-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(122px, 1fr));
  gap: 12px;
}
.body-comp-corridor { margin-top: -4px; }
.body-comp-calibrated { margin-top: -4px; }

/* ------------------------------------------------------------------ DEXA -- */
/* The scans listed under Composition, and the screen each one opens. Tables
 * read like the chart tables in base.css, always open. */

.body-dexa-scans, .body-dexa-part { display: grid; gap: 8px; }
.body-dexa { display: grid; gap: 16px; }
.body-dexa .card-title { margin-bottom: 0; }
.body-dexa-title {
  margin: 0;
  font: 600 var(--fs-4)/1.3 var(--font);
  color: var(--ink);
}
.body-dexa-scroll { overflow-x: auto; }
.body-dexa-table { width: 100%; border-collapse: collapse; font-size: var(--fs-3); }
.body-dexa-table th, .body-dexa-table td {
  text-align: right;
  padding: 6px 8px;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.body-dexa-table th:first-child { text-align: left; }
.body-dexa-table thead th { color: var(--ink-3); font-weight: 600; }
.body-dexa-table tbody th { color: var(--ink); font-weight: 500; }
.body-dexa-table td { color: var(--ink-2); }

/* ------------------------------------------------- calipers: the JP7 panel -- */

.body-calipers { display: grid; gap: 12px; }
.body-calipers .card-title { margin-bottom: 0; }
.body-jp7-say { margin-top: -4px; }

/* The baseline line: the pack's own starting panel, that it came from the pack
 * rather than from a caliper session, and — quoted, in the mono face, because
 * it is a citation and not prose — which key in the pack it is. */
.body-jp7-baseline {
  display: flex;
  align-items: baseline;
  gap: 6px;
  flex-wrap: wrap;
  padding: 9px 11px;
  border-left: 2px solid var(--line);
  border-radius: var(--radius-xs);
  background: var(--surface-2);
  font: 400 var(--fs-3)/1.45 var(--font);
  color: var(--ink-2);
}
.body-jp7-baseline strong { color: var(--ink); font-weight: 600; }
.body-jp7-ref {
  flex-basis: 100%;
  font-size: var(--fs-1);
  color: var(--ink-3);
  word-break: break-word;
}

.body-jp7-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(122px, 1fr));
  gap: 12px;
}

.body-jp7-sites { display: grid; gap: 4px; }

/* One fold per row: name, the reading, what the baseline panel had there, and
 * the difference. Four columns on a phone would crush the name, so the name
 * takes the top line and the three figures sit under it. */
.body-jp7-site {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: baseline;
  gap: 2px 10px;
  padding: 8px 10px;
  border-radius: var(--radius-xs);
  background: var(--surface-2);
}
.body-jp7-name {
  grid-column: 1;
  font: 500 var(--fs-3)/1.35 var(--font);
  color: var(--ink);
  min-width: 0;
}
.body-jp7-mm {
  grid-column: 2;
  font: 600 var(--fs-3)/1.35 var(--font);
  color: var(--ink);
  white-space: nowrap;
}
.body-jp7-delta {
  grid-column: 3;
  font: 600 var(--fs-3)/1.35 var(--font);
  color: var(--ink-2);
  text-align: right;
  white-space: nowrap;
}
.body-jp7-base {
  grid-column: 1 / -1;
  font: 500 var(--fs-1)/1.4 var(--font);
  letter-spacing: -.01em;
  color: var(--ink-3);
}

.body-jp7-conds {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.body-jp7-conds-k {
  font: 500 var(--fs-2)/1.4 var(--font);
  color: var(--ink-3);
}

/* The sheet's running total. It is arithmetic on screen, not a stored value —
 * the server recomputes the sum from the folds — so it reads as a readout. */
.body-jp7-live {
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  font: 600 var(--fs-4)/1.4 var(--font);
  letter-spacing: -.01em;
  color: var(--ink);
}

/* ------------------------------------------------------------------ tape -- */

.body-tape { display: grid; gap: 12px; }
.body-tape .card-title { margin-bottom: 0; }

.body-tape-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 10px;
}

.body-tape-site {
  display: grid;
  gap: 8px;
  padding: 12px;
  background: var(--surface-2);
  border-radius: var(--radius-sm);
  min-width: 0;
}

.body-tape-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
}
.body-tape-name {
  font: 600 var(--fs-4)/1.3 var(--font);
  color: var(--ink);
  flex: 1 1 auto;
}
.body-tape-now {
  font: 600 var(--fs-4)/1.3 var(--font);
  color: var(--ink-2);
  /* The figure keeps the right edge on every site; a "Goal met" or a fence
   * pill sits before it, not after it. */
  order: 2;
}
.body-tape-site .progress-track { background: var(--line); }
.body-tape-chart { margin-top: 2px; }
.body-tape-site .empty { font-size: var(--fs-3); }

/* ------------------------------------------------------------- telemetry -- */

.body-telemetry { display: grid; gap: 12px; }
.body-telemetry .card-title { margin-bottom: 0; }

.body-telemetry-status {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.body-telemetry-advice {
  font: 400 var(--fs-3)/1.45 var(--font);
  color: var(--ink-2);
  flex: 1 1 220px;
}

.body-metric-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
}
.body-metric-name {
  font: 600 var(--fs-4)/1.3 var(--font);
  color: var(--ink);
}
.body-metric-now {
  font: 600 var(--fs-3)/1.3 var(--font);
  color: var(--ink-2);
}

/* ---------------------------------------------------------------- photos -- */

.body-photos { display: grid; gap: 12px; }
.body-photos .card-title { margin-bottom: 0; }

.body-photo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: 8px;
}

.body-photo {
  display: grid;
  gap: 6px;
  padding: 6px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--ink-2);
  font: inherit;
  text-align: left;
  cursor: pointer;
  min-height: var(--tap);
  transition: border-color .15s var(--ease);
}
.body-photo:hover { border-color: var(--line); }
/* "Picked for compare" is a selection, and the app already has one way to draw
 * a selection: white ink, like .wk-chip-on and the set checkmark. Red stays on
 * its allowlist — active tab, primary action, calorie ring, chart series,
 * over budget. */
.body-photo.is-picked { border-color: var(--ink); color: var(--ink); }

.body-photo-img {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 4;
  object-fit: cover;
  border-radius: var(--radius-xs);
  background: var(--line);
}
.body-photo-img.is-missing {
  display: grid;
  place-items: center;
  aspect-ratio: 3 / 4;
  border-radius: var(--radius-xs);
  background: var(--line);
}
.body-photo-cap {
  font: 500 var(--fs-1)/1.35 var(--font);
  color: var(--ink-3);
  word-break: break-word;
}

.body-compare { display: grid; gap: 8px; }
.body-compare-pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
}
.body-compare-side { display: grid; gap: 6px; margin: 0; }
.body-compare-img {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 4;
  object-fit: cover;
  border-radius: var(--radius-sm);
  background: var(--line);
}
.body-compare-img.is-missing {
  display: grid;
  place-items: center;
  aspect-ratio: 3 / 4;
  border-radius: var(--radius-sm);
  background: var(--line);
}
.body-compare-cap {
  font: 500 var(--fs-2)/1.35 var(--font);
  color: var(--ink-3);
}
.body-compare-delta {
  font: 600 var(--fs-4)/1.3 var(--font);
  color: var(--ink);
}

/* ---------------------------------------------------------------- sheets -- */

.body-sheet { display: grid; gap: 12px; }

.body-sheet-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 10px;
}

.body-sheet-actions {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
  margin-top: 4px;
}

.body-field { display: grid; gap: 6px; }

.body-file {
  padding: 10px 12px;
  line-height: 1.3;
}

/* ------------------------------------------- the conditions block (§B3) --- */

/* Every form carries it, so it has to read as one object rather than as four
 * more fields: boxed, titled, and set slightly apart from the measurement it
 * qualifies. The controls default to the morning protocol — visible defaults a
 * person can correct, never a server-side assumption — and every one of them
 * offers "Not stated", which is what makes the refusal below a real choice
 * instead of an unreachable branch. */
.body-cond {
  display: grid;
  gap: 10px;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.body-cond-legend {
  font: 600 var(--fs-4)/1.3 var(--font);
  color: var(--ink);
}
.body-cond-say { margin-top: -4px; }
.body-cond-row { display: grid; gap: 6px; }

.body-cond-chips {
  display: flex;
  align-items: center;
  gap: 5px;
  flex-wrap: wrap;
}
/* A caption chip sits under a photo, not in a card header — one step down. */
.body-cond-chip { padding: 3px 8px; font-size: var(--fs-1); }

/* A pill never wraps, so a condition that is a whole sentence — "BIA reports
 * dehydration as improved hydration" — gets a line of its own rather than
 * pushing the card sideways. Same mono register, one step quieter. */
.body-cond-text {
  flex-basis: 100%;
  font: 500 var(--fs-1)/1.45 var(--font);
  letter-spacing: -.01em;
  color: var(--ink-3);
  word-break: break-word;
}

/* ------------------------------------------------ the eufy panel (§B3) --- */

.body-eufy {
  display: grid;
  gap: 8px;
  padding: 12px;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.body-eufy-title {
  font: 600 var(--fs-4)/1.3 var(--font);
  color: var(--ink);
}

/* ------------------ the whole scale print, on a weigh-in (QA §6) --------- */

/* The common weigh-in is one number typed in eleven seconds, so the panel is
 * folded away: one tap from open, never in the way of the number that matters.
 * The summary is a real tap target at 44px, like every other control here. */
.body-weigh-panel {
  display: grid;
  gap: 8px;
  padding: 0 12px 12px;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.body-weigh-panel > summary {
  display: flex;
  align-items: center;
  min-height: 44px;
  margin: 0 -12px;
  padding: 0 12px;
  font: 600 var(--fs-4)/1.3 var(--font);
  color: var(--ink);
  cursor: pointer;
}
/* The focus ring is the base sheet's, and deliberately not re-declared here —
 * it sets no radius on purpose (see base.css) and this summary wants the same. */
.body-weigh-panel[open] > summary { margin-bottom: 4px; }

.body-weigh-source { display: grid; gap: 6px; }

/* The duplicate advisory (QA P2). Amber-adjacent, never red: the reading was
 * WRITTEN, and nothing here is an error — it is a question about two rows. */
.body-dupe { display: grid; gap: 8px; }
.body-dupe-head { font: 400 var(--fs-4)/1.45 var(--font); }
.body-dupe-what {
  font: 500 var(--fs-3)/1.45 var(--font);
  letter-spacing: -.01em;
  word-break: break-word;
}
.body-dupe-rule,
.body-dupe-law { color: var(--ink-3); }
.body-dupe-actions { grid-template-columns: minmax(0, 1fr); }

/* --------------------------------- the photo frame + checklist (§B3) --- */

/* The framing rules go ON the frame. A progress photo is only evidence when the
 * conditions repeat, and the one that decides whether two shots can be compared
 * at all is the arms at 40° — so the guide draws that angle and the checklist
 * sits over it, where the eye already is. */
.body-shot-frame {
  position: relative;
  display: grid;
  place-items: stretch;
  aspect-ratio: 4 / 5;
  max-height: 340px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--bg);
  overflow: hidden;
}
.body-shot-preview {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.body-shot-guide {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
.body-shot-arm {
  stroke: var(--accent-ink);
  stroke-width: 1.4;
  stroke-linecap: round;
  opacity: .85;
}
.body-shot-axis {
  stroke: var(--ink-3);
  stroke-width: 1;
  stroke-dasharray: 4 5;
}

/* The list rides the bottom of the frame on a scrim, so it stays readable over
 * a bright photo without hiding the pose behind it. */
.body-shotlist {
  position: absolute;
  inset: auto 0 0 0;
  display: grid;
  gap: 2px;
  max-height: 74%;
  overflow-y: auto;
  padding: 8px;
  background: color-mix(in srgb, var(--bg) 76%, transparent);
}
.body-shot-item {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  width: 100%;
  padding: 6px 8px;
  border: 1px solid transparent;
  border-radius: var(--radius-xs);
  background: transparent;
  color: var(--ink-2);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: color .15s var(--ease), border-color .15s var(--ease);
}
.body-shot-item:hover { border-color: var(--line); }
/* Done is a selection, and this app draws a selection in white ink. */
.body-shot-item.is-on { color: var(--ink); }
.body-shot-box {
  flex: none;
  width: 16px;
  height: 16px;
  margin-top: 1px;
  border: 1.5px solid var(--ink-3);
  border-radius: var(--radius-xs);
}
.body-shot-item.is-on .body-shot-box {
  background: var(--ink);
  border-color: var(--ink);
}
.body-shot-text {
  font: 500 var(--fs-2)/1.4 var(--font);
  min-width: 0;
}

/* fb_98a0eafb item 5 — the reference-set checklist sits in the sheet, not on
 * the photo, and the latest set is compared with the last one pose by pose. */
.body-set-list {
  display: grid;
  gap: 2px;
}
.body-set-compare {
  display: grid;
  gap: 8px;
  margin-bottom: 12px;
}
.body-set-compare-title {
  font: 600 var(--fs-3)/1.3 var(--font);
  color: var(--ink-2);
  margin: 0;
}

/* fb_98a0eafb item 4 — the night-episode list. */
.body-episode-list {
  display: grid;
  gap: 12px;
}
.body-episode {
  display: grid;
  gap: 2px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--line);
}
.body-episode-head {
  font: 600 var(--fs-4)/1.3 var(--font);
  color: var(--ink);
  margin: 0;
}
.body-episode-notes {
  font: 400 var(--fs-3)/1.4 var(--font);
  color: var(--ink-2);
  margin: 0;
}
.body-episode-report {
  display: inline-block;
  margin-top: 8px;
  color: var(--accent-ink);
  font: 600 var(--fs-4)/1.3 var(--font);
}
.body-episode-notes-input {
  min-height: 72px;
  resize: vertical;
}

/* ------------------------------------------------------------ responsive -- */

@container (min-width: 488px) {
  .body-tape-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .body-supp-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@container (max-width: 376px) {
  .body-photo-grid { grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); }
}

@media (prefers-reduced-motion: reduce) {
  .body-supp-item { transition: none; }
  .body-photo { transition: none; }
  .body-shot-item { transition: none; }
}

/* ------------------------------------------------- the proportion figure -- */

.body-morph { display: grid; gap: 12px; }
.body-morph .card-title { margin-bottom: 0; }
.body-morph-say { margin-top: -4px; }

.body-morph-controls {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.body-morph-view,
.body-morph-mode { flex: 1 1 160px; min-width: 0; }

/* The figure sits on the card's own ground, centred, and never grows past a
 * comfortable reading width — a body twice as tall as the text beside it reads
 * as a poster, not as a measurement. */
.body-morph-stage {
  display: grid;
  place-items: center;
  padding: 8px 0 2px;
}
.body-figure {
  display: block;
  width: 100%;
  max-width: 240px;
  height: auto;
  overflow: visible;
}

/* The body itself: a filled silhouette in the card's second grey, outlined in
 * secondary ink. Filled, because the torso has to cover the tops of the limbs
 * for the figure to read as one body rather than five parts. */
.body-figure-path {
  fill: var(--surface-2);
  stroke: var(--ink-2);
  stroke-width: 1.2;
  stroke-linejoin: round;
}
.body-figure-torso { fill: var(--surface-2); }

/* The state you are not standing at, drawn as an outline behind the figure.
 * The target is the one coloured thing on this card, and since Build Spec 4 §C3
 * that colour is the PERSON's accent: red now means over-budget and nothing
 * else, and a goal outline is not a warning. */
.body-figure-ghost-path {
  fill: none;
  stroke-width: 1;
  stroke-linejoin: round;
  stroke-dasharray: 5 4;
}
.body-figure-ghost-now .body-figure-ghost-path { stroke: var(--ink-3); }
.body-figure-ghost-goal .body-figure-ghost-path { stroke: var(--accent-ink); opacity: .8; }

/* An inferred site gets a dashed tick across the part it governs — the drawing
 * says "guessed here" in the same place the badge does. */
.body-figure-mark {
  stroke: var(--ink-3);
  stroke-width: 1;
}

.body-morph-scrub { display: grid; gap: 6px; }

.body-morph-ends {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  font: 500 var(--fs-2)/1.4 var(--font);
  color: var(--ink-3);
}

.body-morph-slider,
.body-morph-timeline {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  min-height: var(--tap);
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
}
.body-morph-slider::-webkit-slider-runnable-track,
.body-morph-timeline::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: 2px;
  background: var(--line);
}
.body-morph-slider::-moz-range-track,
.body-morph-timeline::-moz-range-track {
  height: 4px;
  border-radius: 2px;
  background: var(--line);
}
.body-morph-slider::-webkit-slider-thumb,
.body-morph-timeline::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 22px;
  height: 22px;
  margin-top: -9px;
  border: 2px solid var(--bg);
  border-radius: 50%;
  background: var(--accent-mark);
}
.body-morph-slider::-moz-range-thumb,
.body-morph-timeline::-moz-range-thumb {
  width: 22px;
  height: 22px;
  border: 2px solid var(--bg);
  border-radius: 50%;
  background: var(--accent-mark);
}

.body-morph-scrub-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
}
.body-morph-when {
  font: 500 var(--fs-2)/1.4 var(--font);
  color: var(--ink-2);
}
.body-morph-play { flex: none; }
/* Playing is a state, and this app draws a state in white ink — red stays on
 * its allowlist. Solid, because the resting button is a dashed-free ghost and
 * the eye reads the filled border as "engaged". */
.body-morph-play[aria-pressed="true"] {
  color: var(--ink);
  border-color: var(--ink);
}

.body-morph-readout {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
  gap: 6px;
}
.body-morph-chip {
  display: grid;
  gap: 1px;
  padding: 7px 9px;
  /* Transparent by default so an estimated chip and a measured one have
   * identical box metrics and the row stays on one baseline grid. */
  border-left: 2px solid transparent;
  border-radius: var(--radius-xs);
  background: var(--surface-2);
  min-width: 0;
}
/* An inferred site, marked where the number is actually read — dragging the
 * slider, the eye is on this chip, not on the delta table below. Dashed in
 * --ink-3 is the same sentence the SVG's .body-figure-mark says on the figure
 * and the .body-morph-est pill says in the table: nobody put a tape here.
 * Never red: an estimate is not an error. Written to survive either shape the
 * tab might emit — the attribute alone, or the attribute plus a pill — and to
 * stay quiet if a site is ever marked data-estimated="false". */
.body-morph-chip[data-estimated]:not([data-estimated="false"]) {
  border-left-color: var(--ink-3);
  border-left-style: dashed;
}
/* Flex, baseline, wrapping — the same row the delta table's name cell is, so a
 * badge sits on the label's baseline in both places. It wraps rather than
 * overflows: a 96px chip is narrower than "Shoulders" plus a pill. */
.body-morph-chip-k {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 2px 5px;
  min-width: 0;
  font: 500 var(--fs-1)/1.3 var(--font);
  color: var(--ink-3);
}
/* The table's badge is a full pill; in a chip this narrow it has to give back
 * its horizontal padding to fit beside the label. */
.body-morph-chip .body-morph-est { flex: none; padding: 0 5px; }
.body-morph-chip-v {
  font: 600 var(--fs-3)/1.3 var(--font);
  color: var(--ink);
}

.body-morph-lag {
  font: 400 var(--fs-3)/1.5 var(--font);
  color: var(--ink-2);
}
.body-morph-lag strong { color: var(--ink); }

.body-morph-deltas { display: grid; gap: 4px; }

.body-morph-delta {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) auto auto auto minmax(0, .9fr);
  align-items: baseline;
  gap: 8px;
  padding: 8px 10px;
  border-left: 2px solid transparent;
  border-radius: var(--radius-xs);
  background: var(--surface-2);
}
/* Addressed by attribute, not by an `is-` class: this stylesheet is only
 * allowed to speak in .body- selectors, and the laggard ranking comes from
 * /api/morph/deltas, never from a rule written here. */
.body-morph-delta[data-laggard] { border-left-color: var(--accent-mark); }

.body-morph-delta-name {
  display: flex;
  align-items: baseline;
  gap: 6px;
  flex-wrap: wrap;
  font: 500 var(--fs-3)/1.35 var(--font);
  color: var(--ink);
  min-width: 0;
}
.body-morph-delta-now,
.body-morph-delta-target {
  font: 500 var(--fs-3)/1.35 var(--font);
  color: var(--ink-2);
  white-space: nowrap;
}
.body-morph-delta-arrow { color: var(--ink-3); }
.body-morph-delta-diff {
  font: 600 var(--fs-3)/1.35 var(--font);
  color: var(--ink);
  text-align: right;
  white-space: nowrap;
}

.body-morph-est {
  padding: 1px 6px;
  border: 1px solid var(--line);
  border-radius: 999px;
  font: 500 var(--fs-1)/1.5 var(--font);
  color: var(--ink-3);
  white-space: nowrap;
}

.body-morph-n,
.body-morph-est-hint { margin-top: -2px; }

/* Narrow: the name takes its own line and the three numbers sit under it, so a
 * 390px screen never has to scroll sideways to read a delta. */
@container (max-width: 448px) {
  .body-morph-delta {
    grid-template-columns: auto auto minmax(0, 1fr) auto;
    row-gap: 2px;
  }
  .body-morph-delta-name { grid-column: 1 / -1; grid-row: 1; }
  .body-morph-delta-now { grid-column: 1; grid-row: 2; }
  .body-morph-delta-arrow { grid-column: 2; grid-row: 2; }
  .body-morph-delta-target { grid-column: 3; grid-row: 2; justify-self: start; }
  .body-morph-delta-diff { grid-column: 4; grid-row: 2; }
}

/* ---------------------------------------------------- the Fitted Mirror --- */

/* The card around the 3-D figure. The figure ITSELF is styled by css/mirror.css
 * — every class in it is `mirror-` prefixed and this sheet may only carry
 * `body-` ones, so there is no overlap and no duplication: js/tab_body.js links
 * that sheet the first time the mirror is used. What lives here is the frame,
 * the milestone chips and the two honesty lines under the figure.
 *
 * No hue of its own, like the rest of this sheet. --accent means "this person",
 * --red means "past a limit" and nothing else, and a miss can never be read as
 * a hit. */

.body-mirror { display: grid; gap: 12px; }
.body-mirror .card-title { margin-bottom: 0; }
.body-mirror-say { margin-top: -4px; }

/* The mount point. It reserves the figure's own 3:4 box BEFORE the mesh
 * arrives, so the cards below do not jump 300px down the page when it does. */
.body-mirror-host { display: grid; gap: 12px; min-height: 120px; }

/* ---- the milestone chips ----
 *
 * The scrubber underneath is continuous; these are its named stops, and they
 * are the only place the pack's `model:` provenance is legible at a glance.
 * Which is the point: a projected body and a measured one must never look
 * alike, so the provenance is on its own line inside every chip rather than
 * hidden behind a tap. */
.body-mirror-stops {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
  padding-bottom: 2px;
}
.body-mirror-stops:empty { display: none; }

.body-mirror-stop {
  flex: none;
  scroll-snap-align: start;
  display: grid;
  gap: 1px;
  justify-items: start;
  min-height: var(--tap);
  padding: 6px 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--ink-2);
  text-align: left;
  cursor: pointer;
}

.body-mirror-stop[aria-pressed="true"] {
  border-color: var(--accent-mark);
  background: var(--accent-wash);
  color: var(--ink);
}

.body-mirror-stop-name {
  font: 600 var(--fs-3)/1.25 var(--font);
  color: var(--ink);
  white-space: nowrap;
}

/* A projection is dimmer than a measurement, and it says the word as well —
 * colour alone would leave the distinction unreadable to anyone who cannot see
 * it, and this is the one distinction on the card that must never be missed. */
.body-mirror-stop-model {
  font: 500 var(--fs-1)/1.4 var(--font);
  color: var(--ink-3);
  white-space: nowrap;
}
.body-mirror-stop[data-model="measured"] .body-mirror-stop-model,
.body-mirror-stop[data-model="measured_pending_dexa"] .body-mirror-stop-model {
  color: var(--ink-2);
}
/* A pressed stop is on the wash, where text is ink or ink-2 only. */
.body-mirror-stop[aria-pressed="true"] .body-mirror-stop-model,
.body-mirror-stop[aria-pressed="true"] .body-mirror-stop-when { color: var(--ink-2); }

.body-mirror-stop-when {
  font: 400 var(--fs-1)/1.4 var(--font);
  color: var(--ink-3);
  white-space: nowrap;
}

/* ---- the two lines under the figure ---- */

/* The caliper's answer, not the solver's. It carries a verdict attribute rather
 * than an `is-` class because this sheet may only address `body-` selectors,
 * and a state hook the stylesheet cannot legally reach is no hook at all. */
.body-mirror-proof {
  margin: 0;
  padding: 8px 10px;
  border-left: 2px solid var(--line);
  border-radius: 0 var(--radius-xs) var(--radius-xs) 0;
  background: var(--surface-2);
  font: 500 var(--fs-3)/1.5 var(--font);
  color: var(--ink-2);
}
.body-mirror-proof[hidden] { display: none; }
.body-mirror-proof[data-verdict="hit"] { border-left-color: var(--accent-mark); color: var(--ink); }
.body-mirror-proof[data-verdict="miss"] { border-left-color: var(--red); color: var(--ink); }

.body-mirror-source,
.body-mirror-excuses { margin-top: -2px; }
.body-mirror-excuses[hidden] { display: none; }


/* Every tape site on its first panel: the card says so once (tapeCard), so
 * each site's own "one reading so far" line is not drawn nine times over. */
.body-tape.body-tape-first-panel .body-tape-chart { display: none; }
.body-tape-first { margin: 0; }
