/* Two real weights rather than one synthesised across a range: a browser
   faking 700 from a 600 file smears the wordmark, which is the one piece of
   type on screen that is the brand. Latin subsets only; 13 KB each. */
@font-face {
  font-family: "Space Grotesk";
  src: url("./fonts/space-grotesk-500.woff2") format("woff2");
  font-weight: 500;
  font-display: swap;
}
@font-face {
  font-family: "Space Grotesk";
  src: url("./fonts/space-grotesk-700.woff2") format("woff2");
  font-weight: 600 700;
  font-display: swap;
}
@font-face {
  font-family: "Inter";
  src: url("./fonts/inter-variable.woff2") format("woff2");
  font-weight: 400 600;
  font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono";
  src: url("./fonts/jetbrains-mono-400.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}

/*
 * The coach app's shell.
 *
 * Hand-written CSS against the brand tokens, no framework. The JS budget is
 * 300 KB for the whole app and a utility framework spends a third of it
 * before the first row renders.
 *
 * Dark by default because this is a tool somebody opens at six in the
 * morning and at eleven at night, and because the brand is obsidian with one
 * accent. Vermilion is reserved: it means "this needs you". Nothing
 * decorative is allowed to use it, or it stops meaning anything.
 */

:root {
  --obsidian: #0B0E11;
  --graphite: #15191E;
  --iron: #212830;
  --steel: #333C47;
  --ash: #5C6773;
  --pewter: #8C97A3;
  --mist: #C3CAD2;
  --chalk: #EEF1F4;
  --vermilion: #E8502F;
  --vermilion-deep: #B8341A;

  --bg: var(--obsidian);
  --surface: var(--graphite);
  --surface-raised: var(--iron);
  --line: var(--steel);
  --fg: var(--chalk);
  --fg-muted: var(--pewter);
  --fg-dim: var(--ash);

  --font-display: "Space Grotesk", ui-sans-serif, system-ui, sans-serif;
  --font-body: Inter, ui-sans-serif, system-ui, -apple-system, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px; --s6: 24px; --s8: 32px;
  --radius: 10px;
  /* Every tappable thing clears this. A worklist used one-handed on a phone
     between sets does not get to have 32px targets. */
  --tap: 44px;
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --bg: var(--chalk);
    --surface: #FFFFFF;
    --surface-raised: #FFFFFF;
    --line: #DCE2E8;
    --fg: var(--obsidian);
    --fg-muted: var(--ash);
    --fg-dim: var(--pewter);
  }
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
  /* The roster is a list; nothing here scrolls sideways. */
  overflow-x: hidden;
}

button { font: inherit; color: inherit; }

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

.top-inner {
  display: flex;
  align-items: center;
  gap: var(--s3);
  /* Same column as the roster: a wordmark pinned to the window edge while
     the list sits in a centred column reads as two unrelated pages. */
  width: 100%;
  max-width: 760px;
  margin: 0 auto;
  /* main is border-box with the same max-width and its own side padding, so
     the header needs that padding too or it sits 32px wider than the rows. */
  padding: 0 var(--s4);
}

.top {
  position: sticky;
  top: 0;
  z-index: 10;
  padding: var(--s3) 0;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--line);
}

/* Without the blur the header is simply translucent, and the grid scrolls
   through it. Opaque is the right fallback, not a nicer-looking bug. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .top { background: var(--bg); }
}

.wordmark {
  white-space: nowrap;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  margin: 0;
}
.wordmark span { color: var(--fg-dim); font-weight: 500; letter-spacing: 0.2em; }

.top .spacer { flex: 1; }

.freshness {
  margin: 0 0 var(--s3);
  text-align: right;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--fg-dim);
  /* It may wrap on a narrow phone; it must never widen the page. */
  overflow-wrap: anywhere;
  /* Reserved whether or not there is text, so nothing shifts on first load. */
  min-height: 1.2em;
}
.freshness[data-stale="true"] { color: var(--vermilion); }

.ghost {
  white-space: nowrap;
  min-height: var(--tap);
  padding: 0 var(--s3);
  background: none;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--fg-muted);
  cursor: pointer;
}
.ghost:hover { color: var(--fg); border-color: var(--ash); }
.ghost:focus-visible { outline: 2px solid var(--vermilion); outline-offset: 2px; }

/* ------------------------------------------------------------------ layout */

/*
 * The shell.
 *
 * A coach works at a computer -- that is where check-ins get answered
 * and programmes get written -- so the desktop layout uses the screen
 * it is given rather than a reading column down the middle. A 760px
 * strip on a 27-inch monitor wastes two thirds of the space a coach
 * paid for, and this app's densest surfaces (the daily log, the
 * check-in grid, a photo comparison) are exactly the ones that want
 * the width.
 *
 * Phone keeps its own shape, which is a different design and not a
 * squeezed version of this one.
 */
main { max-width: 760px; margin: 0 auto; padding: var(--s4) var(--s4) var(--s8); }
.top-inner { max-width: 760px; }

@media (min-width: 900px) {
  main,
  .top-inner {
    /* Full width, with a gutter wide enough that nothing touches the
       edge of the glass. */
    max-width: none;
    padding-left: var(--s6);
    padding-right: var(--s6);
  }
}

/*
 * Prose still gets a reading measure.
 *
 * Full width is right for a table and wrong for a paragraph: a line
 * of text 1,800px long is genuinely hard to read, and the coach's own
 * notes and a client's written answers are prose. The shell is wide;
 * the words inside it are not.
 */
.panel p,
.panel .note { max-width: 70ch; }

/*
 * The reply sits under the check-in rows, full width.
 *
 * It was side by side for a while. The grid is the widest thing on
 * this screen and wants all of the window, and a reply box squeezed
 * into a third of it is a worse place to write than one the full
 * width underneath. What was actually wrong with the old reply panel
 * was that it re-rendered the whole check-in inside itself, and that
 * is fixed either way.
 */
.panel-reply textarea { min-height: 9rem; }

@media (min-width: 1280px) {
  /* Room to write on a big screen, without becoming a wall. */
  .panel-reply textarea { min-height: 11rem; }
  .panel-reply .reply { max-width: 70rem; }
}

/* The roster's rows carry a strip of summary cells; on a wide screen
   they should all be visible rather than scrolled to. */
body:has(.roster) main { max-width: 1180px; }
body:has(.roster) .top-inner { max-width: 1180px; }

@media (min-width: 900px) {
  body:has(.roster) main,
  body:has(.roster) .top-inner { max-width: none; }
}

h2.section {
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fg-dim);
  margin: var(--s6) 0 var(--s2);
}
h2.section:first-child { margin-top: var(--s2); }

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

.roster { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s2); }

.row {
  display: grid;
  grid-template-columns: 40px 1fr auto;
  align-items: center;
  gap: var(--s3);
  width: 100%;
  min-height: 64px;
  padding: var(--s3);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  text-align: left;
  cursor: pointer;
  /* No transition on background: a worklist should feel instant, and 150ms
     of fade on tap is the difference between an app and a web page. */
}
.row:hover { border-color: var(--ash); }
.row:focus-visible { outline: 2px solid var(--vermilion); outline-offset: 2px; }

/* The accent is a 2px edge, not a fill. A list of filled red rows reads as
   an outage; a list of red edges reads as a queue. */
.row[data-attention="waiting"] { border-left: 2px solid var(--vermilion); }
.row[data-attention="quiet"] { border-left: 2px solid var(--steel); }

.avatar {
  display: grid;
  place-items: center;
  width: 40px; height: 40px;
  border-radius: 50%;
  background: var(--surface-raised);
  color: var(--fg-muted);
  font-family: var(--font-display);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.04em;
}

.who { min-width: 0; }
/* Each of these is its own line. As inline spans they ran together into one
   sentence, which reads as prose rather than as a scannable row. */
.name, .since, .trouble { display: block; }
.name {
  font-weight: 550;
  font-size: 15px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.since { font-size: 13px; color: var(--fg-muted); }
.trouble { font-size: 12px; color: var(--vermilion); }

.badge {
  font-family: var(--font-mono);
  font-size: 11px;
  padding: var(--s1) var(--s2);
  border-radius: 999px;
  white-space: nowrap;
}
.badge[data-kind="waiting"] { background: var(--vermilion); color: #fff; }
.badge[data-kind="quiet"] { background: var(--surface-raised); color: var(--fg-muted); }

/* ------------------------------------------------------------------- states */

.note {
  padding: var(--s8) var(--s4);
  text-align: center;
  color: var(--fg-muted);
}
.note strong { display: block; color: var(--fg); font-family: var(--font-display); }

/* Skeletons rather than a spinner: the layout is known before the data is,
   so nothing moves when the rows arrive. */
.skeleton { pointer-events: none; }
.skeleton .avatar, .skeleton .bar {
  background: linear-gradient(90deg, var(--surface-raised), var(--steel), var(--surface-raised));
  background-size: 200% 100%;
  animation: shimmer 1.4s linear infinite;
  color: transparent;
}
.bar { height: 11px; border-radius: 4px; }
.bar.w-name { width: 42%; margin-bottom: var(--s2); }
.bar.w-since { width: 28%; }
@keyframes shimmer { to { background-position: -200% 0; } }
@media (prefers-reduced-motion: reduce) {
  .skeleton .avatar, .skeleton .bar { animation: none; }
}

/* ------------------------------------------------------------------ sign-in */

.signin {
  max-width: 380px;
  margin: 12vh auto;
  padding: var(--s8) var(--s6);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  text-align: center;
}
.signin h1 { font-size: 15px; margin: 0 0 var(--s2); }
.signin p { color: var(--fg-muted); font-size: 14px; margin: 0 0 var(--s6); }
.signin .err { color: var(--vermilion); font-size: 13px; min-height: 1.4em; }

.banner {
  position: fixed;
  left: var(--s4); right: var(--s4); bottom: var(--s4);
  max-width: 720px;
  margin: 0 auto;
  padding: var(--s3) var(--s4);
  background: var(--vermilion-deep);
  color: #fff;
  border-radius: var(--radius);
  font-size: 14px;
}
.banner[hidden] { display: none; }

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

.crumb {
  display: flex;
  align-items: center;
  gap: var(--s2);
  min-height: var(--tap);
  padding: 0 var(--s2) 0 0;
  background: none;
  border: 0;
  color: var(--fg-muted);
  cursor: pointer;
  font-size: 14px;
}
.crumb:hover { color: var(--fg); }
.crumb:focus-visible { outline: 2px solid var(--vermilion); outline-offset: 2px; }

.client-head { margin: 0 0 var(--s4); }
.client-head h2 {
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 600;
  margin: 0;
}
.client-head .sub { color: var(--fg-muted); font-size: 14px; }

.panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  margin-bottom: var(--s4);
  overflow: hidden;
}
.panel > h3 {
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fg-dim);
  margin: 0;
  padding: var(--s3) var(--s4);
  border-bottom: 1px solid var(--line);
}

/*
 * The grid: weeks down, questions across, exactly as the sheet lays it out.
 *
 * It scrolls sideways inside its own panel -- the page never does. The week
 * column is sticky, because a coach comparing week 12 to week 3 needs to
 * know which row they are on after scrolling past twenty questions.
 */
/*
 * Its own scroll box, in both directions.
 *
 * Horizontal because the grid is wider than any phone by design. Vertical
 * because `position: sticky` resolves against the nearest scrolling
 * ancestor: without a bounded box the column headings stick to the viewport
 * and slide under the app's own header instead of staying above the grid.
 */
.gridwrap {
  overflow: auto;
  max-height: min(70vh, 640px);
  overscroll-behavior: contain;
  /*
   * A fade at the right edge when there is more table than panel.
   *
   * Overlay scrollbars are invisible until touched, so a table that
   * is cut off looks like a table that ends there -- and a coach
   * does not scroll sideways to find a column they do not know
   * exists. The gradient is pinned to the scroll container and only
   * shows while there is something to the right of it.
   */
  background:
    linear-gradient(to right, var(--surface) 30%, transparent) left / 2rem 100% no-repeat local,
    linear-gradient(to left, var(--surface) 30%, transparent) right / 2rem 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%, rgb(0 0 0 / 0.22), transparent)
      left / 1rem 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%, rgb(0 0 0 / 0.22), transparent)
      right / 1rem 100% no-repeat scroll;
}
.grid { border-collapse: collapse; font-size: 14px; }
.grid th, .grid td {
  padding: var(--s2) var(--s3);
  border-bottom: 1px solid var(--line);
  border-right: 1px solid var(--line);
  text-align: left;
  vertical-align: top;
}
.grid thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--surface-raised);
  color: var(--fg-muted);
  font-weight: 550;
  font-size: 12px;
  /* Wide enough to read, narrow enough that eight fit on a laptop. */
  min-width: 150px;
  max-width: 260px;
}
/*
 * A long answer is clamped rather than allowed to set the row height.
 *
 * One client writing five lines about their knee made every other cell in
 * that week a tall empty box on a phone -- and the paragraph was three
 * columns off-screen, so the coach could not even see what had caused it.
 * The full text is one tap away and in the title attribute.
 */
.grid td { max-width: 260px; }

/*
 * The clamp goes on an element INSIDE the cell, never on the cell.
 *
 * `display: -webkit-box` on a <td> stops it being a table cell at all: the
 * answers stacked vertically down each row and the grid came apart. The
 * inner box clamps; the cell stays a cell.
 */
.grid .cellbox {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
}
/* The open row is NOT unclamped. Expanding it made a phone screen of empty
   boxes out of every other answer in that week, for the sake of a paragraph
   three columns off to the right that the coach could not see anyway. The
   whole of the selected week is in the reply panel instead, where they are
   about to answer it. */

.saidwrap { padding: var(--s4) var(--s4) 0; }
.said { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s3); }
.said dt {
  font-size: 12px;
  color: var(--fg-dim);
  margin-bottom: var(--s1);
}
.said dd { margin: 0; font-size: 15px; white-space: pre-wrap; }
.said { margin: 0; }
.grid .wk {
  position: sticky;
  left: 0;
  z-index: 3;
  min-width: 96px;
  background: var(--surface-raised);
  white-space: nowrap;
}
.grid thead .wk { z-index: 4; }
.grid tbody tr:hover td { background: color-mix(in srgb, var(--steel) 22%, transparent); }
/* The open row is marked by lift, not by colour: vermilion means "this needs
   you", and a red wash across the row the coach happens to have open says
   something the app does not mean. */
.grid tbody tr[aria-selected="true"] td,
.grid tbody tr[aria-selected="true"] th { background: var(--iron); }
.grid tbody tr[aria-selected="true"] .wk { box-shadow: inset 2px 0 0 var(--mist); }
.grid td.num { text-align: right; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.grid td.empty { color: var(--fg-dim); }
.grid .date { font-weight: 550; }
.grid .wknum { display: block; font-size: 11px; color: var(--fg-dim); font-family: var(--font-mono); }

.dot {
  display: inline-block;
  width: 7px; height: 7px;
  border-radius: 50%;
  margin-right: var(--s1);
  vertical-align: middle;
}
.dot[data-state="waiting"] { background: var(--vermilion); }
.dot[data-state="sending"] { background: var(--mist); }
.dot[data-state="replied"] { background: var(--ash); }
.dot[data-state="reviewed"] { background: var(--ash); }

.dormant {
  padding: var(--s3) var(--s4);
  font-size: 13px;
  color: var(--fg-dim);
  border-top: 1px solid var(--line);
}
.dormant button {
  background: none; border: 0; padding: 0;
  color: var(--fg-muted); text-decoration: underline; cursor: pointer;
}

/* ------------------------------------------------------------------ reply */

.reply { padding: var(--s4); display: grid; gap: var(--s3); }
/* A rule between what the client said and what the coach is about to write,
   so the two are not read as one block of text. */
.saidwrap + .reply { border-top: 1px solid var(--line); margin-top: var(--s4); }
.reply label {
  display: block;
  font-size: 12px;
  color: var(--fg-dim);
  margin-bottom: var(--s1);
}
.reply textarea, .reply input[type="url"] {
  width: 100%;
  padding: var(--s3);
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  font: inherit;
  resize: vertical;
}
.reply textarea { min-height: 110px; }
.reply textarea.short { min-height: 70px; }
.reply textarea:focus, .reply input:focus { outline: 2px solid var(--vermilion); outline-offset: -1px; }
.reply .row-actions { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; }
.reply .check { display: flex; align-items: center; gap: var(--s2); min-height: var(--tap); cursor: pointer; }
.reply .check input { width: 18px; height: 18px; accent-color: var(--vermilion); }
.reply .state { font-size: 12px; color: var(--fg-dim); margin-left: auto; }

.primary {
  min-height: var(--tap);
  padding: 0 var(--s6);
  background: var(--vermilion);
  color: #fff;
  border: 0;
  border-radius: var(--radius);
  font-weight: 550;
  cursor: pointer;
}
.primary:disabled { background: var(--steel); color: var(--fg-dim); cursor: default; }
.primary:focus-visible { outline: 2px solid var(--chalk); outline-offset: 2px; }

/* ----------------------------------------------------------- measurements */

.sites { list-style: none; margin: 0; padding: 0; }
.sites li {
  display: flex;
  align-items: baseline;
  gap: var(--s3);
  padding: var(--s3) var(--s4);
  border-bottom: 1px solid var(--line);
}
.sites li:last-child { border-bottom: 0; }
.sites .site { flex: 1; }
.sites .latest { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.sites .delta { font-family: var(--font-mono); font-size: 13px; min-width: 72px; text-align: right; }
.sites .delta[data-kind="good"] { color: var(--mist); }
.sites .delta[data-kind="against"] { color: var(--vermilion); }
.sites .delta[data-kind="neutral"] { color: var(--fg-dim); }

/* -------------------------------------------------------- the roster row */

/*
 * A row is an identity block plus a strip of summary cells.
 *
 * The strip scrolls sideways rather than dropping columns on a phone: which
 * columns a coach wants on a phone is not knowable from here, and guessing
 * wrong hides the one they came for.
 */
.rrow {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}
.rrow[data-attention="waiting"] { border-left: 2px solid var(--vermilion); }
.rrow[data-attention="quiet"] { border-left: 2px solid var(--steel); }

.who-btn {
  display: grid;
  grid-template-columns: 40px 1fr auto;
  align-items: center;
  gap: var(--s3);
  width: 100%;
  min-height: 64px;
  padding: var(--s3);
  background: none;
  border: 0;
  text-align: left;
  cursor: pointer;
}
.who-btn:hover { background: color-mix(in srgb, var(--steel) 18%, transparent); }
.who-btn:focus-visible { outline: 2px solid var(--vermilion); outline-offset: -2px; }

.cells {
  display: flex;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  /* Surface, not line: a 1px gap trick leaves the unused end of a short
     strip showing as a dark block the width of whatever is left over. The
     separators are borders on the cells themselves. */
  background: var(--surface);
  border-top: 1px solid var(--line);
}
.cell {
  flex: 0 0 auto;
  border-right: 1px solid var(--line);
  min-width: 112px;
  min-height: 58px;
  padding: var(--s2) var(--s3);
  background: var(--surface);
  border: 0;
  text-align: left;
  cursor: pointer;
  display: grid;
  align-content: center;
  gap: 2px;
}
.cell:last-child { border-right: 0; }
.cell:hover { background: var(--surface-raised); }
.cell:focus-visible { outline: 2px solid var(--vermilion); outline-offset: -2px; }
.clabel {
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-dim);
  white-space: nowrap;
}
.cvalue {
  font-size: 13px;
  color: var(--fg);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 230px;
}
.cnote {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--fg-muted);
}
/* Direction is stated, not coloured: on this roster a loss may be the plan
   or may be a problem, and the app does not know which. */
.cnote[data-dir="up"]::before { content: "▲ "; }
.cnote[data-dir="down"]::before { content: "▼ "; }
.cell[data-late="true"] .cvalue { color: var(--vermilion); }

/* --------------------------------------------------------- section tabs */

/* Scrolls on a phone, wraps on anything wider.
   The eighth tab pushed "Their link" out of the visible row on a 1100px
   desktop -- present in the DOM, reachable only by scrolling a row that
   gives no sign it scrolls. A tab nobody can see is a feature nobody has. */
.tabs {
  display: flex;
  gap: var(--s1);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  margin-bottom: var(--s4);
  padding-bottom: var(--s1);
  border-bottom: 1px solid var(--line);
}
.tab {
  flex: 0 0 auto;
  min-height: var(--tap);
  padding: 0 var(--s3);
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  color: var(--fg-muted);
  font-size: 14px;
  cursor: pointer;
  white-space: nowrap;
}
@media (min-width: 720px) {
  .tabs { flex-wrap: wrap; overflow-x: visible; }
}

.tab:hover { color: var(--fg); }
.tab[aria-current="page"] { color: var(--fg); border-bottom-color: var(--vermilion); }
.tab:focus-visible { outline: 2px solid var(--vermilion); outline-offset: -2px; }

/* -------------------------------------------------------------- photos */

/* Screen-reader-only. Each week picker needs a label saying which side it
   aims, and on screen the pane itself already says that. */
.vh {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.poses {
  display: flex;
  gap: var(--s2);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  padding-bottom: var(--s2);
  margin-bottom: var(--s3);
}
.pose {
  flex: 0 0 auto;
  min-height: 32px;
  padding: 0 var(--s3);
  background: var(--surface-raised);
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--fg-muted);
  font-size: 13px;
  white-space: nowrap;
  cursor: pointer;
}
.pose:hover { color: var(--fg); }
.pose[aria-current="true"] {
  background: var(--vermilion-deep);
  border-color: var(--vermilion);
  color: var(--chalk);
}
/* A pose with nothing uploaded is still offered -- that it is empty is
   itself the answer -- but it does not look like one that has photos. */
.pose[data-bare="true"] { color: var(--fg-dim); font-style: italic; }
.pose:focus-visible { outline: 2px solid var(--vermilion); outline-offset: 2px; }

.span {
  margin: 0 0 var(--s3);
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--fg-muted);
}

/* Two panes side by side at every width, phones included. Stacked, each
   photo is bigger and the screen stops doing its job: a before and after
   the coach has to scroll between is not a comparison, and the phone is
   where check-ins get read. The frame is capped in vh so both panes, their
   captions, their pickers and the filmstrip fit one screen. */
.compare {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s2);
}
@media (min-width: 720px) {
  .compare { gap: var(--s4); }
}
.shot { margin: 0; min-width: 0; }
/* Both panes get the same frame, and the photo is fitted inside it rather
   than filling it. Two shots of the same person a month apart are rarely
   the same aspect -- framed differently, cropped differently, one portrait
   and one not -- and a comparison where the two sides are different sizes
   is a comparison of the framing. Capped at 62vh so the captions and the
   filmstrip stay on screen with the photos. */
.shot img {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 4;
  max-height: 46vh;
  object-fit: contain;
  border-radius: var(--radius);
  background: var(--surface-raised);
}
.pick { display: block; margin-top: var(--s2); }
.pick select {
  width: 100%;
  min-height: 36px;
  padding: 0 var(--s2);
  background: var(--surface-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--fg);
  font-family: var(--font-body);
  font-size: 13px;
}
.shot figcaption {
  margin-top: var(--s2);
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--fg-muted);
}
.noshot {
  display: grid;
  place-items: center;
  aspect-ratio: 3 / 4;
  max-height: 46vh;
  border: 1px dashed var(--line);
  border-radius: var(--radius);
  color: var(--fg-dim);
  font-size: 13px;
  text-align: center;
  padding: var(--s3);
}

/* Every week of the chosen pose, labelled. The horizontal scroll is the
   point: the strip is a filmstrip, and a wrapped one loses the sense of a
   timeline. */
.strip {
  display: flex;
  gap: var(--s2);
  list-style: none;
  margin: var(--s4) 0 0;
  padding: var(--s1) 0 var(--s2);
  overflow-x: auto;
  overscroll-behavior-x: contain;
}
.strip li { flex: 0 0 auto; }
.thumb {
  display: block;
  width: 72px;
  padding: 0;
  background: none;
  border: 2px solid transparent;
  border-radius: var(--radius);
  color: var(--fg-muted);
  cursor: pointer;
}
.thumb img, .thumb .gap {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 4;
  border-radius: calc(var(--radius) - 2px);
  background: var(--surface-raised);
  object-fit: cover;
}
.thumb .gap {
  display: grid;
  place-items: center;
  border: 1px dashed var(--line);
  color: var(--fg-dim);
}
.thumb .wk {
  display: block;
  padding-top: var(--s1);
  font-family: var(--font-mono);
  font-size: 10px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.thumb[aria-current="true"] { border-color: var(--vermilion); color: var(--fg); }
.thumb:disabled { cursor: default; opacity: 0.55; }
.thumb:focus-visible { outline: 2px solid var(--vermilion); outline-offset: 2px; }

/* ----------------------------------------------------------- their link */

/* .note is centred and heavily padded, because elsewhere it is an empty
   state filling a panel. Here it is a paragraph the coach has to read
   closely, so it reads as a paragraph. */
.freshlink .note, .linkacts + .note, .panel > .note.small {
  text-align: left;
  padding: 0;
}
.freshlink .note strong { display: inline; }

.freshlink {
  padding: var(--s4);
  margin-bottom: var(--s4);
  background: var(--surface-raised);
  border: 1px solid var(--vermilion);
  border-radius: var(--radius);
}
/* Vermilion means "this needs you", and a link shown once needs the coach
   right now: it is gone when they navigate. */
.freshlink .note strong { color: var(--vermilion); }

.linkrow {
  display: flex;
  gap: var(--s2);
  margin-top: var(--s3);
}
.linkbox {
  flex: 1 1 auto;
  min-width: 0;
  min-height: var(--tap);
  padding: 0 var(--s3);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--fg);
  font-family: var(--font-mono);
  font-size: 12px;
  /* A key is 43 characters and the box is one line: it scrolls inside
     itself rather than widening the panel on a phone. */
  text-overflow: ellipsis;
}
.linkacts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
}
.note.small { margin-top: var(--s3); font-size: 12px; color: var(--fg-dim); }
.note code {
  font-family: var(--font-mono);
  font-size: 12px;
  padding: 1px 5px;
  background: var(--surface-raised);
  border-radius: 4px;
}

/* ---------------------------------------------------------- form checks

   A player with step buttons, and the list of clips under it. The
   buttons are large: a coach steps a frame at a time with a thumb,
   often on a phone, often repeatedly.
*/
.player { margin-bottom: var(--s4); }

.player video {
  width: 100%;
  max-height: 60vh;
  background: #000;
  border-radius: var(--r2);
  display: block;
}

.player-bar {
  display: flex;
  align-items: center;
  gap: var(--s2);
  margin-top: var(--s2);
  flex-wrap: wrap;
}

.player-bar .step {
  min-width: 3rem;
  min-height: 2.75rem;      /* a thumb target, not a mouse target */
  font-size: 15px;
  color: var(--fg);
  background: var(--raise);
  border: 1px solid var(--line);
  border-radius: var(--r2);
  cursor: pointer;
}
.player-bar .step:hover { border-color: var(--accent); }
.player-bar .step:active { background: var(--line); }

.player-at {
  /* Tabular figures: a counter that shifts as the digits change is a
     counter a coach cannot read while stepping. */
  font-variant-numeric: tabular-nums;
  font-size: 14px;
  color: var(--fg-dim);
  margin-left: auto;
}

.player-rate select {
  font: inherit;
  font-size: 14px;
  color: var(--fg);
  background: var(--raise);
  border: 1px solid var(--line);
  border-radius: var(--r2);
  padding: var(--s1) var(--s2);
}

.cliplist { list-style: none; margin: 0; padding: 0; }

.clip {
  display: flex;
  align-items: stretch;
  gap: var(--s2);
  border-top: 1px solid var(--line);
}

.clip-pick {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--s2) 0;
  text-align: left;
  background: none;
  border: 0;
  color: inherit;
  font: inherit;
  cursor: pointer;
}
.clip.is-current .clip-title { color: var(--accent); font-weight: 600; }
.clip-title { font-size: 15px; }
.clip-meta { font-size: 13px; color: var(--fg-dim); }

.clip-star {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-width: 4.5rem;
  padding: var(--s2);
  background: none;
  border: 0;
  /* Dim until starred: an unstarred clip's star is an invitation, not
     a state, and a row of bright stars says nothing. */
  color: var(--fg-dim);
  font-size: 17px;
  cursor: pointer;
}
.clip-star.on { color: var(--accent); }
.clip-expiry { font-size: 11px; color: var(--fg-dim); }

/* Screen-reader-only label. */
.vh {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ------------------------------------------------------------ daily log

   A sheet, because the coach already reads a sheet fluently and the
   app has no business teaching them a second shape for the same data.
   What it adds is the folding: weeks collapse to their averages, and
   one opens to show its days.
*/
.panel-wide { padding-left: 0; padding-right: 0; }
.panel-wide > h3 { padding-left: var(--s4); }

.logscroll {
  overflow-x: auto;
  /* Momentum scrolling, because this is wider than a phone and a
     coach will swipe it. */
  -webkit-overflow-scrolling: touch;
}

.logtable {
  border-collapse: collapse;
  font-size: 13px;
  /* Tabular figures throughout: a column of numbers whose digits are
     different widths cannot be scanned down, which is the only thing
     anyone does with this table. */
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  min-width: 100%;
}

.logtable th,
.logtable td {
  padding: var(--s2) var(--s3);
  text-align: right;
  border-bottom: 1px solid var(--line);
}

.log-wk,
.log-day { text-align: left; }

/* The week gutter, written down the side like the reference. */
.log-wk {
  position: sticky;
  left: 0;
  z-index: 2;
  background: var(--bg);
  font-size: 11px;
  letter-spacing: 0.06em;
  color: var(--fg-dim);
  text-transform: uppercase;
  vertical-align: middle;
  border-right: 1px solid var(--line);
}

.log-day {
  position: sticky;
  left: 3.5rem;
  z-index: 2;
  background: var(--bg);
  color: var(--fg-dim);
  border-right: 1px solid var(--line);
}

.log-h {
  font-size: 11px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--fg-dim);
  /* The heading is the coach's own wording and can be long; it wraps
     rather than stretching the column past the numbers under it. */
  white-space: normal;
  max-width: 7rem;
  vertical-align: bottom;
}

/*
   The band row, and the colour each band carries.

   --band is set inline from the band's NAME, because the app cannot
   know what "DIET" means and the next coach will write something
   else. The stylesheet decides how saturated and how light; the name
   decides which hue. Both themes get the same hue at different
   lightness so a band is the same band in either.
*/
.log-bands th {
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-align: center;
  color: hsl(var(--band, 0) 45% 42%);
  background: hsl(var(--band, 0) 45% 42% / 0.08);
  border-bottom: 1px solid var(--line);
}
.log-bands .log-nogroup { background: none; }

.log-h[style*="--band"] { color: hsl(var(--band) 40% 44%); }
.log-c[style*="--band"] { color: hsl(var(--band) 45% 38%); }

:root[data-theme="dark"] .log-bands th { color: hsl(var(--band, 0) 55% 68%); }
:root[data-theme="dark"] .log-h[style*="--band"] { color: hsl(var(--band) 40% 60%); }
:root[data-theme="dark"] .log-c[style*="--band"] { color: hsl(var(--band) 55% 66%); }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .log-bands th { color: hsl(var(--band, 0) 55% 68%); }
  :root:not([data-theme="light"]) .log-h[style*="--band"] { color: hsl(var(--band) 40% 60%); }
  :root:not([data-theme="light"]) .log-c[style*="--band"] { color: hsl(var(--band) 55% 66%); }
}

/* The week's average row: the one a coach reads when everything is
   folded, so it carries the weight. */
.log-avg > td,
.log-avg > th {
  background: var(--raise);
  font-weight: 600;
  border-bottom: 1px solid var(--line);
}

.log-toggle {
  font: inherit;
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--fg);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}
.log-toggle:hover { color: var(--accent); }

.log-row.is-today > td { background: color-mix(in srgb, var(--accent) 7%, transparent); }
.log-row.is-today .log-day { color: var(--fg); font-weight: 600; }

/* An empty cell keeps its column's colour rather than going grey, so
   a half-filled week still reads as columns. */
.log-c { opacity: 1; }
.log-c:not([style]) { color: var(--fg); }

/* ---------------------------------------------------------------- photos */

.photobar {
  display: flex;
  align-items: center;
  gap: var(--s3);
  flex-wrap: wrap;
  padding: 0 var(--s4) var(--s3);
}

.posepick select,
.dlpick select {
  font: inherit;
  font-size: 14px;
  color: var(--fg);
  background: var(--raise);
  border: 1px solid var(--line);
  border-radius: var(--r2);
  padding: var(--s2) var(--s3);
  min-width: 11rem;
}

/* Two states of one control, so it reads as a choice rather than as
   two buttons that happen to sit together. */
.viewpick {
  display: inline-flex;
  border: 1px solid var(--line);
  border-radius: var(--r2);
  overflow: hidden;
  margin-left: auto;
}
.viewpick button {
  font: inherit;
  font-size: 13px;
  padding: var(--s2) var(--s3);
  color: var(--fg-dim);
  background: none;
  border: 0;
  cursor: pointer;
}
.viewpick button[aria-pressed="true"] {
  color: var(--bg);
  background: var(--fg);
}

/*
   The swipe comparison.

   Both photos fill the same frame, so the body is in the same place
   in each and the divider sweeps across one image rather than
   between two. --at is written by the range input as the coach
   drags, which is why nothing re-renders.
*/
/*
   One box, both photos.

   The first version let each image size itself and centred them,
   which overflowed a phone viewport by 33px -- caught by the UI
   check, not by looking. Worse in principle: two photos of different
   proportions would render at different sizes, and the divider would
   then sweep across two differently-scaled bodies, which is exactly
   the comparison it is meant to make possible.

   So the frame has the size, and both images fill it with
   object-fit: contain. Same box, same scale, same position -- the
   divider is honest whatever the camera did.
*/
.swipe {
  position: relative;
  overflow: hidden;
  background: #000;
  border-radius: var(--r2);
  margin: 0 var(--s4);
  /* Portrait, which is how a progress photo is taken. Capped in
     viewport height so it never pushes the controls off a laptop. */
  aspect-ratio: 3 / 4;
  max-height: 72vh;
}

.swipe img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.swipe-over {
  position: absolute;
  inset: 0;
  /* Clipped from the right: the later photo covers the earlier one
     up to the divider. */
  clip-path: inset(0 calc(100% - var(--at)) 0 0);
}

.swipe-line {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--at);
  width: 2px;
  background: var(--chalk, #fff);
  box-shadow: 0 0 0 1px rgb(0 0 0 / 0.35);
  pointer-events: none;
}

/* The range sits over the whole frame and is invisible: the coach
   drags the picture, not a slider under it. Still a real input, so
   it keeps keyboard and screen-reader support. */
.swipe-range {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: ew-resize;
  -webkit-appearance: none;
  appearance: none;
  background: none;
}
.swipe-range:focus-visible + .swipe-tag,
.swipe:has(.swipe-range:focus-visible) .swipe-line {
  outline: 2px solid var(--accent);
}

.swipe-tag {
  position: absolute;
  bottom: var(--s2);
  font-size: 12px;
  padding: 2px var(--s2);
  border-radius: var(--r1);
  color: #fff;
  background: rgb(0 0 0 / 0.55);
  pointer-events: none;
}
.swipe-tag.left { left: var(--s2); }
.swipe-tag.right { right: var(--s2); }

.weekpicks {
  display: flex;
  gap: var(--s3);
  padding: var(--s3) var(--s4) 0;
}
.weekpicks .pick { flex: 1; }

.dlbar {
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex-wrap: wrap;
  padding: var(--s3) var(--s4) var(--s4);
}
.dlbar button[disabled] { opacity: 0.5; cursor: progress; }

/* ----------------------------------------------------- the protocol grid
 *
 * Weeks across, compounds down, the way the Pharmacology tab is laid
 * out. The compound column is sticky because the whole point of this
 * view is reading ALONG a row, and a coach who scrolls to week 12 and
 * loses the names is reading a grid of numbers.
 */
.weeknav {
  display: flex;
  align-items: center;
  gap: var(--s3);
  margin-bottom: var(--s2);
}
.weeknav .note { margin: 0; }
.wkstep {
  min-width: 2.25rem;
  padding: var(--s1) var(--s2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm, 6px);
  background: var(--surface-raised);
  color: var(--fg);
  font: inherit;
  cursor: pointer;
}
.wkstep:disabled { opacity: 0.4; cursor: default; }
.wkstep:not(:disabled):hover { border-color: var(--vermilion); }

.protocolgrid th.wk,
.protocolgrid th.compound {
  position: sticky;
  left: 0;
  z-index: 3;
  background: var(--surface-raised);
  min-width: 11rem;
}
.protocolgrid thead th.wk { z-index: 4; }
.protocolgrid thead th {
  /* Narrow: a dose is three characters and twelve weeks should fit. */
  min-width: 4.5rem;
  max-width: 6rem;
  text-align: center;
}
/* .num sets right-align for the rest of the app and wins on
 * specificity; under a week heading a dose reads better centred. */
.protocolgrid td,
.protocolgrid td.num { text-align: center; vertical-align: middle; }
.protocolgrid .wklabel { display: block; }
.protocolgrid .wkdate {
  display: block;
  font-size: 11px;
  color: var(--fg-dim);
  font-weight: 400;
}
.protocolgrid .compound .site { display: block; }
.protocolgrid .compound .freq {
  display: block;
  font-size: 11px;
  color: var(--fg-dim);
}

/* This week, marked down the whole column rather than just its heading:
 * a coach's eye lands on the column, not on the label above it. */
.protocolgrid th.now,
.protocolgrid td.now { background: color-mix(in oklab, var(--vermilion) 14%, transparent); }
.protocolgrid thead th.now { color: var(--fg); }

/*
 * A dose that changed from the week before.
 *
 * Marked rather than coloured in: a ramp and a taper are both changes
 * and the app has no business deciding which is good. Reading a row of
 * identical numbers to find the different one is the work this saves.
 */
.protocolgrid td.changed { font-weight: 650; }
.protocolgrid td.changed::before {
  content: "▸";
  color: var(--vermilion);
  margin-right: 0.25em;
  font-size: 0.8em;
}

/* The section heading rows, which are the coach's own words. */
.protocolgrid .sectionrow th {
  position: sticky;
  left: 0;
  background: var(--surface);
  color: var(--fg-muted);
  font-size: 12px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.protonotes {
  margin: var(--s3) 0 0;
  padding-left: var(--s4);
  color: var(--fg-muted);
  font-size: 13px;
}
.protonotes li { margin: var(--s1) 0; }

/*
 * The weeks fill whatever room the panel has.
 *
 * A protocol that stops two-thirds of the way across a desktop looks
 * like a protocol that ends there. The compound column keeps its width
 * because a name needs it; the week columns share the rest.
 */
.protocolgrid { width: 100%; table-layout: auto; }
@media (min-width: 900px) {
  .protocolgrid thead th:not(.wk) { width: auto; max-width: none; }
}

/* On a phone the names give up some room so more than two weeks fit.
 * A truncated compound name is still recognisable; one week of doses
 * is not a timeline. */
@media (max-width: 560px) {
  .protocolgrid th.wk,
  .protocolgrid th.compound { min-width: 7.5rem; }
  .protocolgrid .compound .site { font-size: 13px; }
}

/* ------------------------------------------------------- the meal plan
 *
 * Day types as a row of picks, because a coach has three or four and
 * reads one at a time. Then every meal of that day, with its foods and
 * whatever columns the coach chose to track.
 */
.daypicks {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  margin-bottom: var(--s3);
}
.daypick {
  padding: var(--s2) var(--s3);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm, 6px);
  background: transparent;
  color: var(--fg-muted);
  font: inherit;
  cursor: pointer;
}
.daypick:hover { border-color: var(--vermilion); color: var(--fg); }
.daypick.on {
  background: var(--surface-raised);
  border-color: var(--vermilion);
  color: var(--fg);
  font-weight: 550;
}

.meal { margin: 0 0 var(--s4); }
.meal h4 {
  margin: 0 0 var(--s2);
  font-size: 14px;
  font-weight: 600;
}
/*
 * An option's name is the COACH'S, not ours -- "Option B - travel day"
 * is a sentence she wrote, and uppercasing it shouts it back at her.
 * The generated "Option A" reads fine either way.
 */
.optlabel {
  margin: var(--s3) 0 var(--s1);
  font-size: 12px;
  color: var(--fg-dim);
  letter-spacing: 0.02em;
}

/* ------------------------------------------------------------ adding a row */

.addrow { margin: var(--s2) 0 0; }
.protoadds { display: flex; flex-wrap: wrap; gap: var(--s2); }
.protoadds .rowform { flex: 1 1 100%; }
.addrow .ghost { font-size: 13px; min-height: 32px; }

.rowform {
  margin: var(--s3) 0 0;
  padding: var(--s3);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-raised);
}
.rowfields {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  margin: var(--s2) 0 0;
}
.rowfield { display: flex; flex-direction: column; gap: 2px; min-width: 72px; flex: 1 1 72px; }
.rowfield.wide { flex: 2 1 160px; }
.rowfield > span { font-size: 11px; color: var(--fg-muted); }
.rowfield input {
  font: inherit;
  min-height: 34px;
  padding: 0 var(--s2);
  background: var(--surface);
  color: var(--fg);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  min-width: 0;
}
.rowfield input:focus-visible { outline: 2px solid var(--vermilion); outline-offset: 1px; }
.rowfield.calc input { color: var(--fg-dim); text-align: center; }

/*
 * What the edit will do, in the worker's own words.
 *
 * describe() writes these, so the sentence a coach agrees to is the
 * sentence the thing that does the work produced -- not a second,
 * friendlier account of it written here that could drift from what
 * actually happens.
 */
.rowpreview {
  margin: var(--s3) 0 0;
  padding-left: var(--s4);
  font-size: 13px;
  color: var(--fg-muted);
}
.rowpreview li { margin: 2px 0; }
.rownote { margin: var(--s2) 0 0; font-size: 13px; color: var(--fg-muted); }
.rownote.bad { color: var(--vermilion); }
.rowactions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  margin: var(--s3) 0 0;
}
.rowactions .primary { padding: 0 var(--s4); }

.rowlog { list-style: none; margin: 0; padding: 0; }
.rowlog li {
  padding: var(--s2) 0;
  border-top: 1px solid var(--line);
}
.rowlog li:first-child { border-top: 0; }
.rowlogwhat { margin: 0; font-size: 14px; }
.rowlogwhen { margin: 2px 0 0; font-size: 13px; color: var(--fg-muted); }
/*
 * Vermilion for the two that need the coach to do something, and
 * nothing for the two that are simply in progress. A pending row is
 * not a problem and colouring it like one trains people to ignore the
 * colour.
 */
.rowlog li[data-status="refused"] .rowlogwhen,
.rowlog li[data-status="failed"] .rowlogwhen { color: var(--vermilion); }
.mealgrid { width: 100%; }
.mealgrid th.wk,
.mealgrid .foodname {
  min-width: 10rem;
  font-weight: 500;
}
.mealgrid thead th:not(.wk) { min-width: 4.5rem; text-align: right; }
/* The coach's own total row, set apart from the foods above it. */
.mealgrid tfoot th,
.mealgrid tfoot td {
  border-top: 2px solid var(--line);
  font-weight: 600;
}
